diff --git a/example/fundamentals/library-deps/bom-2-managed/build.mill b/example/fundamentals/library-deps/bom-2-managed/build.mill index a873d276ac9..7bdd52fa01b 100644 --- a/example/fundamentals/library-deps/bom-2-managed/build.mill +++ b/example/fundamentals/library-deps/bom-2-managed/build.mill @@ -53,17 +53,20 @@ object baz extends JavaModule { // Here, given that grpc-protobuf is fetched during dependency resolution, // `com.google.protobuf:protobuf-java` is excluded from it because of the dependency management. -// One manage and publish BOM modules from Mill, by using `BomModule`: +// One can manage and publish BOM modules from Mill, by using `BomModule`: object myBom extends BomModule { + // Importing external BOMs from our BOM module with bomIvyDeps def bomIvyDeps = Agg( ivy"com.google.protobuf:protobuf-bom:4.28.1" ) + // Managing versions directly from our BOM module with depManagement def depManagement = Agg( ivy"io.grpc:grpc-protobuf:1.67.1" ) } +// Use to the BOM you defined with bomModuleDeps: object bomUser extends JavaModule { def bomModuleDeps = Seq( myBom @@ -85,6 +88,8 @@ object myPublishedBom extends BomModule with MyPublishModule { ) } +// Once published, the BOM can be used in other projects, +// but we can still use right from our build with bomModuleDeps: object publishedBomUser extends JavaModule with MyPublishModule { def bomModuleDeps = Seq( myPublishedBom diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala index f91f173e3dc..3dc700b676d 100644 --- a/scalalib/src/mill/scalalib/JavaModule.scala +++ b/scalalib/src/mill/scalalib/JavaModule.scala @@ -1568,24 +1568,24 @@ trait BomModule extends JavaModule { def compile: T[CompilationResult] = Task { val sources = allSourceFiles() if (sources.nonEmpty) - throw new Exception(s"A BomModule cannot have sources") + throw new Exception("A BomModule cannot have sources") CompilationResult(T.dest / "zinc", PathRef(T.dest / "classes")) } def resources: T[Seq[PathRef]] = Task { val value = super.resources() if (value.nonEmpty) - throw new Exception(s"A BomModule cannot have ressources") + throw new Exception("A BomModule cannot have resources") Seq.empty[PathRef] } def jar: T[PathRef] = Task { - (throw new Exception(s"A BomModule doesn't have a JAR")): PathRef + (throw new Exception("A BomModule doesn't have a JAR")): PathRef } def docJar: T[PathRef] = Task { - (throw new Exception(s"A BomModule doesn't have a doc JAR")): PathRef + (throw new Exception("A BomModule doesn't have a doc JAR")): PathRef } def sourceJar: T[PathRef] = Task { - (throw new Exception(s"A BomModule doesn't have a source JAR")): PathRef + (throw new Exception("A BomModule doesn't have a source JAR")): PathRef } } diff --git a/scalalib/src/mill/scalalib/PublishModule.scala b/scalalib/src/mill/scalalib/PublishModule.scala index d14b99cbe90..272c8302126 100644 --- a/scalalib/src/mill/scalalib/PublishModule.scala +++ b/scalalib/src/mill/scalalib/PublishModule.scala @@ -27,7 +27,7 @@ trait PublishModule extends JavaModule { outer => ) } - // TODO Add this when we can break bin-compat + // TODO Add this when we can break bin-compat. See also below in publishXmlBomDeps. // override def bomModuleDeps: Seq[BomModule with PublishModule] = super.bomModuleDeps.map { // case m: BomModule with PublishModule => m // case other => @@ -227,6 +227,9 @@ trait PublishModule extends JavaModule { outer => (processedDeps.map(_.moduleVersion).toMap, depMgmt) } + /** + * Path to the ivy.xml file for this module + */ def ivy: T[PathRef] = Task { val content = ivy(hasJar = pomPackagingType != PackagingType.Pom)() val ivyPath = T.dest / "ivy.xml" @@ -234,6 +237,12 @@ trait PublishModule extends JavaModule { outer => PathRef(ivyPath) } + /** + * ivy.xml content for this module + * + * @param hasJar Whether this module has a JAR or not + * @return + */ def ivy(hasJar: Boolean): Task[String] = Task.Anon { val (results, bomDepMgmt) = defaultResolver().processDeps( Seq( diff --git a/scalalib/src/mill/scalalib/publish/Ivy.scala b/scalalib/src/mill/scalalib/publish/Ivy.scala index 687635883fe..73e2b4f30ea 100644 --- a/scalalib/src/mill/scalalib/publish/Ivy.scala +++ b/scalalib/src/mill/scalalib/publish/Ivy.scala @@ -10,6 +10,16 @@ object Ivy { case class Override(organization: String, name: String, version: String) + /** + * Generates the content of an ivy.xml file + * + * @param artifact Coordinates of the module + * @param dependencies Dependencies of the module + * @param extras Extra artifacts published alongside the module + * @param overrides Version overrides + * @param hasJar Whether the module has a JAR + * @return ivy.xml content + */ def apply( artifact: Artifact, dependencies: Agg[Dependency], diff --git a/scalalib/src/mill/scalalib/publish/LocalIvyPublisher.scala b/scalalib/src/mill/scalalib/publish/LocalIvyPublisher.scala index adc817d6d21..e4d04a198de 100644 --- a/scalalib/src/mill/scalalib/publish/LocalIvyPublisher.scala +++ b/scalalib/src/mill/scalalib/publish/LocalIvyPublisher.scala @@ -16,6 +16,19 @@ class LocalIvyPublisher(localIvyRepo: os.Path) { )(implicit ctx: Ctx.Log): Unit = publishLocal(Some(jar), Some(sourcesJar), Some(docJar), pom, Right(ivy), artifact, extras) + /** + * Publishes a module locally + * + * @param jar The JAR of this module, if it has one + * @param sourcesJar The source JAR of this module, if it has one + * @param docJar The javadoc JAR of this module, if it has one + * @param pom The POM of this module + * @param ivy If right, the path to the ivy.xml file of this module; if left, its content as a String + * @param artifact Coordinates of this module + * @param extras Extra files to publish in this module + * @param ctx + * @return The files created or written to when publishing locally this module + */ def publishLocal( jar: Option[os.Path], sourcesJar: Option[os.Path], diff --git a/scalalib/src/mill/scalalib/publish/LocalM2Publisher.scala b/scalalib/src/mill/scalalib/publish/LocalM2Publisher.scala index e420cf23845..343169bcb28 100644 --- a/scalalib/src/mill/scalalib/publish/LocalM2Publisher.scala +++ b/scalalib/src/mill/scalalib/publish/LocalM2Publisher.scala @@ -4,6 +4,18 @@ import mill.api.Ctx class LocalM2Publisher(m2Repo: os.Path) { + /** + * Publishes a module in the local Maven repository + * + * @param jar The JAR of this module, if it has one + * @param sourcesJar The source JAR of this module, if it has one + * @param docJar The javadoc JAR of this module, if it has one + * @param pom The POM of this module + * @param artifact Coordinates of this module + * @param extras Extra files to publish in this module + * @param ctx + * @return + */ def publish( jar: Option[os.Path], sourcesJar: Option[os.Path], diff --git a/scalalib/src/mill/scalalib/publish/settings.scala b/scalalib/src/mill/scalalib/publish/settings.scala index de24898ad9d..bbbf2a7e608 100644 --- a/scalalib/src/mill/scalalib/publish/settings.scala +++ b/scalalib/src/mill/scalalib/publish/settings.scala @@ -44,6 +44,8 @@ object Scope { case object Provided extends Scope case object Runtime extends Scope case object Test extends Scope + + /** Maven "import" scope, to refer to BOM modules */ case object Import extends Scope } diff --git a/scalalib/test/src/mill/scalalib/BomTests.scala b/scalalib/test/src/mill/scalalib/BomTests.scala index 6d3df7d4637..bbd100fd6b5 100644 --- a/scalalib/test/src/mill/scalalib/BomTests.scala +++ b/scalalib/test/src/mill/scalalib/BomTests.scala @@ -1048,11 +1048,15 @@ object BomTests extends TestSuite { test("chainedBoms") { test("simple") - UnitTester(modules, null).scoped { implicit eval => + // dep management of simpleOverrides has precedence over those of chainedBoms isInClassPath( modules.bomModule.chainedBoms.simpleOverrides.bomUser, "jackson-core-2.18.2.jar", Seq(modules.bomModule.chainedBoms, modules.bomModule.chainedBoms.simpleOverrides) ) + // More contrived test - simpleOverrides pulls an external BOM for protobuf-java:4.28.2, + // and an internal one that requires protobuf-java:4.28.1 via an external BOM. For now, + // the internal one takes precedence over the external one. isInClassPath( modules.bomModule.chainedBoms.simpleOverrides.bomUser, "protobuf-java-4.28.1.jar", @@ -1061,11 +1065,16 @@ object BomTests extends TestSuite { } test("crossed") - UnitTester(modules, null).scoped { implicit eval => + // Contrived test like above - crossedOverrides pulls an internal BOM that requires + // jackson-core:2.18.1 via its depManagement, and an external BOM that wants jackson-core:2.18.2. + // The internal BOM takes precedence over the external one. isInClassPath( modules.bomModule.chainedBoms.crossedOverrides.bomUser, "jackson-core-2.18.1.jar", Seq(modules.bomModule.chainedBoms, modules.bomModule.chainedBoms.crossedOverrides) ) + // crossedOverrides wants protobuf-java:4.28.2 via its depManagement. This takes + // precedence over protobuf-java:4.28.1, wanted via an internal BOM. isInClassPath( modules.bomModule.chainedBoms.crossedOverrides.bomUser, "protobuf-java-4.28.2.jar",