diff --git a/contrib/package.mill b/contrib/package.mill index 3d75cbc5d80..09e1e0f2342 100644 --- a/contrib/package.mill +++ b/contrib/package.mill @@ -229,4 +229,9 @@ object `package` extends mill.Module { def compileModuleDeps = Seq(build.libs.scalalib) def testModuleDeps = super.testModuleDeps ++ Seq(build.libs.scalalib) } + + object sbom extends ContribModule { + def compileModuleDeps = Seq(build.libs.scalalib) + def testModuleDeps: Seq[JavaModule] = super.testModuleDeps ++ Seq(build.libs.scalalib) + } } diff --git a/contrib/sbom/readme.adoc b/contrib/sbom/readme.adoc new file mode 100644 index 00000000000..3146ab04111 --- /dev/null +++ b/contrib/sbom/readme.adoc @@ -0,0 +1,70 @@ += SBOM file +:page-aliases: Plugin_SBOM.adoc + +This plugin creates Software Bill of Materials (SBOM) + +This module has some limitations at the moment: + +- Minimal SBOM, various properties of libraries are missing. e.g. the license. +- Only JVM ecosystem libraries are reported. +- Only the CycloneDX JSON format is supported + +To declare a module that generates an SBOM extend the `mill.contrib.sbom.CycloneDXModuleTests` trait when defining your module. + +Quickstart: + +.`build.mill` +[source,scala] +---- +package build +import mill.* +import mill.javalib.* +import $ivy.`com.lihaoyi::mill-contrib-sbom:` +import mill.contrib.sbom.CycloneDXJavaModule + +object `sbom-demo` extends JavaModule with CycloneDXJavaModule { + // An example dependency + override def ivyDeps = Seq(ivy"ch.qos.logback:logback-classic:1.5.12") +} +---- + +This provides the `sbomJsonFile` task that produces a CycloneDX JSON file: + +[source,bash] +---- +$ mill show sbom-demo.sbomJsonFile # Creates the SBOM file in the JSON format +---- + +== Uploading to Dependency Track +Uploading the SBOM to https://dependencytrack.org/[Dependency Track] is supported. +Add the `DependencyTrackModule` and provide the necessary details: + +.`build.mill` +[source,scala] +---- +package build +import mill.* +import mill.javalib.* +import $ivy.`com.lihaoyi::mill-contrib-sbom:` +import mill.contrib.sbom.CycloneDXModule +import mill.contrib.sbom.upload.DependencyTrack + +object `sbom-demo` extends JavaModule with CycloneDXJavaModule with DependencyTrackModule { + def depTrackUrl = "http://localhost:8081" + def depTrackProjectID = "7c1a9efd-8f05-4cdb-bb16-602cb5c1d6e0" + def depTrackApiKey = "odt_rTKFk9MCDtWpdun1VKUUfsOsdOumo96q" + // An example dependency + override def ivyDeps = Seq(ivy"ch.qos.logback:logback-classic:1.5.12") +} +---- + +Affter that you upload the SBOM: + +[source,bash] +---- +./mill sbom-demo.sbomUpload +---- + + + + diff --git a/contrib/sbom/src/mill/contrib/sbom/CycloneDX.scala b/contrib/sbom/src/mill/contrib/sbom/CycloneDX.scala new file mode 100644 index 00000000000..59cf759038e --- /dev/null +++ b/contrib/sbom/src/mill/contrib/sbom/CycloneDX.scala @@ -0,0 +1,76 @@ +package mill.contrib.sbom + +import coursier.Dependency +import os.Path +import upickle.default.macroRW +import upickle.default.ReadWriter + +import java.math.BigInteger +import java.security.MessageDigest +import java.time.Instant +import java.util.UUID + +object CycloneDX { + case class SbomJson( + bomFormat: String, + specVersion: String, + serialNumber: String, + version: Int, + metadata: MetaData, + components: Seq[Component] + ) + + case class MetaData(timestamp: String = Instant.now().toString) + + case class ComponentHash(alg: String, content: String) + + case class LicenseHolder(license: License) + + case class License(name: String, url: Option[String]) + + case class Component( + `type`: String, + `bom-ref`: String, + group: String, + name: String, + version: String, + description: String, + licenses: Seq[LicenseHolder], + hashes: Seq[ComponentHash] + ) + + object Component { + def fromDeps(path: Path, dep: Dependency, licenses: Seq[coursier.Info.License]): Component = { + val compLicenses = licenses.map { lic => + LicenseHolder(License(lic.name, lic.url)) + } + Component( + "library", + s"pkg:maven/${dep.module.organization.value}/${dep.module.name.value}@${dep.version}?type=jar", + dep.module.organization.value, + dep.module.name.value, + dep.version, + dep.module.orgName, + compLicenses, + Seq(ComponentHash("SHA-256", sha256(path))) + ) + } + } + + implicit val sbomRW: ReadWriter[SbomJson] = macroRW + implicit val metaRW: ReadWriter[MetaData] = macroRW + implicit val componentHashRW: ReadWriter[ComponentHash] = macroRW + implicit val componentRW: ReadWriter[Component] = macroRW + implicit val licenceHolderRW: ReadWriter[LicenseHolder] = macroRW + implicit val licenceRW: ReadWriter[License] = macroRW + + private def sha256(f: Path): String = { + val md = MessageDigest.getInstance("SHA-256") + val fileContent = os.read.bytes(f) + val digest = md.digest(fileContent) + String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)) + } + + case class SbomHeader(serialNumber: UUID, timestamp: Instant) + +} diff --git a/contrib/sbom/src/mill/contrib/sbom/CycloneDXJavaModule.scala b/contrib/sbom/src/mill/contrib/sbom/CycloneDXJavaModule.scala new file mode 100644 index 00000000000..51c73d8576a --- /dev/null +++ b/contrib/sbom/src/mill/contrib/sbom/CycloneDXJavaModule.scala @@ -0,0 +1,72 @@ +package mill.contrib.sbom + +import coursier.{Fetch, Resolution, VersionConstraint, core as cs} +import mill.Task +import mill.javalib.{BoundDep, JavaModule} + +/** + * Report the Java/Scala/Kotlin dependencies in a SBOM. + * By default, it reports all dependencies in the [[ivyDeps]] and [[runIvyDeps]]. + * Other scopes and unmanaged dependencies are not added to the report. + * + * Change this behavior by overriding [[sbomComponents]] + */ +trait CycloneDXJavaModule extends JavaModule with CycloneDXModule { + import CycloneDX.* + + /** + * Lists of all components used for this module. + * By default, uses the [[ivyDeps]] and [[runIvyDeps]] for the list of components + */ + def sbomComponents: Task[Seq[Component]] = Task { + val resolved = resolvedRunIvyDepsDetails()() + resolvedSbomComponents(resolved) + } + + protected def resolvedSbomComponents(resolved: Fetch.Result): Seq[Component] = { + val distinctDeps = resolved.fullDetailedArtifacts0 + .flatMap { + case (dep, _, _, Some(path)) => Some(dep -> path) + case _ => None + } + // Artifacts.Result.files does eliminate duplicates path: Do the same + .distinctBy(_._2) + .map { case (dep, path) => + val license = findLicenses(resolved.resolution, dep.module, dep.versionConstraint) + Component.fromDeps(os.Path(path), dep, license) + } + distinctDeps + } + + /** Copied from [[resolvedRunIvyDeps]], but getting the raw artifacts */ + private def resolvedRunIvyDepsDetails(): Task[Fetch.Result] = Task.Anon { + millResolver().fetch(Seq( + BoundDep( + coursierDependency.withConfiguration(cs.Configuration.runtime), + force = false + ) + )) + } + + private def findLicenses( + resolution: Resolution, + module: coursier.core.Module, + version: VersionConstraint + ): Seq[coursier.Info.License] = { + val projects = resolution.projectCache0 + val project = projects.get(module -> version) + project match + case None => Seq.empty + case Some((_, proj)) => + val licences = proj.info.licenseInfo + if (licences.nonEmpty) { + licences + } else { + proj.parent0.map((pm, v) => + findLicenses(resolution, pm, VersionConstraint.fromVersion(v)) + ) + .getOrElse(Seq.empty) + } + } + +} diff --git a/contrib/sbom/src/mill/contrib/sbom/CycloneDXModule.scala b/contrib/sbom/src/mill/contrib/sbom/CycloneDXModule.scala new file mode 100644 index 00000000000..71d37a995cf --- /dev/null +++ b/contrib/sbom/src/mill/contrib/sbom/CycloneDXModule.scala @@ -0,0 +1,50 @@ +package mill.contrib.sbom + +import mill.* +import mill.javalib.{BoundDep, JavaModule} +import coursier.{Artifacts, Dependency, Resolution, VersionConstraint, core as cs} +import os.Path +import upickle.default.{ReadWriter, macroRW} + +import java.math.BigInteger +import java.security.MessageDigest +import java.time.Instant +import java.util.UUID + +trait CycloneDXModule extends Module { + import CycloneDX.* + + /** Lists of all components used for this module. */ + def sbomComponents: Task[Seq[Component]] + + /** + * Each time the SBOM is generated, a new UUID and timestamp are generated + * Can be overridden to use a more predictable method, eg. for reproducible builds + */ + def sbomHeader(): SbomHeader = SbomHeader(UUID.randomUUID(), Instant.now()) + + /** + * Generates the SBOM Json for this module, based on the components returned by [[sbomComponents]] + * @return + */ + def sbom: T[SbomJson] = Target { + val header = sbomHeader() + val components = sbomComponents() + + SbomJson( + bomFormat = "CycloneDX", + specVersion = "1.2", + serialNumber = s"urn:uuid:${header.serialNumber}", + version = 1, + metadata = MetaData(timestamp = header.timestamp.toString), + components = components + ) + } + + def sbomJsonFile: T[PathRef] = Target { + val sbomFile = Target.dest / "sbom.json" + os.write(sbomFile, upickle.default.write(sbom(), indent = 2)) + PathRef(sbomFile) + } + +} diff --git a/contrib/sbom/src/mill/contrib/sbom/upload/DependencyTrackModule.scala b/contrib/sbom/src/mill/contrib/sbom/upload/DependencyTrackModule.scala new file mode 100644 index 00000000000..ae20ddd016e --- /dev/null +++ b/contrib/sbom/src/mill/contrib/sbom/upload/DependencyTrackModule.scala @@ -0,0 +1,51 @@ +package mill.contrib.sbom.upload + +import java.util.Base64 +import java.nio.charset.StandardCharsets +import mill._ +import mill.contrib.sbom.CycloneDXModule +import upickle.default.{ReadWriter, macroRW} + +object DependencyTrackModule { + case class Payload(project: String, bom: String) + + implicit val depTrackPayload: ReadWriter[Payload] = macroRW +} +trait DependencyTrackModule extends CycloneDXModule { + import DependencyTrackModule._ + + def depTrackUrl: T[String] + def depTrackProjectID: T[String] + def depTrackApiKey: T[String] + + /** + * Uploads the generated SBOM to the configured dependency track instance + */ + def sbomUpload(): Command[Unit] = Task.Command { + val url = depTrackUrl() + val projectId = depTrackProjectID() + val apiKey = depTrackApiKey() + + val bomString = upickle.default.write(sbom()) + val payload = Payload( + projectId, + Base64.getEncoder.encodeToString( + bomString.getBytes(StandardCharsets.UTF_8) + ) + ) + val body = upickle.default.stream[Payload](payload) + val bodyBytes = requests.RequestBlob.ByteSourceRequestBlob(body)(identity) + val r = requests.put( + s"$url/api/v1/bom", + headers = Map( + "Content-Type" -> "application/json", + "X-API-Key" -> apiKey + ), + data = bodyBytes + ) + assert(r.is2xx) + } + + def myCmdC(test: String) = Task.Command { println("hi above"); 34 } + +} diff --git a/contrib/sbom/test/reference/pom.xml b/contrib/sbom/test/reference/pom.xml new file mode 100644 index 00000000000..94f1f63248b --- /dev/null +++ b/contrib/sbom/test/reference/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + + com.example + sbom-example-reference + jar + 1.1-SNAPSHOT + + CycloneDX reference + + This is a reference on how the CycloneDX Maven plugin generates an SBOM. + This way we can inspect differences between Mill and the wildly used Maven plugin. + Run: mvn package, then inspect the target/bom.json + + + + 11 + ${java.version} + ${java.version} + + + + + + ch.qos.logback + logback-classic + 1.5.12 + + + commons-io + commons-io + 2.18.0 + + + + + + + org.cyclonedx + cyclonedx-maven-plugin + + + package + + makeAggregateBom + + + + + + + diff --git a/contrib/sbom/test/resources/withDeps.sbom.json b/contrib/sbom/test/resources/withDeps.sbom.json new file mode 100644 index 00000000000..f4a92f55e33 --- /dev/null +++ b/contrib/sbom/test/resources/withDeps.sbom.json @@ -0,0 +1,89 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.2", + "serialNumber": "urn:uuid:a9d6a1c7-18d4-4901-891c-cbcc8f2c5241", + "version": 1, + "metadata": { + "timestamp": "2025-03-17T17:00:56.263933698Z" + }, + "components": [ + { + "type": "library", + "bom-ref": "pkg:maven/ch.qos.logback/logback-classic@1.5.12?type=jar", + "group": "ch.qos.logback", + "name": "logback-classic", + "version": "1.5.12", + "description": "ch.qos.logback:logback-classic", + "licenses": [ + { + "license": { + "name": "Eclipse Public License - v 1.0", + "url": "http://www.eclipse.org/legal/epl-v10.html" + } + }, + { + "license": { + "name": "GNU Lesser General Public License", + "url": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "ebe1a2ce1072b365090d58af40fcb7482d7864a31cd2b1c62c9b1d13f9a80c09" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:maven/ch.qos.logback/logback-core@1.5.12?type=jar", + "group": "ch.qos.logback", + "name": "logback-core", + "version": "1.5.12", + "description": "ch.qos.logback:logback-core", + "licenses": [ + { + "license": { + "name": "Eclipse Public License - v 1.0", + "url": "http://www.eclipse.org/legal/epl-v10.html" + } + }, + { + "license": { + "name": "GNU Lesser General Public License", + "url": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "3f35b41621c2cbf72a9d9f3ce2270ba2040e4808bd6befdd720866e926d3e84a" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:maven/org.slf4j/slf4j-api@2.0.15?type=jar", + "group": "org.slf4j", + "name": "slf4j-api", + "version": "2.0.15", + "description": "org.slf4j:slf4j-api", + "licenses": [ + { + "license": { + "name": "MIT License", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "5bfda32d723dde8ccef9db6bdc2537dabdb87321597c7e00e66a73a5777fbb24" + } + ] + } + ] +} \ No newline at end of file diff --git a/contrib/sbom/test/resources/withModuleDeps.sbom.json b/contrib/sbom/test/resources/withModuleDeps.sbom.json new file mode 100644 index 00000000000..654bf4046d8 --- /dev/null +++ b/contrib/sbom/test/resources/withModuleDeps.sbom.json @@ -0,0 +1,111 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.2", + "serialNumber": "urn:uuid:a9d6a1c7-18d4-4901-891c-cbcc8f2c5241", + "version": 1, + "metadata": { + "timestamp": "2025-03-17T17:00:56.263933698Z" + }, + "components": [ + { + "type": "library", + "bom-ref": "pkg:maven/commons-io/commons-io@2.18.0?type=jar", + "group": "commons-io", + "name": "commons-io", + "version": "2.18.0", + "description": "commons-io:commons-io", + "licenses": [ + { + "license": { + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:maven/ch.qos.logback/logback-classic@1.5.12?type=jar", + "group": "ch.qos.logback", + "name": "logback-classic", + "version": "1.5.12", + "description": "ch.qos.logback:logback-classic", + "licenses": [ + { + "license": { + "name": "Eclipse Public License - v 1.0", + "url": "http://www.eclipse.org/legal/epl-v10.html" + } + }, + { + "license": { + "name": "GNU Lesser General Public License", + "url": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "ebe1a2ce1072b365090d58af40fcb7482d7864a31cd2b1c62c9b1d13f9a80c09" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:maven/ch.qos.logback/logback-core@1.5.12?type=jar", + "group": "ch.qos.logback", + "name": "logback-core", + "version": "1.5.12", + "description": "ch.qos.logback:logback-core", + "licenses": [ + { + "license": { + "name": "Eclipse Public License - v 1.0", + "url": "http://www.eclipse.org/legal/epl-v10.html" + } + }, + { + "license": { + "name": "GNU Lesser General Public License", + "url": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "3f35b41621c2cbf72a9d9f3ce2270ba2040e4808bd6befdd720866e926d3e84a" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:maven/org.slf4j/slf4j-api@2.0.15?type=jar", + "group": "org.slf4j", + "name": "slf4j-api", + "version": "2.0.15", + "description": "org.slf4j:slf4j-api", + "licenses": [ + { + "license": { + "name": "MIT License", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "5bfda32d723dde8ccef9db6bdc2537dabdb87321597c7e00e66a73a5777fbb24" + } + ] + } + ] +} \ No newline at end of file diff --git a/contrib/sbom/test/src/mill/contrib/sbom/CycloneDXModuleTests.scala b/contrib/sbom/test/src/mill/contrib/sbom/CycloneDXModuleTests.scala new file mode 100644 index 00000000000..077e7802187 --- /dev/null +++ b/contrib/sbom/test/src/mill/contrib/sbom/CycloneDXModuleTests.scala @@ -0,0 +1,86 @@ +package mill.contrib.sbom + +import mill.* +import mill.Agg +import mill.javalib.* +import mill.testkit.{TestRootModule, UnitTester} +import utest.{TestSuite, Tests, test} +import mill.define.{Discover, Target} + +import java.util.UUID +import java.time.Instant + +object TestModule extends TestRootModule { + + val fixedHeader = CycloneDX.SbomHeader( + UUID.fromString("a9d6a1c7-18d4-4901-891c-cbcc8f2c5241"), + Instant.parse("2025-03-17T17:00:56.263933698Z") + ) + + object noDeps extends JavaModule with CycloneDXJavaModule {} + + object withDeps extends JavaModule with CycloneDXJavaModule { + override def sbomHeader(): CycloneDX.SbomHeader = fixedHeader + override def mvnDeps = Seq(mvn"ch.qos.logback:logback-classic:1.5.12") + } + + object withModuleDeps extends JavaModule with CycloneDXJavaModule { + override def sbomHeader(): CycloneDX.SbomHeader = fixedHeader + override def moduleDeps = Seq(withDeps) + override def mvnDeps = Seq(mvn"commons-io:commons-io:2.18.0") + } + + lazy val millDiscover = Discover[this.type] +} +object CycloneDXModuleTests extends TestSuite { + + override def tests = Tests { + test("Report dependencies of an module without dependencies") - UnitTester( + TestModule, + null + ).scoped { eval => + val Right(result) = eval.apply(TestModule.noDeps.sbom) + val components = result.value.components + assert(components.size == 0) + } + test("Report dependencies of a single module") - UnitTester(TestModule, null).scoped { eval => + val toTest = TestModule.withDeps + val Right(result) = eval.apply(toTest.sbom) + val Right(file) = eval.apply(toTest.sbomJsonFile) + val components = result.value.components + assert(components.size == 3) + assert(components.exists(_.name == "logback-classic")) + assert(components.exists(_.name == "logback-core")) + assert(components.exists(_.name == "slf4j-api")) + + assertSameAsReference("withDeps.sbom.json", file.value) + } + test("Report transitive module dependencies") - UnitTester(TestModule, null).scoped { eval => + val toTest = TestModule.withModuleDeps + val Right(result) = eval.apply(toTest.sbom) + val Right(file) = eval.apply(toTest.sbomJsonFile) + val components = result.value.components + assert(components.size == 4) + assert(components.exists(_.name == "commons-io")) + assert(components.exists(_.name == "logback-classic")) + assert(components.exists(_.name == "logback-core")) + assert(components.exists(_.name == "slf4j-api")) + + assertSameAsReference("withModuleDeps.sbom.json", file.value) + } + } + + private def assertSameAsReference(refFile: String, file: PathRef) = { + val reference = String(getClass.getClassLoader.getResourceAsStream(refFile).readAllBytes()) + val current = os.read(file.path) + val actualContentPath = os.pwd / refFile + if (reference != current) { + os.write(actualContentPath, current) + } + assert( + reference == current, + s"The reference file and the current generated SBOM file should match. " + + s"Reference $refFile. Actual file content at: $actualContentPath" + ) + } +} diff --git a/core/util/src/mill/util/Jvm.scala b/core/util/src/mill/util/Jvm.scala index 80f756423a4..da28830dbbb 100644 --- a/core/util/src/mill/util/Jvm.scala +++ b/core/util/src/mill/util/Jvm.scala @@ -9,7 +9,7 @@ import coursier.maven.MavenRepositoryLike import coursier.params.ResolutionParams import coursier.parse.RepositoryParser import coursier.util.Task -import coursier.{Artifacts, Classifier, Dependency, Repository, Resolution, Resolve, Type} +import coursier.{Artifacts, Classifier, Dependency, Fetch, Repository, Resolution, Resolve, Type} import mill.api.* import mill.define.{PathRef, TaskCtx} @@ -460,7 +460,7 @@ object Jvm { /** * Resolve dependencies using Coursier, and return very detailed info about their artifacts. */ - def getArtifacts( + def fetchArtifacts( repositories: Seq[Repository], deps: IterableOnce[Dependency], force: IterableOnce[Dependency] = Nil, @@ -472,7 +472,7 @@ object Jvm { coursierCacheCustomizer: Option[FileCache[Task] => FileCache[Task]] = None, artifactTypes: Option[Set[Type]] = None, resolutionParams: ResolutionParams = ResolutionParams() - ): Result[coursier.Artifacts.Result] = { + ): Result[Fetch.Result] = { val resolutionRes = resolveDependenciesMetadataSafe( repositories, deps, @@ -510,8 +510,12 @@ object Jvm { Result.Failure( s"Failed to load ${if (sources) "source " else ""}dependencies" + errorDetails ) - case Right(res) => - Result.Success(res) + case Right(artifacts) => + Result.Success(Fetch.Result( + resolution, + artifacts.fullDetailedArtifacts0, + artifacts.fullExtraArtifacts + )) } } } @@ -536,7 +540,7 @@ object Jvm { artifactTypes: Option[Set[Type]] = None, resolutionParams: ResolutionParams = ResolutionParams() ): Result[Seq[PathRef]] = - getArtifacts( + fetchArtifacts( repositories, deps, force, @@ -548,9 +552,9 @@ object Jvm { coursierCacheCustomizer, artifactTypes, resolutionParams - ).map { res => + ).map { artifacts => mill.define.BuildCtx.withFilesystemCheckerDisabled { - res.files + artifacts.files .map(os.Path(_)) .map(PathRef(_, quick = true)) } diff --git a/libs/scalalib/src/mill/scalalib/CoursierModule.scala b/libs/scalalib/src/mill/scalalib/CoursierModule.scala index 4e53358f539..edbed27f8d8 100644 --- a/libs/scalalib/src/mill/scalalib/CoursierModule.scala +++ b/libs/scalalib/src/mill/scalalib/CoursierModule.scala @@ -4,7 +4,7 @@ import coursier.cache.FileCache import coursier.core.Resolution import coursier.core.VariantSelector.VariantMatcher import coursier.params.ResolutionParams -import coursier.{Dependency, Repository, Resolve, Type} +import coursier.{Dependency, Fetch, Repository, Resolve, Type} import mill.define.Task import mill.define.{PathRef} import mill.api.{Result} @@ -266,15 +266,15 @@ object CoursierModule { /** * Raw artifact results for the passed dependencies */ - def artifacts[T: CoursierModule.Resolvable]( + def fetch[T: CoursierModule.Resolvable]( deps: IterableOnce[T], sources: Boolean = false - )(implicit ctx: mill.define.TaskCtx): coursier.Artifacts.Result = { + )(implicit ctx: mill.define.TaskCtx): Fetch.Result = { val deps0 = deps .iterator .map(implicitly[CoursierModule.Resolvable[T]].bind(_, bind)) .toSeq - Jvm.getArtifacts( + Jvm.fetchArtifacts( repositories, deps0.map(_.dep), checkGradleModules = checkGradleModules,