From d35695e67151fbcd1cc4b137defd403ace3970ff Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Mon, 25 Nov 2024 12:33:09 +0100 Subject: [PATCH 1/3] Add BOM / dependency management support --- .../ROOT/pages/fundamentals/library-deps.adoc | 28 + .../ROOT/pages/javalib/dependencies.adoc | 5 + .../ROOT/pages/kotlinlib/dependencies.adoc | 6 + .../ROOT/pages/scalalib/dependencies.adoc | 5 + .../bom-1-external-bom/build.mill | 21 + .../bom-2-dependency-management/build.mill | 54 ++ example/package.mill | 1 + main/util/src/mill/util/CoursierSupport.scala | 37 +- .../src/mill/scalalib/CoursierModule.scala | 31 ++ scalalib/src/mill/scalalib/Dep.scala | 2 + scalalib/src/mill/scalalib/JavaModule.scala | 171 ++++++- .../src/mill/scalalib/PublishModule.scala | 77 ++- scalalib/src/mill/scalalib/publish/Ivy.scala | 26 +- scalalib/src/mill/scalalib/publish/Pom.scala | 75 ++- .../test/src/mill/scalalib/BomTests.scala | 484 ++++++++++++++++++ .../src/mill/scalalib/publish/PomTests.scala | 5 +- 16 files changed, 986 insertions(+), 42 deletions(-) create mode 100644 example/fundamentals/library-deps/bom-1-external-bom/build.mill create mode 100644 example/fundamentals/library-deps/bom-2-dependency-management/build.mill create mode 100644 scalalib/test/src/mill/scalalib/BomTests.scala diff --git a/docs/modules/ROOT/pages/fundamentals/library-deps.adoc b/docs/modules/ROOT/pages/fundamentals/library-deps.adoc index 8c703ab8175..00500ae6df5 100644 --- a/docs/modules/ROOT/pages/fundamentals/library-deps.adoc +++ b/docs/modules/ROOT/pages/fundamentals/library-deps.adoc @@ -96,6 +96,34 @@ def runIvyDeps = Agg( It is also possible to use a higher version of the same library dependencies already defined in `ivyDeps`, to ensure you compile against a minimal API version, but actually run with the latest available version. +== Dependency management + +Dependency management consists in listing dependencies whose versions we want to force. Having +a dependency in dependency management doesn't mean that this dependency will be fetched, only +that + +* if it ends up being fetched transitively, its version will be forced to the one in dependency management + +* if its version is empty in an `ivyDeps` section in Mill, the version from dependency management will be used + +Dependency management also allows to add exclusions to dependencies, both explicit dependencies and +transitive ones. + +Dependency management can be passed to Mill in two ways: + +* via external Maven BOMs, like https://repo1.maven.org/maven2/com/google/cloud/libraries-bom/26.50.0/libraries-bom-26.50.0.pom[this one], +whose Maven coordinates are `com.google.cloud:libraries-bom:26.50.0` + +* via the `dependencyManagement` task, that allows to directly list dependencies whose versions we want to enforce + +=== External BOMs + +include::partial$example/fundamentals/library-deps/bom-1-external-bom.adoc[] + +=== Dependency management task + +include::partial$example/fundamentals/library-deps/bom-2-dependency-management.adoc[] + == Searching For Dependency Updates include::partial$example/fundamentals/dependencies/1-search-updates.adoc[] diff --git a/docs/modules/ROOT/pages/javalib/dependencies.adoc b/docs/modules/ROOT/pages/javalib/dependencies.adoc index 76ebe8d4449..ac2c01aef22 100644 --- a/docs/modules/ROOT/pages/javalib/dependencies.adoc +++ b/docs/modules/ROOT/pages/javalib/dependencies.adoc @@ -13,6 +13,11 @@ include::partial$example/javalib/dependencies/1-ivy-deps.adoc[] include::partial$example/javalib/dependencies/2-run-compile-deps.adoc[] +== Dependency Management + +Mill has support for dependency management, see the +xref:fundamentals/library-deps.adoc#_dependency_management[Dependency Management section] +in xref:fundamentals/library-deps.adoc[]. == Unmanaged Jars diff --git a/docs/modules/ROOT/pages/kotlinlib/dependencies.adoc b/docs/modules/ROOT/pages/kotlinlib/dependencies.adoc index a970a256d4e..9670e311702 100644 --- a/docs/modules/ROOT/pages/kotlinlib/dependencies.adoc +++ b/docs/modules/ROOT/pages/kotlinlib/dependencies.adoc @@ -19,6 +19,12 @@ include::partial$example/kotlinlib/dependencies/1-ivy-deps.adoc[] include::partial$example/kotlinlib/dependencies/2-run-compile-deps.adoc[] +== Dependency Management + +Mill has support for dependency management, see the +xref:fundamentals/library-deps.adoc#_dependency_management[Dependency Management section] +in xref:fundamentals/library-deps.adoc[]. + == Unmanaged Jars include::partial$example/kotlinlib/dependencies/3-unmanaged-jars.adoc[] diff --git a/docs/modules/ROOT/pages/scalalib/dependencies.adoc b/docs/modules/ROOT/pages/scalalib/dependencies.adoc index c4c0bf2ef89..902d8b7be15 100644 --- a/docs/modules/ROOT/pages/scalalib/dependencies.adoc +++ b/docs/modules/ROOT/pages/scalalib/dependencies.adoc @@ -16,6 +16,11 @@ include::partial$example/scalalib/dependencies/1-ivy-deps.adoc[] include::partial$example/scalalib/dependencies/2-run-compile-deps.adoc[] +== Dependency Management + +Mill has support for dependency management, see the +xref:fundamentals/library-deps.adoc#_dependency_management[Dependency Management section] +in xref:fundamentals/library-deps.adoc[]. == Unmanaged Jars diff --git a/example/fundamentals/library-deps/bom-1-external-bom/build.mill b/example/fundamentals/library-deps/bom-1-external-bom/build.mill new file mode 100644 index 00000000000..eeb3a5344c5 --- /dev/null +++ b/example/fundamentals/library-deps/bom-1-external-bom/build.mill @@ -0,0 +1,21 @@ +// Pass an external BOM to a `JavaModule` / `ScalaModule` / `KotlinModule` with `bomDeps`, like + +//// SNIPPET:BUILD1 +package build +import mill._, javalib._ + +object bom extends JavaModule { + def bomDeps = Agg( + ivy"com.google.cloud:libraries-bom:26.50.0" + ) + def ivyDeps = Agg( + ivy"io.grpc:grpc-protobuf" + ) +} + +// The version of grpc-protobuf (`io.grpc:grpc-protobuf`) isn't written down here, so the version +// from the BOM, `1.67.1` is used. +// +// Also, by default, grpc-protobuf `1.67.1` pulls version `3.25.3` of protobuf-java (`com.google.protobuf:protobuf-java`) . +// But the BOM specifies another version for that dependency, `4.28.3`, so +// protobuf-java `4.28.3` ends up being pulled here. diff --git a/example/fundamentals/library-deps/bom-2-dependency-management/build.mill b/example/fundamentals/library-deps/bom-2-dependency-management/build.mill new file mode 100644 index 00000000000..22f1fe18de3 --- /dev/null +++ b/example/fundamentals/library-deps/bom-2-dependency-management/build.mill @@ -0,0 +1,54 @@ +// Pass dependencies to `dependencyManagement` in a `JavaModule` / `ScalaModule` / `KotlinModule`, like + +//// SNIPPET:BUILD1 +package build +import mill._, javalib._ + +object dependencyManagement extends JavaModule { + def dependencyManagement = Agg( + ivy"com.google.protobuf:protobuf-java:4.28.3", + ivy"io.grpc:grpc-protobuf:1.67.1" + ) + def ivyDeps = Agg( + ivy"io.grpc:grpc-protobuf" + ) +} + +// The version of grpc-protobuf (`io.grpc:grpc-protobuf`) isn't written down here, so the version +// found in `dependencyManagement`, `1.67.1` is used. +// +// Also, by default, grpc-protobuf `1.67.1` pulls version `3.25.3` of protobuf-java (`com.google.protobuf:protobuf-java`) . +// But `dependencyManagement` specifies another version for that dependency, `4.28.3`, so +// protobuf-java `4.28.3` ends up being pulled here. + +// One can also add exclusions via dependency management, like + +object dependencyManagementWithVersionAndExclusions extends JavaModule { + def dependencyManagement = Agg( + ivy"io.grpc:grpc-protobuf:1.67.1" + .exclude(("com.google.protobuf", "protobuf-java")) + ) + def ivyDeps = Agg( + ivy"io.grpc:grpc-protobuf" + ) +} + +// Here, grpc-protobuf has an empty version in `ivyDeps`, so the one in `dependencyManagement`, +// `1.67.1`, is used. Also, `com.google.protobuf:protobuf-java` is excluded from grpc-protobuf +// in `dependencyManagement`, so it ends up being excluded from it in `ivyDeps` too. + +// If one wants to add exclusions via `dependencyManagement`, specifying a version is optional, +// like + +object dependencyManagementWithExclusions extends JavaModule { + def dependencyManagement = Agg( + ivy"io.grpc:grpc-protobuf" + .exclude(("com.google.protobuf", "protobuf-java")) + ) + def ivyDeps = Agg( + ivy"io.grpc:grpc-protobuf:1.67.1" + ) +} + +// Here, given that grpc-protobuf is fetched during dependency resolution, +// `com.google.protobuf:protobuf-java` is excluded from it because of the dependency management. diff --git a/example/package.mill b/example/package.mill index b7f53d51527..1705260e539 100644 --- a/example/package.mill +++ b/example/package.mill @@ -81,6 +81,7 @@ object `package` extends RootModule with Module { object cross extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "cross")) object `out-dir` extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "out-dir")) object libraries extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "libraries")) + object `library-deps` extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "library-deps")) } object depth extends Module { diff --git a/main/util/src/mill/util/CoursierSupport.scala b/main/util/src/mill/util/CoursierSupport.scala index c44b32c3c65..6f6ccff62b2 100644 --- a/main/util/src/mill/util/CoursierSupport.scala +++ b/main/util/src/mill/util/CoursierSupport.scala @@ -31,6 +31,20 @@ trait CoursierSupport { ctx.fold(cache)(c => cache.withLogger(new TickerResolutionLogger(c))) } + private def isLocalTestDep(dep: Dependency): Option[Seq[PathRef]] = { + val org = dep.module.organization.value + val name = dep.module.name.value + val classpathKey = s"$org-$name" + + val classpathResourceText = + try Some(os.read( + os.resource(getClass.getClassLoader) / "mill/local-test-overrides" / classpathKey + )) + catch { case e: os.ResourceNotFoundException => None } + + classpathResourceText.map(_.linesIterator.map(s => PathRef(os.Path(s))).toSeq) + } + /** * Resolve dependencies using Coursier. * @@ -51,26 +65,8 @@ trait CoursierSupport { artifactTypes: Option[Set[Type]] = None, resolutionParams: ResolutionParams = ResolutionParams() ): Result[Agg[PathRef]] = { - def isLocalTestDep(dep: Dependency): Option[Seq[PathRef]] = { - val org = dep.module.organization.value - val name = dep.module.name.value - val classpathKey = s"$org-$name" - - val classpathResourceText = - try Some(os.read( - os.resource(getClass.getClassLoader) / "mill/local-test-overrides" / classpathKey - )) - catch { case e: os.ResourceNotFoundException => None } - - classpathResourceText.map(_.linesIterator.map(s => PathRef(os.Path(s))).toSeq) - } - - val (localTestDeps, remoteDeps) = deps.iterator.toSeq.partitionMap(d => - isLocalTestDep(d) match { - case None => Right(d) - case Some(vs) => Left(vs) - } - ) + val (localTestDeps, remoteDeps) = + deps.iterator.toSeq.partitionMap(d => isLocalTestDep(d).toLeft(d)) val resolutionRes = resolveDependenciesMetadataSafe( repositories, @@ -262,6 +258,7 @@ trait CoursierSupport { val rootDeps = deps.iterator .map(d => mapDependencies.fold(d)(_.apply(d))) + .filter(dep => isLocalTestDep(dep).isEmpty) .toSeq val forceVersions = force.iterator diff --git a/scalalib/src/mill/scalalib/CoursierModule.scala b/scalalib/src/mill/scalalib/CoursierModule.scala index 0b687f66646..c7c483cceb1 100644 --- a/scalalib/src/mill/scalalib/CoursierModule.scala +++ b/scalalib/src/mill/scalalib/CoursierModule.scala @@ -231,6 +231,37 @@ object CoursierModule { sources: Boolean ): Agg[PathRef] = resolveDeps(deps, sources, None) + + /** + * Processes dependencies and BOMs with coursier + * + * This makes coursier read and process BOM dependencies, and fill version placeholders + * in dependencies with the BOMs. + * + * Note that this doesn't throw when a version placeholder cannot be filled, and just leaves + * the placeholder behind. + * + * @param deps dependencies that might have placeholder versions ("_" as version) + * @param resolutionParams coursier resolution parameters + * @return dependencies with version placeholder filled + */ + def processDeps[T: CoursierModule.Resolvable]( + deps: IterableOnce[T], + resolutionParams: ResolutionParams = ResolutionParams() + ): Seq[Dependency] = { + val deps0 = deps + .map(implicitly[CoursierModule.Resolvable[T]].bind(_, bind)) + val res = Lib.resolveDependenciesMetadataSafe( + repositories = repositories, + deps = deps0, + mapDependencies = mapDependencies, + customizer = customizer, + coursierCacheCustomizer = coursierCacheCustomizer, + ctx = ctx, + resolutionParams = resolutionParams + ).getOrThrow + res.processedRootDependencies + } } sealed trait Resolvable[T] { diff --git a/scalalib/src/mill/scalalib/Dep.scala b/scalalib/src/mill/scalalib/Dep.scala index ac2569ada99..3b02d8daa4d 100644 --- a/scalalib/src/mill/scalalib/Dep.scala +++ b/scalalib/src/mill/scalalib/Dep.scala @@ -118,6 +118,8 @@ object Dep { } (module.split(':') match { + case Array(a, b) => Dep(a, b, "_", cross = empty(platformed = false)) + case Array(a, "", b) => Dep(a, b, "_", cross = Binary(platformed = false)) case Array(a, b, c) => Dep(a, b, c, cross = empty(platformed = false)) case Array(a, b, "", c) => Dep(a, b, c, cross = empty(platformed = true)) case Array(a, "", b, c) => Dep(a, b, c, cross = Binary(platformed = false)) diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala index 1c901525557..a2498b669e1 100644 --- a/scalalib/src/mill/scalalib/JavaModule.scala +++ b/scalalib/src/mill/scalalib/JavaModule.scala @@ -1,7 +1,7 @@ package mill package scalalib -import coursier.core.Resolution +import coursier.core.{BomDependency, Configuration, DependencyManagement, Resolution} import coursier.parse.JavaOrScalaModule import coursier.parse.ModuleParser import coursier.util.ModuleMatcher @@ -159,6 +159,139 @@ trait JavaModule */ def runIvyDeps: T[Agg[Dep]] = Task { Agg.empty[Dep] } + /** + * Any BOM dependencies you want to add to this Module, in the format + * ivy"org:name:version" + */ + def bomDeps: T[Agg[Dep]] = Task { Agg.empty[Dep] } + + def allBomDeps: Task[Agg[BomDependency]] = Task.Anon { + val modVerOrMalformed = + bomDeps().map(bindDependency()).map { bomDep => + val fromModVer = coursier.core.Dependency(bomDep.dep.module, bomDep.dep.version) + if (fromModVer == bomDep.dep) + Right(bomDep.dep.asBomDependency) + else + Left(bomDep) + } + + val malformed = modVerOrMalformed.collect { + case Left(malformedBomDep) => + malformedBomDep + } + if (malformed.isEmpty) + modVerOrMalformed.collect { + case Right(bomDep) => bomDep + } + else + throw new Exception( + "Found BOM dependencies with invalid parameters:" + System.lineSeparator() + + malformed.map("- " + _.dep + System.lineSeparator()).mkString + + "Only organization, name, and version are accepted." + ) + } + + /** + * Dependency management data + * + * Versions and exclusions in dependency management override those of transitive dependencies, + * while they have no effect if the corresponding dependency isn't pulled during dependency + * resolution. + * + * For example, the following forces com.lihaoyi::os-lib to version 0.11.3, and + * excludes org.slf4j:slf4j-api from com.lihaoyi::cask that it forces to version 0.9.4 + * {{{ + * def dependencyManagement = super.dependencyManagement() ++ Agg( + * ivy"com.lihaoyi::os-lib:0.11.3", + * ivy"com.lihaoyi::cask:0.9.4".exclude("org.slf4j", "slf4j-api") + * ) + * }}} + */ + def dependencyManagement: T[Agg[Dep]] = Task { Agg.empty[Dep] } + + private def addBoms( + dep: coursier.core.Dependency, + bomDeps: Seq[coursier.core.BomDependency], + depMgmt: Seq[(DependencyManagement.Key, DependencyManagement.Values)], + depMgmtMap: DependencyManagement.Map, + overrideVersions: Boolean = false + ): coursier.core.Dependency = { + val depMgmtKey = DependencyManagement.Key( + dep.module.organization, + dep.module.name, + coursier.core.Type.jar, + dep.publication.classifier + ) + val versionOverrideOpt = + if (dep.version == "_") depMgmtMap.get(depMgmtKey).map(_.version) + else None + val extraExclusions = depMgmtMap.get(depMgmtKey).map(_.minimizedExclusions) + dep + // add BOM coordinates - coursier will handle the rest + .addBomDependencies( + if (overrideVersions) bomDeps.map(_.withForceOverrideVersions(overrideVersions)) + else bomDeps + ) + // add dependency management ourselves: + // - overrides meant to apply to transitive dependencies + // - fill version if it's empty + // - add extra exclusions from dependency management + .withOverrides(dep.overrides ++ depMgmt) + .withVersion(versionOverrideOpt.getOrElse(dep.version)) + .withMinimizedExclusions( + extraExclusions.fold(dep.minimizedExclusions)(dep.minimizedExclusions.join(_)) + ) + } + + /** + * Data from dependencyManagement, converted to a type ready to be passed to coursier + * for dependency resolution + */ + private def processedDependencyManagement(deps: Seq[coursier.core.Dependency]) + : Seq[(DependencyManagement.Key, DependencyManagement.Values)] = { + val keyValuesOrErrors = + deps.map { depMgmt => + val fromUsedValues = coursier.core.Dependency(depMgmt.module, depMgmt.version) + .withPublication(coursier.core.Publication( + "", + depMgmt.publication.`type`, + coursier.core.Extension.empty, + depMgmt.publication.classifier + )) + .withMinimizedExclusions(depMgmt.minimizedExclusions) + .withOptional(depMgmt.optional) + if (fromUsedValues == depMgmt) { + val key = DependencyManagement.Key( + depMgmt.module.organization, + depMgmt.module.name, + if (depMgmt.publication.`type`.isEmpty) coursier.core.Type.jar + else depMgmt.publication.`type`, + depMgmt.publication.classifier + ) + val values = DependencyManagement.Values( + Configuration.empty, + if (depMgmt.version == "_") "" // shouldn't be needed with future coursier versions + else depMgmt.version, + depMgmt.minimizedExclusions, + depMgmt.optional + ) + Right(key -> values) + } else + Left(depMgmt) + } + + val errors = keyValuesOrErrors.collect { + case Left(errored) => errored + } + if (errors.isEmpty) + keyValuesOrErrors.collect { case Right(kv) => kv } + else + throw new Exception( + "Found dependency management entries with invalid values. Only organization, name, version, type, classifier, exclusions, and optionality can be specified" + System.lineSeparator() + + errors.map("- " + _ + System.lineSeparator()).mkString + ) + } + /** * Default artifact types to fetch and put in the classpath. Add extra types * here if you'd like fancy artifact extensions to be fetched. @@ -326,13 +459,45 @@ trait JavaModule */ def unmanagedClasspath: T[Agg[PathRef]] = Task { Agg.empty[PathRef] } + /** + * Returns a function adding BOM and dependency management details of + * this module to a `coursier.core.Dependency` + */ + def processDependency( + overrideVersions: Boolean = false + ): Task[coursier.core.Dependency => coursier.core.Dependency] = Task.Anon { + val bomDeps0 = allBomDeps().toSeq.map(_.withConfig(Configuration.compile)) + val depMgmt = processedDependencyManagement( + dependencyManagement().toSeq.map(bindDependency()).map(_.dep) + ) + val depMgmtMap = depMgmt.toMap + + dep => + addBoms(dep, bomDeps0, depMgmt, depMgmtMap, overrideVersions = overrideVersions) + } + + /** + * The Ivy dependencies of this module, with BOM and dependency management details + * added to them. This should be used when propagating the dependencies transitively + * to other modules. + */ + def processedIvyDeps: Task[Agg[BoundDep]] = Task.Anon { + val processDependency0 = processDependency()() + allIvyDeps().map(bindDependency()).map { dep => + dep.copy(dep = processDependency0(dep.dep)) + } + } + /** * The transitive ivy dependencies of this module and all it's upstream modules. * This is calculated from [[ivyDeps]], [[mandatoryIvyDeps]] and recursively from [[moduleDeps]]. */ def transitiveIvyDeps: T[Agg[BoundDep]] = Task { - allIvyDeps().map(bindDependency()) ++ - T.traverse(moduleDepsChecked)(_.transitiveIvyDeps)().flatten + val processDependency0 = processDependency(overrideVersions = true)() + processedIvyDeps() ++ + T.traverse(moduleDepsChecked)(_.transitiveIvyDeps)().flatten.map { dep => + dep.copy(dep = processDependency0(dep.dep)) + } } /** diff --git a/scalalib/src/mill/scalalib/PublishModule.scala b/scalalib/src/mill/scalalib/PublishModule.scala index 796c3ff10f9..75c4eb93c33 100644 --- a/scalalib/src/mill/scalalib/PublishModule.scala +++ b/scalalib/src/mill/scalalib/PublishModule.scala @@ -70,7 +70,7 @@ trait PublishModule extends JavaModule { outer => def publishXmlDeps: Task[Agg[Dependency]] = Task.Anon { val ivyPomDeps = - (ivyDeps() ++ mandatoryIvyDeps()).map(resolvePublishDependency.apply().apply(_)) + processedIvyDeps().map(_.toDep).map(resolvePublishDependency.apply().apply(_)) val compileIvyPomDeps = compileIvyDeps() .map(resolvePublishDependency.apply().apply(_)) @@ -89,6 +89,20 @@ trait PublishModule extends JavaModule { outer => compileModulePomDeps.map(Dependency(_, Scope.Provided)) } + /** + * BOM dependency to specify in the POM + */ + def publishXmlBomDeps: Task[Agg[Dependency]] = Task.Anon { + bomDeps().map(resolvePublishDependency.apply().apply(_)) + } + + /** + * Dependency management to specify in the POM + */ + def publishXmlDepMgmt: Task[Agg[Dependency]] = Task.Anon { + dependencyManagement().map(resolvePublishDependency.apply().apply(_)) + } + def pom: T[PathRef] = Task { val pom = Pom( artifactMetadata(), @@ -97,15 +111,72 @@ trait PublishModule extends JavaModule { outer => pomSettings(), publishProperties(), packagingType = pomPackagingType, - parentProject = pomParentProject() + parentProject = pomParentProject(), + bomDependencies = publishXmlBomDeps(), + dependencyManagement = publishXmlDepMgmt() ) val pomPath = T.dest / s"${artifactId()}-${publishVersion()}.pom" os.write.over(pomPath, pom) PathRef(pomPath) } + /** + * Dependencies with version placeholder filled from BOMs, alongside with BOM data + */ + def bomDetails: T[(Map[coursier.core.Module, String], coursier.core.DependencyManagement.Map)] = + Task { + val processedDeps = defaultResolver().processDeps( + transitiveCompileIvyDeps() ++ transitiveIvyDeps(), + resolutionParams = resolutionParams() + ) + val depMgmt: coursier.core.DependencyManagement.Map = + if (processedDeps.isEmpty) Map.empty + else { + val overrides = processedDeps.map(_.overrides) + overrides.tail.foldLeft(overrides.head) { (acc, map) => + acc.filter { + case (key, values) => + map.get(key).contains(values) + } + } + } + (processedDeps.map(_.moduleVersion).toMap, depMgmt) + } + def ivy: T[PathRef] = Task { - val ivy = Ivy(artifactMetadata(), publishXmlDeps(), extraPublish()) + val (rootDepVersions, bomDepMgmt) = bomDetails() + val publishXmlDeps0 = publishXmlDeps().map { dep => + if (dep.artifact.version == "_") + dep.copy( + artifact = dep.artifact.copy( + version = rootDepVersions.getOrElse( + coursier.core.Module( + coursier.core.Organization(dep.artifact.group), + coursier.core.ModuleName(dep.artifact.id), + Map.empty + ), + "" /* throw instead? */ + ) + ) + ) + else + dep + } + val overrides = + dependencyManagement().toSeq.map(bindDependency()).map(_.dep) + .filter(depMgmt => depMgmt.version.nonEmpty && depMgmt.version != "_") + .map { depMgmt => + Ivy.Override( + depMgmt.module.organization.value, + depMgmt.module.name.value, + depMgmt.version + ) + } ++ + bomDepMgmt.map { + case (key, values) => + Ivy.Override(key.organization.value, key.name.value, values.version) + } + val ivy = Ivy(artifactMetadata(), publishXmlDeps0, extraPublish(), overrides) val ivyPath = T.dest / "ivy.xml" os.write.over(ivyPath, ivy) PathRef(ivyPath) diff --git a/scalalib/src/mill/scalalib/publish/Ivy.scala b/scalalib/src/mill/scalalib/publish/Ivy.scala index ae340ac0d75..75ba33dbec1 100644 --- a/scalalib/src/mill/scalalib/publish/Ivy.scala +++ b/scalalib/src/mill/scalalib/publish/Ivy.scala @@ -8,10 +8,13 @@ object Ivy { val head = "\n" + case class Override(organization: String, name: String, version: String) + def apply( artifact: Artifact, dependencies: Agg[Dependency], - extras: Seq[PublishInfo] = Seq.empty + extras: Seq[PublishInfo] = Seq.empty, + overrides: Seq[Override] = Nil ): String = { def renderExtra(e: PublishInfo): Elem = { @@ -49,13 +52,29 @@ object Ivy { {extras.map(renderExtra)} - {dependencies.map(renderDependency).toSeq} + + {dependencies.map(renderDependency).toSeq} + {overrides.map(renderOverride)} + val pp = new PrettyPrinter(120, 4) head + pp.format(xml).replaceAll(">", ">") } + // bin-compat shim + def apply( + artifact: Artifact, + dependencies: Agg[Dependency], + extras: Seq[PublishInfo] + ): String = + apply( + artifact, + dependencies, + extras, + Nil + ) + private def renderDependency(dep: Dependency): Elem = { if (dep.exclusions.isEmpty) } + private def renderOverride(override0: Override): Elem = + + private def depIvyConf(d: Dependency): String = { if (d.optional) "optional" else d.scope match { diff --git a/scalalib/src/mill/scalalib/publish/Pom.scala b/scalalib/src/mill/scalalib/publish/Pom.scala index 76d1e728d36..6940f25c47f 100644 --- a/scalalib/src/mill/scalalib/publish/Pom.scala +++ b/scalalib/src/mill/scalalib/publish/Pom.scala @@ -39,10 +39,15 @@ object Pom { pomSettings = pomSettings, properties = properties, packagingType = pomSettings.packaging, - parentProject = None + parentProject = None, + bomDependencies = Agg.empty[Dependency], + dependencyManagement = Agg.empty[Dependency] ) - @deprecated("Use overload with parentProject parameter instead", "Mill 0.12.1") + @deprecated( + "Use overload with parentProject, bomDependencies, and dependencyManagement parameters instead", + "Mill 0.12.1" + ) def apply( artifact: Artifact, dependencies: Agg[Dependency], @@ -57,7 +62,9 @@ object Pom { pomSettings = pomSettings, properties = properties, packagingType = packagingType, - parentProject = None + parentProject = None, + bomDependencies = Agg.empty[Dependency], + dependencyManagement = Agg.empty[Dependency] ) def apply( @@ -68,6 +75,29 @@ object Pom { properties: Map[String, String], packagingType: String, parentProject: Option[Artifact] + ): String = + apply( + artifact, + dependencies, + name, + pomSettings, + properties, + packagingType, + parentProject, + Agg.empty[Dependency], + Agg.empty[Dependency] + ) + + def apply( + artifact: Artifact, + dependencies: Agg[Dependency], + name: String, + pomSettings: PomSettings, + properties: Map[String, String], + packagingType: String, + parentProject: Option[Artifact], + bomDependencies: Agg[Dependency], + dependencyManagement: Agg[Dependency] ): String = { val xml = - {dependencies.map(renderDependency).iterator} + { + dependencies.map(renderDependency(_)).iterator ++ + bomDependencies.map(renderDependency(_, isImport = true)).iterator + } + + + {dependencyManagement.map(renderDependency(_)).iterator} + + val pp = new PrettyPrinter(120, 4) @@ -143,29 +181,39 @@ object Pom { {property._2}.copy(label = property._1) } - private def renderDependency(d: Dependency): Elem = { - val scope = d.scope match { - case Scope.Compile => NodeSeq.Empty - case Scope.Provided => provided - case Scope.Test => test - case Scope.Runtime => runtime - } + private def renderDependency(d: Dependency, isImport: Boolean = false): Elem = { + val scope = + if (isImport) import + else + d.scope match { + case Scope.Compile => NodeSeq.Empty + case Scope.Provided => provided + case Scope.Test => test + case Scope.Runtime => runtime + } + + val `type` = if (isImport) pom else NodeSeq.Empty val optional = if (d.optional) true else NodeSeq.Empty + val version = + if (d.artifact.version == "_") NodeSeq.Empty + else {d.artifact.version} + if (d.exclusions.isEmpty) {d.artifact.group} {d.artifact.id} - {d.artifact.version} + {version} {scope} + {`type`} {optional} else {d.artifact.group} {d.artifact.id} - {d.artifact.version} + {version} { d.exclusions.map(ex => @@ -175,6 +223,7 @@ object Pom { } {scope} + {`type`} {optional} } diff --git a/scalalib/test/src/mill/scalalib/BomTests.scala b/scalalib/test/src/mill/scalalib/BomTests.scala new file mode 100644 index 00000000000..db3ce96b356 --- /dev/null +++ b/scalalib/test/src/mill/scalalib/BomTests.scala @@ -0,0 +1,484 @@ +package mill +package scalalib + +import mill.scalalib.publish._ +import mill.testkit.{TestBaseModule, UnitTester} +import utest._ + +import scala.jdk.CollectionConverters._ + +object BomTests extends TestSuite { + + trait TestPublishModule extends PublishModule { + def pomSettings = PomSettings( + description = artifactName(), + organization = "com.lihaoyi.mill-tests", + url = "https://github.com/com-lihaoyi/mill", + licenses = Seq(License.`Apache-2.0`), + versionControl = VersionControl.github("com-lihaoyi", "mill"), + developers = Nil + ) + def publishVersion = "0.1.0-SNAPSHOT" + } + + object modules extends TestBaseModule { + object bom extends Module { + object placeholder extends JavaModule with TestPublishModule { + def bomDeps = Agg( + ivy"com.google.cloud:libraries-bom:26.50.0" + ) + def ivyDeps = Agg( + ivy"com.google.protobuf:protobuf-java" + ) + + object dependee extends JavaModule with TestPublishModule { + def moduleDeps = Seq( + placeholder + ) + } + + object subDependee extends JavaModule with TestPublishModule { + def moduleDeps = Seq( + dependee + ) + } + + object check extends JavaModule { + def ivyDeps = Agg( + ivy"com.google.protobuf:protobuf-java" + ) + } + } + + object versionOverride extends JavaModule with TestPublishModule { + def bomDeps = Agg( + ivy"com.google.cloud:libraries-bom:26.50.0" + ) + def ivyDeps = Agg( + ivy"com.thesamet.scalapb:scalapbc_2.13:0.9.8" + ) + + object dependee extends JavaModule with TestPublishModule { + def moduleDeps = Seq( + versionOverride + ) + } + + object subDependee extends JavaModule with TestPublishModule { + def moduleDeps = Seq( + dependee + ) + } + + object check extends JavaModule { + def ivyDeps = Agg( + ivy"com.thesamet.scalapb:scalapbc_2.13:0.9.8" + ) + } + } + + object invalid extends TestBaseModule { + object exclude extends JavaModule { + def bomDeps = Agg( + ivy"com.google.cloud:libraries-bom:26.50.0".exclude(("foo", "thing")) + ) + } + } + } + + object depMgmt extends JavaModule with TestPublishModule { + def ivyDeps = Agg( + ivy"com.thesamet.scalapb:scalapbc_2.13:0.9.8" + ) + def dependencyManagement = Agg( + ivy"com.google.protobuf:protobuf-java:4.28.3" + ) + + object transitive extends JavaModule with TestPublishModule { + def moduleDeps = Seq(depMgmt) + } + + object extraExclude extends JavaModule with TestPublishModule { + def ivyDeps = Agg( + ivy"com.lihaoyi:cask_2.13:0.9.4" + ) + def dependencyManagement = Agg( + // The exclude should be automatically added to the dependency above + // thanks to dependency management, but the version should be left + // untouched + ivy"com.lihaoyi:cask_2.13:0.9.3" + .exclude(("org.slf4j", "slf4j-api")) + ) + + object transitive extends JavaModule with TestPublishModule { + def moduleDeps = Seq(extraExclude) + } + } + + object exclude extends JavaModule with TestPublishModule { + def ivyDeps = Agg( + ivy"com.lihaoyi:cask_2.13:0.9.4" + ) + def dependencyManagement = Agg( + ivy"org.java-websocket:Java-WebSocket:1.5.2" + .exclude(("org.slf4j", "slf4j-api")) + ) + + object transitive extends JavaModule with TestPublishModule { + def moduleDeps = Seq(exclude) + } + } + + object onlyExclude extends JavaModule with TestPublishModule { + def ivyDeps = Agg( + ivy"com.lihaoyi:cask_2.13:0.9.4" + ) + def dependencyManagement = Agg( + ivy"org.java-websocket:Java-WebSocket" + .exclude(("org.slf4j", "slf4j-api")) + ) + + object transitive extends JavaModule with TestPublishModule { + def moduleDeps = Seq(onlyExclude) + } + } + + object invalid extends TestBaseModule { + object transitive extends JavaModule { + def dependencyManagement = { + val dep = ivy"org.java-websocket:Java-WebSocket:1.5.3" + Agg( + dep.copy( + dep = dep.dep.withTransitive(false) + ) + ) + } + } + } + + object placeholder extends JavaModule with TestPublishModule { + def ivyDeps = Agg( + ivy"com.google.protobuf:protobuf-java" + ) + def dependencyManagement = Agg( + ivy"com.google.protobuf:protobuf-java:4.28.3" + ) + + object transitive extends JavaModule with TestPublishModule { + def moduleDeps = Seq(placeholder) + } + } + } + + object bomOnModuleDependency extends JavaModule with TestPublishModule { + def ivyDeps = Agg( + ivy"com.google.protobuf:protobuf-java:3.23.4" + ) + + object dependee extends JavaModule with TestPublishModule { + def bomDeps = Agg( + ivy"com.google.cloud:libraries-bom:26.50.0" + ) + def moduleDeps = Seq(bomOnModuleDependency) + } + } + } + + def expectedProtobufJavaVersion = "4.28.3" + def expectedCommonsCompressVersion = "1.23.0" + + def expectedProtobufJarName = s"protobuf-java-$expectedProtobufJavaVersion.jar" + def expectedCommonsCompressJarName = s"commons-compress-$expectedCommonsCompressVersion.jar" + + def compileClasspathFileNames(module: JavaModule)(implicit + eval: UnitTester + ): Seq[String] = + eval(module.compileClasspath).toTry.get.value + .toSeq.map(_.path.last) + + def compileClasspathContains( + module: JavaModule, + fileName: String, + jarCheck: Option[String => Boolean] + )(implicit + eval: UnitTester + ) = { + val fileNames = compileClasspathFileNames(module) + assert(fileNames.contains(fileName)) + for (check <- jarCheck; fileName <- fileNames) + assert(check(fileName)) + } + + def publishLocalAndResolve( + module: PublishModule, + dependencyModules: Seq[PublishModule], + scalaSuffix: String + )(implicit eval: UnitTester): Seq[os.Path] = { + val localIvyRepo = eval.evaluator.workspace / "ivy2Local" + eval(module.publishLocal(localIvyRepo.toString)).toTry.get + for (dependencyModule <- dependencyModules) + eval(dependencyModule.publishLocal(localIvyRepo.toString)).toTry.get + + val moduleString = eval(module.artifactName).toTry.get.value + + coursierapi.Fetch.create() + .addDependencies( + coursierapi.Dependency.of( + "com.lihaoyi.mill-tests", + moduleString.replace('.', '-') + scalaSuffix, + "0.1.0-SNAPSHOT" + ) + ) + .addRepositories( + coursierapi.IvyRepository.of(localIvyRepo.toNIO.toUri.toASCIIString + "[defaultPattern]") + ) + .fetch() + .asScala + .map(os.Path(_)) + .toVector + } + + def publishM2LocalAndResolve( + module: PublishModule, + dependencyModules: Seq[PublishModule], + scalaSuffix: String + )(implicit eval: UnitTester): Seq[os.Path] = { + val localM2Repo = eval.evaluator.workspace / "m2Local" + eval(module.publishM2Local(localM2Repo.toString)).toTry.get + for (dependencyModule <- dependencyModules) + eval(dependencyModule.publishM2Local(localM2Repo.toString)).toTry.get + + val moduleString = eval(module.artifactName).toTry.get.value + + coursierapi.Fetch.create() + .addDependencies( + coursierapi.Dependency.of( + "com.lihaoyi.mill-tests", + moduleString.replace('.', '-') + scalaSuffix, + "0.1.0-SNAPSHOT" + ) + ) + .addRepositories( + coursierapi.MavenRepository.of(localM2Repo.toNIO.toUri.toASCIIString) + ) + .fetch() + .asScala + .map(os.Path(_)) + .toVector + } + + def isInClassPath( + module: JavaModule with PublishModule, + jarName: String, + dependencyModules: Seq[PublishModule] = Nil, + jarCheck: Option[String => Boolean] = None, + ivy2LocalCheck: Boolean = true, + scalaSuffix: String = "" + )(implicit eval: UnitTester): Unit = { + compileClasspathContains(module, jarName, jarCheck) + + if (ivy2LocalCheck) { + val resolvedCp = publishLocalAndResolve(module, dependencyModules, scalaSuffix) + assert(resolvedCp.map(_.last).contains(jarName)) + for (check <- jarCheck; fileName <- resolvedCp.map(_.last)) + assert(check(fileName)) + } + + val resolvedM2Cp = publishM2LocalAndResolve(module, dependencyModules, scalaSuffix) + assert(resolvedM2Cp.map(_.last).contains(jarName)) + for (check <- jarCheck; fileName <- resolvedM2Cp.map(_.last)) + assert(check(fileName)) + } + + def tests = Tests { + + test("bom") { + test("placeholder") { + test("check") - UnitTester(modules, null).scoped { eval => + val res = eval(modules.bom.placeholder.check.compileClasspath) + assert( + res.left.exists(_.toString.contains( + "not found: https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/_/protobuf-java-_.pom" + )) + ) + } + + test("simple") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath(modules.bom.placeholder, expectedProtobufJarName) + } + + test("dependee") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.bom.placeholder.dependee, + expectedProtobufJarName, + Seq(modules.bom.placeholder) + ) + } + + test("subDependee") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.bom.placeholder.subDependee, + expectedProtobufJarName, + Seq(modules.bom.placeholder, modules.bom.placeholder.dependee) + ) + } + } + + test("versionOverride") { + test("check") - UnitTester(modules, null).scoped { implicit eval => + val fileNames = compileClasspathFileNames(modules.bom.versionOverride.check) + assert(fileNames.exists(v => v.startsWith("protobuf-java-") && v.endsWith(".jar"))) + assert(!fileNames.contains(expectedProtobufJarName)) + } + + test("simple") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath(modules.bom.versionOverride, expectedProtobufJarName) + } + + test("dependee") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.bom.versionOverride.dependee, + expectedProtobufJarName, + Seq(modules.bom.versionOverride) + ) + } + + test("subDependee") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.bom.versionOverride.subDependee, + expectedProtobufJarName, + Seq(modules.bom.versionOverride, modules.bom.versionOverride.dependee) + ) + } + } + + test("invalid") { + test - UnitTester(modules, null).scoped { eval => + val res = eval(modules.bom.invalid.exclude.compileClasspath) + assert( + res.left.exists(_.toString.contains( + "Found BOM dependencies with invalid parameters:" + )) + ) + } + } + } + + test("depMgmt") { + test("override") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath(modules.depMgmt, expectedProtobufJarName) + } + + test("transitiveOverride") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath(modules.depMgmt.transitive, expectedProtobufJarName, Seq(modules.depMgmt)) + } + + test("extraExclude") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.depMgmt.extraExclude, + "cask_2.13-0.9.4.jar", + jarCheck = Some { jarName => + !jarName.startsWith("slf4j-api-") + } + ) + } + + test("transitiveExtraExclude") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.depMgmt.extraExclude.transitive, + "cask_2.13-0.9.4.jar", + Seq(modules.depMgmt.extraExclude), + jarCheck = Some { jarName => + !jarName.startsWith("slf4j-api-") + }, + ivy2LocalCheck = false // we could make that work + ) + } + + test("exclude") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.depMgmt.exclude, + "Java-WebSocket-1.5.2.jar", + jarCheck = Some { jarName => + !jarName.startsWith("slf4j-api-") + }, + ivy2LocalCheck = false // dep mgmt excludes can't be put in ivy.xml + ) + } + + test("transitiveExclude") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.depMgmt.exclude.transitive, + "Java-WebSocket-1.5.2.jar", + Seq(modules.depMgmt.exclude), + jarCheck = Some { jarName => + !jarName.startsWith("slf4j-api-") + }, + ivy2LocalCheck = false // dep mgmt excludes can't be put in ivy.xml + ) + } + + test("onlyExclude") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.depMgmt.onlyExclude, + "Java-WebSocket-1.5.3.jar", + jarCheck = Some { jarName => + !jarName.startsWith("slf4j-api-") + }, + ivy2LocalCheck = false // dep mgmt excludes can't be put in ivy.xml + ) + } + + test("transitiveOnlyExclude") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.depMgmt.onlyExclude.transitive, + "Java-WebSocket-1.5.3.jar", + Seq(modules.depMgmt.onlyExclude), + jarCheck = Some { jarName => + !jarName.startsWith("slf4j-api-") + }, + ivy2LocalCheck = false // dep mgmt excludes can't be put in ivy.xml + ) + } + + test("invalid") { + test - UnitTester(modules, null).scoped { eval => + val res = eval(modules.depMgmt.invalid.transitive.compileClasspath) + assert( + res.left.exists(_.toString.contains( + "Found dependency management entries with invalid values." + )) + ) + } + } + + test("placeholder") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath(modules.depMgmt.placeholder, expectedProtobufJarName) + } + + test("transitivePlaceholder") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.depMgmt.placeholder.transitive, + expectedProtobufJarName, + Seq(modules.depMgmt.placeholder) + ) + } + } + + test("bomOnModuleDependency") { + test("check") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.bomOnModuleDependency, + "protobuf-java-3.23.4.jar" + ) + } + test("dependee") - UnitTester(modules, null).scoped { implicit eval => + isInClassPath( + modules.bomOnModuleDependency.dependee, + expectedProtobufJarName, + Seq(modules.bomOnModuleDependency) + ) + } + } + } +} diff --git a/scalalib/test/src/mill/scalalib/publish/PomTests.scala b/scalalib/test/src/mill/scalalib/publish/PomTests.scala index d1b52b602aa..d8fb8d44bbd 100644 --- a/scalalib/test/src/mill/scalalib/publish/PomTests.scala +++ b/scalalib/test/src/mill/scalalib/publish/PomTests.scala @@ -222,7 +222,10 @@ object PomTests extends TestSuite { artifactId, pomSettings, properties, - PackagingType.Jar + PackagingType.Jar, + None, + Agg.empty[Dependency], + Agg.empty[Dependency] )) def singleText(seq: NodeSeq) = From 9891b4e30fe783cbb33d63a7d22cc7f6edae5f56 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Wed, 27 Nov 2024 23:42:29 +0100 Subject: [PATCH 2/3] Use bomDeps when importing Maven projects --- main/maven/src/mill/main/maven/BuildGen.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main/maven/src/mill/main/maven/BuildGen.scala b/main/maven/src/mill/main/maven/BuildGen.scala index 06b62e719f5..817344986b6 100644 --- a/main/maven/src/mill/main/maven/BuildGen.scala +++ b/main/maven/src/mill/main/maven/BuildGen.scala @@ -148,14 +148,18 @@ object BuildGen { } optional(s"override def pomPackagingType = ", packagingType) } - val pomParentProjectSetting = { + val pomParentProjectSettings = { val parent = model.getParent if (null == parent) "" else { val group = parent.getGroupId val id = parent.getArtifactId val version = parent.getVersion - s"override def pomParentProject = Some(Artifact(\"$group\", \"$id\", \"$version\"))" + s"""override def pomParentProject = Some(Artifact("$group", "$id", "$version")) + | + |override def bomDeps = super.bomDeps() ++ Agg(ivy"$group:$id:$version") + |""".stripMargin + s"" } } val metadataSettings = if (cfg.baseModule.isEmpty) metadata(model, cfg) else "" @@ -191,7 +195,7 @@ object BuildGen { | |$pomPackagingTypeSetting | - |$pomParentProjectSetting + |$pomParentProjectSettings | |$metadataSettings | From 00aef36944841e50fd8424ca224be92cb80220a5 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Fri, 6 Dec 2024 00:27:05 +0100 Subject: [PATCH 3/3] fixes --- main/maven/src/mill/main/maven/BuildGen.scala | 1 - main/maven/test/resources/expected/config/all/build.mill | 6 ++++++ .../expected/config/base-module/server/package.mill | 3 +++ .../expected/config/base-module/webapp/package.mill | 3 +++ .../expected/config/deps-object/server/package.mill | 3 +++ .../expected/config/deps-object/webapp/package.mill | 3 +++ main/maven/test/resources/expected/config/merge/build.mill | 6 ++++++ .../expected/maven-samples/multi-module/server/package.mill | 3 +++ .../expected/maven-samples/multi-module/webapp/package.mill | 3 +++ 9 files changed, 30 insertions(+), 1 deletion(-) diff --git a/main/maven/src/mill/main/maven/BuildGen.scala b/main/maven/src/mill/main/maven/BuildGen.scala index 817344986b6..9c936ab6765 100644 --- a/main/maven/src/mill/main/maven/BuildGen.scala +++ b/main/maven/src/mill/main/maven/BuildGen.scala @@ -159,7 +159,6 @@ object BuildGen { | |override def bomDeps = super.bomDeps() ++ Agg(ivy"$group:$id:$version") |""".stripMargin - s"" } } val metadataSettings = if (cfg.baseModule.isEmpty) metadata(model, cfg) else "" diff --git a/main/maven/test/resources/expected/config/all/build.mill b/main/maven/test/resources/expected/config/all/build.mill index cde6beae10b..79357cb995c 100644 --- a/main/maven/test/resources/expected/config/all/build.mill +++ b/main/maven/test/resources/expected/config/all/build.mill @@ -38,6 +38,9 @@ object `package` extends RootModule with MyModule { "1.0-SNAPSHOT" )) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + object tests extends MavenTests with TestModule.Junit4 { override def ivyDeps = super.ivyDeps() ++ Agg( @@ -66,6 +69,9 @@ object `package` extends RootModule with MyModule { "1.0-SNAPSHOT" )) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + } } diff --git a/main/maven/test/resources/expected/config/base-module/server/package.mill b/main/maven/test/resources/expected/config/base-module/server/package.mill index 6ec97af00e0..93bad6a63cd 100644 --- a/main/maven/test/resources/expected/config/base-module/server/package.mill +++ b/main/maven/test/resources/expected/config/base-module/server/package.mill @@ -13,6 +13,9 @@ object `package` extends RootModule with MyModule { Artifact("com.example.maven-samples", "multi-module-parent", "1.0-SNAPSHOT") ) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + object test extends MavenTests with TestModule.Junit4 { override def ivyDeps = super.ivyDeps() ++ Agg( diff --git a/main/maven/test/resources/expected/config/base-module/webapp/package.mill b/main/maven/test/resources/expected/config/base-module/webapp/package.mill index c1d990c0ed4..334262d23bd 100644 --- a/main/maven/test/resources/expected/config/base-module/webapp/package.mill +++ b/main/maven/test/resources/expected/config/base-module/webapp/package.mill @@ -20,4 +20,7 @@ object `package` extends RootModule with MyModule { Artifact("com.example.maven-samples", "multi-module-parent", "1.0-SNAPSHOT") ) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + } diff --git a/main/maven/test/resources/expected/config/deps-object/server/package.mill b/main/maven/test/resources/expected/config/deps-object/server/package.mill index 3a6e003c149..cb7b469b366 100644 --- a/main/maven/test/resources/expected/config/deps-object/server/package.mill +++ b/main/maven/test/resources/expected/config/deps-object/server/package.mill @@ -20,6 +20,9 @@ object `package` extends RootModule with PublishModule with MavenModule { Artifact("com.example.maven-samples", "multi-module-parent", "1.0-SNAPSHOT") ) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + override def pomSettings = PomSettings( "Logic.", "com.example.maven-samples", diff --git a/main/maven/test/resources/expected/config/deps-object/webapp/package.mill b/main/maven/test/resources/expected/config/deps-object/webapp/package.mill index 9f30469d148..011a2b102ff 100644 --- a/main/maven/test/resources/expected/config/deps-object/webapp/package.mill +++ b/main/maven/test/resources/expected/config/deps-object/webapp/package.mill @@ -29,6 +29,9 @@ object `package` extends RootModule with PublishModule with MavenModule { Artifact("com.example.maven-samples", "multi-module-parent", "1.0-SNAPSHOT") ) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + override def pomSettings = PomSettings( "Webapp.", "com.example.maven-samples", diff --git a/main/maven/test/resources/expected/config/merge/build.mill b/main/maven/test/resources/expected/config/merge/build.mill index 287a175a92d..387f8b74af3 100644 --- a/main/maven/test/resources/expected/config/merge/build.mill +++ b/main/maven/test/resources/expected/config/merge/build.mill @@ -55,6 +55,9 @@ object `package` extends RootModule with PublishModule with MavenModule { "1.0-SNAPSHOT" )) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + override def pomSettings = PomSettings( "Logic.", "com.example.maven-samples", @@ -101,6 +104,9 @@ object `package` extends RootModule with PublishModule with MavenModule { "1.0-SNAPSHOT" )) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + override def pomSettings = PomSettings( "Webapp.", "com.example.maven-samples", diff --git a/main/maven/test/resources/expected/maven-samples/multi-module/server/package.mill b/main/maven/test/resources/expected/maven-samples/multi-module/server/package.mill index c78c2759b8f..f0b20dd23dc 100644 --- a/main/maven/test/resources/expected/maven-samples/multi-module/server/package.mill +++ b/main/maven/test/resources/expected/maven-samples/multi-module/server/package.mill @@ -12,6 +12,9 @@ object `package` extends RootModule with PublishModule with MavenModule { Artifact("com.example.maven-samples", "multi-module-parent", "1.0-SNAPSHOT") ) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + override def pomSettings = PomSettings( "Logic.", "com.example.maven-samples", diff --git a/main/maven/test/resources/expected/maven-samples/multi-module/webapp/package.mill b/main/maven/test/resources/expected/maven-samples/multi-module/webapp/package.mill index 465e676a906..1510c781827 100644 --- a/main/maven/test/resources/expected/maven-samples/multi-module/webapp/package.mill +++ b/main/maven/test/resources/expected/maven-samples/multi-module/webapp/package.mill @@ -19,6 +19,9 @@ object `package` extends RootModule with PublishModule with MavenModule { Artifact("com.example.maven-samples", "multi-module-parent", "1.0-SNAPSHOT") ) + override def bomDeps = super.bomDeps() ++ + Agg(ivy"com.example.maven-samples:multi-module-parent:1.0-SNAPSHOT") + override def pomSettings = PomSettings( "Webapp.", "com.example.maven-samples",