
mill.scalalib.CoursierModule.scala Maven / Gradle / Ivy
The newest version!
package mill.scalalib
import coursier.cache.FileCache
import coursier.core.{BomDependency, DependencyManagement, Resolution}
import coursier.params.ResolutionParams
import coursier.{Dependency, Repository, Resolve, Type}
import mill.define.Task
import mill.api.{PathRef, Result}
import scala.annotation.nowarn
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import mill.Agg
import mill.util.Jvm
/**
* This module provides the capability to resolve (transitive) dependencies from (remote) repositories.
*
* It's mainly used in [[JavaModule]], but can also be used stand-alone,
* in which case you must provide repositories by overriding [[CoursierModule.repositoriesTask]].
*/
trait CoursierModule extends mill.Module {
/**
* Bind a dependency ([[Dep]]) to the actual module context (e.g. the scala version and the platform suffix)
* @return The [[BoundDep]]
*/
def bindDependency: Task[Dep => BoundDep] = Task.Anon { (dep: Dep) =>
BoundDep((resolveCoursierDependency.apply(): @nowarn).apply(dep), dep.force)
}
@deprecated("To be replaced by bindDependency", "Mill after 0.11.0-M0")
def resolveCoursierDependency: Task[Dep => coursier.Dependency] = Task.Anon {
Lib.depToDependencyJava(_: Dep)
}
/**
* A `CoursierModule.Resolver` to resolve dependencies.
*
* Unlike `defaultResolver`, this resolver can resolve Mill modules too
* (obtained via `JavaModule#coursierDependency`).
*
* @return `CoursierModule.Resolver` instance
*/
def millResolver: Task[CoursierModule.Resolver] = Task.Anon {
new CoursierModule.Resolver(
repositories = allRepositories(),
bind = bindDependency(),
mapDependencies = Some(mapDependencies()),
customizer = resolutionCustomizer(),
coursierCacheCustomizer = coursierCacheCustomizer(),
ctx = Some(implicitly[mill.api.Ctx.Log]),
resolutionParams = resolutionParams()
)
}
/**
* A `CoursierModule.Resolver` to resolve dependencies.
*
* Can be used to resolve external dependencies, if you need to download an external
* tool from Maven or Ivy repositories, by calling `CoursierModule.Resolver#classpath`.
*
* @return `CoursierModule.Resolver` instance
*/
def defaultResolver: Task[CoursierModule.Resolver] = Task.Anon {
new CoursierModule.Resolver(
repositories = repositoriesTask(),
bind = bindDependency(),
mapDependencies = Some(mapDependencies()),
customizer = resolutionCustomizer(),
coursierCacheCustomizer = coursierCacheCustomizer(),
ctx = Some(implicitly[mill.api.Ctx.Log]),
resolutionParams = resolutionParams()
)
}
/**
* Task that resolves the given dependencies using the repositories defined with [[repositoriesTask]].
*
* @param deps The dependencies to resolve.
* @param sources If `true`, resolve source dependencies instead of binary dependencies (JARs).
* @param artifactTypes If non-empty, pull the passed artifact types rather than the default ones from coursier
* @return The [[PathRef]]s to the resolved files.
*/
def resolveDeps(
deps: Task[Agg[BoundDep]],
sources: Boolean = false,
artifactTypes: Option[Set[Type]] = None,
enableMillInternalDependencies: Boolean = false
): Task[Agg[PathRef]] = {
val repositoriesTask0 =
if (enableMillInternalDependencies) allRepositories
else repositoriesTask
Task.Anon {
Lib.resolveDependencies(
repositories = repositoriesTask0(),
deps = deps(),
sources = sources,
artifactTypes = artifactTypes,
mapDependencies = Some(mapDependencies()),
customizer = resolutionCustomizer(),
coursierCacheCustomizer = coursierCacheCustomizer(),
ctx = Some(implicitly[mill.api.Ctx.Log])
)
}
}
// bin-compat shim
def resolveDeps(
deps: Task[Agg[BoundDep]],
sources: Boolean,
artifactTypes: Option[Set[Type]]
): Task[Agg[PathRef]] =
resolveDeps(
deps,
sources,
artifactTypes,
enableMillInternalDependencies = false
)
@deprecated("Use the override accepting artifactTypes", "Mill after 0.12.0-RC3")
def resolveDeps(
deps: Task[Agg[BoundDep]],
sources: Boolean
): Task[Agg[PathRef]] =
resolveDeps(deps, sources, None)
/**
* Map dependencies before resolving them.
* Override this to customize the set of dependencies.
*/
def mapDependencies: Task[Dependency => Dependency] = Task.Anon { (d: Dependency) => d }
/**
* Mill internal repositories to be used during dependency resolution
*
* These are not meant to be modified by Mill users, unless you really know what you're
* doing.
*/
private[mill] def internalRepositories: Task[Seq[Repository]] = Task.Anon(Nil)
/**
* The repositories used to resolve dependencies with [[classpath()]].
*
* See [[allRepositories]] if you need to resolve Mill internal modules.
*/
def repositoriesTask: Task[Seq[Repository]] = Task.Anon {
val resolve = Resolve()
val repos = Await.result(
resolve.finalRepositories.future()(resolve.cache.ec),
Duration.Inf
)
repos
}
/**
* The repositories used to resolve dependencies
*
* Unlike [[repositoriesTask]], this includes the Mill internal repositories,
* which allow to resolve Mill internal modules (usually brought in via
* `JavaModule#coursierDependency`).
*
* Beware that this needs to evaluate `JavaModule#coursierProject` of all
* module dependencies of the current module, which itself evaluates `JavaModule#ivyDeps`
* and related tasks. You shouldn't depend on this task from implementations of `ivyDeps`,
* which would introduce cycles between Mill tasks.
*/
def allRepositories: Task[Seq[Repository]] = Task.Anon {
internalRepositories() ++ repositoriesTask()
}
/**
* Customize the coursier resolution process.
* This is rarely needed to changed, as the default try to provide a
* highly reproducible resolution process. But sometime, you need
* more control, e.g. you want to add some OS or JDK specific resolution properties
* which are sometimes used by Maven and therefore found in dependency artifact metadata.
* For example, the JavaFX artifacts are known to use OS specific properties.
* To fix resolution for JavaFX, you could override this task like the following:
* {{{
* override def resolutionCustomizer = Task.Anon {
* Some( (r: coursier.core.Resolution) =>
* r.withOsInfo(coursier.core.Activation.Os.fromProperties(sys.props.toMap))
* )
* }
* }}}
* @return
*/
def resolutionCustomizer: Task[Option[Resolution => Resolution]] = Task.Anon { None }
/**
* Customize the coursier file cache.
*
* This is rarely needed to be changed, but sometimes e.g. you want to load a coursier plugin.
* Doing so requires adding to coursier's classpath. To do this you could use the following:
* {{{
* override def coursierCacheCustomizer = Task.Anon {
* Some( (fc: coursier.cache.FileCache[Task]) =>
* fc.withClassLoaders(Seq(classOf[coursier.cache.protocol.S3Handler].getClassLoader))
* )
* }
* }}}
* @return
*/
def coursierCacheCustomizer
: Task[Option[FileCache[coursier.util.Task] => FileCache[coursier.util.Task]]] =
Task.Anon { None }
/**
* Resolution parameters, allowing to customize resolution internals
*
* This rarely needs to be changed. This allows to disable the new way coursier handles
* BOMs since coursier 2.1.17 (used in Mill since 0.12.3) for example, with:
* {{{
* def resolutionParams = super.resolutionParams()
* .withEnableDependencyOverrides(Some(false))
* }}}
*
* Note that versions forced with `Dep#forceVersion()` take over forced versions manually
* set in `resolutionParams`. The former should be favored to force versions in dependency
* resolution.
*
* The Scala version set via `ScalaModule#scalaVersion` also takes over any Scala version
* provided via `ResolutionParams#scalaVersionOpt`.
*
* The default configuration set in `ResolutionParams#defaultConfiguration` is ignored when
* Mill fetches dependencies to be passed to the compiler (equivalent to Maven "compile scope").
* In that case, it forces the default configuration to be "compile". On the other hand, when
* fetching dependencies for runtime (equivalent to Maven "runtime scope"), the value in
* `ResolutionParams#defaultConfiguration` is used.
*/
def resolutionParams: Task[ResolutionParams] = Task.Anon {
ResolutionParams()
}
}
object CoursierModule {
class Resolver(
repositories: Seq[Repository],
bind: Dep => BoundDep,
mapDependencies: Option[Dependency => Dependency] = None,
customizer: Option[coursier.core.Resolution => coursier.core.Resolution] = None,
ctx: Option[mill.api.Ctx.Log] = None,
coursierCacheCustomizer: Option[
coursier.cache.FileCache[coursier.util.Task] => coursier.cache.FileCache[coursier.util.Task]
] = None,
resolutionParams: ResolutionParams = ResolutionParams()
) {
// bin-compat shim
def this(
repositories: Seq[Repository],
bind: Dep => BoundDep,
mapDependencies: Option[Dependency => Dependency],
customizer: Option[coursier.core.Resolution => coursier.core.Resolution],
ctx: Option[mill.api.Ctx.Log],
coursierCacheCustomizer: Option[
coursier.cache.FileCache[coursier.util.Task] => coursier.cache.FileCache[
coursier.util.Task
]
]
) =
this(
repositories,
bind,
mapDependencies,
customizer,
ctx,
coursierCacheCustomizer,
ResolutionParams()
)
/**
* Class path of the passed dependencies
*
* @param deps root dependencies to resolve
* @param sources whether to fetch source JARs or standard JARs
*/
def classpath[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
sources: Boolean = false,
artifactTypes: Option[Set[coursier.Type]] = None,
resolutionParamsMapOpt: Option[ResolutionParams => ResolutionParams] = None,
mapDependencies: Option[Dependency => Dependency] = null
)(implicit ctx: mill.api.Ctx.Log): Agg[PathRef] =
resolveDepsSafe(
deps,
sources,
artifactTypes,
resolutionParamsMapOpt,
mapDependencies
).getOrThrow
@deprecated("Use classpath instead", "Mill 0.12.10")
def resolveDeps[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
sources: Boolean = false,
artifactTypes: Option[Set[coursier.Type]] = None,
resolutionParamsMapOpt: Option[ResolutionParams => ResolutionParams] = None,
mapDependencies: Option[Dependency => Dependency] = null
)(implicit ctx: mill.api.Ctx.Log): Agg[PathRef] =
classpath(deps, sources, artifactTypes, resolutionParamsMapOpt, mapDependencies)
@deprecated("Use classpath instead", "Mill 0.12.10")
def resolveDeps[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
sources: Boolean,
artifactTypes: Option[Set[coursier.Type]],
resolutionParamsMapOpt: Option[ResolutionParams => ResolutionParams]
): Agg[PathRef] = {
implicit val ctx0: mill.api.Ctx.Log = ctx.orNull
resolveDeps(
deps,
sources,
artifactTypes,
resolutionParamsMapOpt,
None
)
}
def resolveDepsSafe[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
sources: Boolean = false,
artifactTypes: Option[Set[coursier.Type]] = None,
resolutionParamsMapOpt: Option[ResolutionParams => ResolutionParams] = None,
mapDependencies: Option[Dependency => Dependency] = null
)(implicit ctx: mill.api.Ctx.Log): Result[Agg[PathRef]] =
Lib.resolveDependencies(
repositories = repositories,
deps = deps.map(implicitly[CoursierModule.Resolvable[T]].bind(_, bind)),
sources = sources,
artifactTypes = artifactTypes,
mapDependencies = Option(mapDependencies).getOrElse(this.mapDependencies),
customizer = customizer,
coursierCacheCustomizer = coursierCacheCustomizer,
ctx = Option(ctx),
resolutionParams = resolutionParamsMapOpt.fold(resolutionParams)(_(resolutionParams))
)
@deprecated("Use classpath instead", "Mill 0.12.10")
def resolveDeps[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
sources: Boolean,
artifactTypes: Option[Set[coursier.Type]]
): Agg[PathRef] = {
implicit val ctx0: mill.api.Ctx.Log = ctx.orNull
classpath(deps, sources, artifactTypes, None)
}
@deprecated("Use classpath instead", "Mill 0.12.10")
def resolveDeps[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
sources: Boolean
): Agg[PathRef] =
resolveDeps(deps, sources, None)
/**
* Processes dependencies and BOMs with coursier
*
* This makes coursier read and process BOM dependencies, and fill empty versions
* in dependencies with the BOMs.
*
* Note that this doesn't throw when an empty version cannot be filled, and just leaves
* the empty version behind.
*
* @param deps dependencies that might have empty versions
* @param resolutionParams coursier resolution parameters
* @return dependencies with empty version filled
*/
@deprecated(
"Use resolution instead, and extract the values you're interested in from it",
"Mill 0.12.10"
)
def processDeps[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
resolutionParams: ResolutionParams = ResolutionParams(),
boms: IterableOnce[BomDependency] = Nil
): (Seq[coursier.core.Dependency], DependencyManagement.Map) = {
val deps0 = deps
.map(implicitly[CoursierModule.Resolvable[T]].bind(_, bind))
.iterator.toSeq
val boms0 = boms.toSeq
val res = Lib.resolveDependenciesMetadataSafe(
repositories = repositories,
deps = deps0,
mapDependencies = mapDependencies,
customizer = customizer,
coursierCacheCustomizer = coursierCacheCustomizer,
ctx = ctx,
resolutionParams = resolutionParams,
boms = boms0
).getOrThrow
(
res.finalDependenciesCache.getOrElse(
deps0.head.dep,
sys.error(
s"Should not happen - could not find root dependency ${deps0.head.dep} in Resolution#finalDependenciesCache"
)
),
res.projectCache
.get(deps0.head.dep.moduleVersion)
.map(_._2.overrides.flatten.toMap)
.getOrElse {
sys.error(
s"Should not happen - could not find root dependency ${deps0.head.dep.moduleVersion} in Resolution#projectCache"
)
}
)
}
/**
* Raw coursier resolution of the passed dependencies
*
* @param deps root dependencies
*/
def resolution[T: CoursierModule.Resolvable](
deps: IterableOnce[T]
)(implicit ctx: mill.api.Ctx.Log): coursier.core.Resolution = {
val deps0 = deps
.map(implicitly[CoursierModule.Resolvable[T]].bind(_, bind))
.iterator.toSeq
Lib.resolveDependenciesMetadataSafe(
repositories = repositories,
deps = deps0,
mapDependencies = mapDependencies,
customizer = customizer,
coursierCacheCustomizer = coursierCacheCustomizer,
ctx = Option(ctx),
resolutionParams = ResolutionParams(),
boms = Nil
).getOrThrow
}
@deprecated(
"Use resolution instead, and call orderedDependencies on its returned value",
"Mill 0.12.10"
)
def allDeps[T: CoursierModule.Resolvable](
deps: IterableOnce[T]
): Seq[coursier.core.Dependency] = {
implicit val ctx0: mill.api.Ctx.Log = ctx.orNull
resolution(deps).orderedDependencies
}
/**
* Raw artifact results for the passed dependencies
*/
def artifacts[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
sources: Boolean = false
)(implicit ctx0: mill.api.Ctx.Log): coursier.Artifacts.Result = {
val deps0 = deps
.iterator
.map(implicitly[CoursierModule.Resolvable[T]].bind(_, bind))
.toSeq
Jvm.getArtifacts(
repositories,
deps0.map(_.dep),
sources = sources,
ctx = Option(ctx0)
).getOrThrow
}
}
sealed trait Resolvable[T] {
def bind(t: T, bind: Dep => BoundDep): BoundDep
}
implicit case object ResolvableDep extends Resolvable[Dep] {
def bind(t: Dep, bind: Dep => BoundDep): BoundDep = bind(t)
}
implicit case object ResolvableBoundDep extends Resolvable[BoundDep] {
def bind(t: BoundDep, bind: Dep => BoundDep): BoundDep = t
}
implicit case object ResolvableCoursierDep extends Resolvable[coursier.core.Dependency] {
def bind(t: coursier.core.Dependency, bind: Dep => BoundDep): BoundDep =
BoundDep(t, force = false)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy