Skip to content

Commit

Permalink
Bind cross-versions to the context of the originating module (#1574)
Browse files Browse the repository at this point in the history
`mill.scalalib.Dep` represents a generic dependency, which include
potential cross-compilation properties. E.g. it knows whether it is
specific to a `platform` but it does not specify the platform. And it
knows whether it is specific to a Scala binary or full version, but it
does not specify the exact Scala version. This mean we can define a
`Dep` without knowing those context.

In this PR I introduce a new type `mill.scalalib.BoundDep`, which is
kind-of a `Dep` where all these potential open context bindings are
bound to an exact context. There is no further cross-resolution
necessary nor possible. By having a separate type, we always know with
what kind of dependency we work. Also, we can be explicit about the
"when" the binding happens.

When mixing dependencies of multiple modules, we also mix contexts.
Until this PR, we had no way to get it right, as `Dep`s could come from
different contexts. Now, we can safely mix `BoundDep`s as they are
already context-bound.

We could encode the bound-state in the `Dep`, but this would result in
only a dynamic property. To ensure a resolved state, we always would
need to run some runtime checks. Also, it could lead to situations,
where we need a resolved dep, but have not the proper context to do the
resolution. This is exactly what I implemented in the initial attempt,
but it didn't worked out very well. With the separated types, it is
always clear from the API, what kind of dependency we expect.

We could also just resolve to a `coursier.Dependency`, but that would
loose the `force` flag. Also, we want to rather decouple our API from
the coursier API, which seem easier when we avoid to hand over direct
coursier API to the user.

I `@deprecated` the existing `CoursierModule.resolveCoursierDependency`
and bases the new `CoursierModule.bindDependency` on this old task. This
should ease the transition. It's up for revisit, when we decouple from
coursier API altogether.

This fixes 
#860.
  • Loading branch information
lefou authored Dec 16, 2022
2 parents 31d1761 + 72c60c1 commit 72e38b4
Show file tree
Hide file tree
Showing 25 changed files with 527 additions and 119 deletions.
3 changes: 1 addition & 2 deletions contrib/bloop/src/mill/contrib/bloop/BloopImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,7 @@ class BloopImpl(ev: () => Evaluator, wd: os.Path) extends ExternalModule { outer
val repos = module.repositoriesTask()
// same as input of resolvedIvyDeps
val allIvyDeps = module.transitiveIvyDeps() ++ module.transitiveCompileIvyDeps()
val coursierDeps =
allIvyDeps.map(module.resolveCoursierDependency()).toList
val coursierDeps = allIvyDeps.map(_.dep).toList
BloopConfig.Resolution(artifacts(repos, coursierDeps))
}

Expand Down
5 changes: 4 additions & 1 deletion contrib/flyway/src/FlywayModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ trait FlywayModule extends JavaModule {
def flywayDriverDeps: T[Agg[Dep]]

def jdbcClasspath = T {
resolveDeps(flywayDriverDeps)()
resolveDeps(T.task {
val bind = bindDependency()
flywayDriverDeps().map(bind)
})()
}

private def strToOptPair[A](key: String, v: String) =
Expand Down
10 changes: 7 additions & 3 deletions contrib/jmh/src/mill/contrib/jmh/JmhModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ trait JmhModule extends JavaModule {
(sourcesDir, resourcesDir)
}

def generatorDeps =
def generatorDeps = T {
resolveDeps(
T { Agg(ivy"org.openjdk.jmh:jmh-generator-bytecode:${jmhGeneratorByteCodeVersion()}") }
)
T.task {
val bind = bindDependency()
Agg(ivy"org.openjdk.jmh:jmh-generator-bytecode:${jmhGeneratorByteCodeVersion()}").map(bind)
}
)()
}
}
5 changes: 4 additions & 1 deletion contrib/playlib/src/mill/playlib/RouterModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ trait RouterModule extends ScalaModule with Version {
def generatorType: RouteCompilerType = RouteCompilerType.InjectedGenerator

def routerClasspath: T[Agg[PathRef]] = T {
resolveDeps(T.task { Agg(ivy"com.typesafe.play::routes-compiler:${playVersion()}") })()
resolveDeps(T.task {
val bind = bindDependency()
Agg(ivy"com.typesafe.play::routes-compiler:${playVersion()}").map(bind)
})()
}

protected val routeCompilerWorker: RouteCompilerWorkerModule = RouteCompilerWorkerModule
Expand Down
1 change: 0 additions & 1 deletion contrib/playlib/src/mill/playlib/Static.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ trait Static extends ScalaModule {
def webJars = T {
Lib.resolveDependencies(
repositoriesTask(),
Lib.depToDependency(_, scalaVersion()),
webJarDeps()
)
}
Expand Down
4 changes: 3 additions & 1 deletion contrib/proguard/src/Proguard.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ trait Proguard extends ScalaModule {
* These are downloaded from JCenter and fed to `java -cp`
*/
def proguardClasspath: T[Loose.Agg[PathRef]] = T {
resolveDeps(T.task { Agg(ivy"com.guardsquare:proguard-base:${proguardVersion()}") })()
resolveDeps(T.task {
Agg(ivy"com.guardsquare:proguard-base:${proguardVersion()}").map(bindDependency())
})()
}

private def steps: T[Seq[String]] = T {
Expand Down
2 changes: 1 addition & 1 deletion contrib/scalapblib/src/ScalaPBModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ trait ScalaPBModule extends ScalaModule {
coursier.LocalRepositories.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2")
),
Lib.depToDependency(_, "2.13.1"),
Seq(ivy"com.thesamet.scalapb::scalapbc:${scalaPBVersion()}")
.map(Lib.depToBoundDep(_, "2.13.1"))
)
}

Expand Down
19 changes: 14 additions & 5 deletions contrib/scoverage/src/ScoverageModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,21 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
val pluginDep =
Agg(ivy"org.scoverage:scalac-scoverage-plugin_${scalaVersion}:${sv}")

if (isScala3() && isScoverage2()) {
val deps = if (isScala3() && isScoverage2()) {
baseDeps
} else if (isScoverage2()) {
baseDeps ++ pluginDep
} else {
pluginDep
}
deps.map(bindDependency())
})()
}

def scoverageClasspath: T[Agg[PathRef]] = T {
resolveDeps(scoveragePluginDeps)()
resolveDeps(T.task {
scoveragePluginDeps().map(bindDependency())
})()
}

def scoverageReportWorkerClasspath: T[Agg[PathRef]] = T {
Expand Down Expand Up @@ -263,15 +266,21 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
trait ScoverageTests extends outer.Tests {
override def upstreamAssemblyClasspath = T {
super.upstreamAssemblyClasspath() ++
resolveDeps(outer.scoverageRuntimeDeps)()
resolveDeps(T.task {
outer.scoverageRuntimeDeps().map(bindDependency())
})()
}
override def compileClasspath = T {
super.compileClasspath() ++
resolveDeps(outer.scoverageRuntimeDeps)()
resolveDeps(T.task {
outer.scoverageRuntimeDeps().map(bindDependency())
})()
}
override def runClasspath = T {
super.runClasspath() ++
resolveDeps(outer.scoverageRuntimeDeps)()
resolveDeps(T.task {
outer.scoverageRuntimeDeps().map(bindDependency())
})()
}

// Need the sources compiled with scoverage instrumentation to run.
Expand Down
5 changes: 4 additions & 1 deletion contrib/twirllib/src/TwirlModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ trait TwirlModule extends mill.Module { twirlModule =>
val twirlCoursierResolver = new TwirlResolver()

def twirlClasspath: T[Loose.Agg[PathRef]] = T {
twirlCoursierResolver.resolveDeps(twirlIvyDeps)
twirlCoursierResolver.resolveDeps(T.task {
val bind = twirlCoursierResolver.bindDependency()
twirlIvyDeps().map(bind)
})
}

def twirlImports: T[Seq[String]] = T {
Expand Down
12 changes: 7 additions & 5 deletions integration/thirdparty/local/resources/ammonite/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ trait AmmDependenciesResourceFileModule extends JavaModule {
def crossScalaVersion: String
def dependencyResourceFileName: String
override def resources = T.sources {

val deps0 = T.task { compileIvyDeps() ++ transitiveIvyDeps() }()
val deps0 = T.task {
compileIvyDeps().map(bindDependency()) ++ transitiveIvyDeps() }()
val (_, res) = mill.modules.Jvm.resolveDependenciesMetadata(
repositoriesTask(),
deps0.map(resolveCoursierDependency().apply(_)),
deps0.filter(_.force).map(resolveCoursierDependency().apply(_)),
deps0.map(_.dep),
deps0.filter(_.force).map(_.dep),
mapDependencies = Some(mapDependencies())
)

Expand Down Expand Up @@ -159,7 +159,9 @@ object amm extends Cross[MainModule](fullCrossScalaVersions: _*) {
(super.resources() ++
ReplModule.this.sources() ++
ReplModule.this.externalSources() ++
resolveDeps(ivyDeps, sources = true)()).distinct
resolveDeps(T.task{
ivyDeps().map(bindDependency())
}, sources = true)()).distinct
}
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"org.scalaz::scalaz-core:7.2.24"
Expand Down
11 changes: 5 additions & 6 deletions scalajslib/src/mill/scalajslib/ScalaJSModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
// we need to use the scala-library of the currently running mill
resolveDependencies(
repositoriesTask(),
Lib.depToDependency(_, mill.BuildInfo.scalaVersion, ""),
commonDeps ++ envDeps,
(commonDeps ++ envDeps).map(Lib.depToBoundDep(_, mill.BuildInfo.scalaVersion, "")),
ctx = Some(T.log)
)
}
Expand Down Expand Up @@ -253,13 +252,13 @@ trait TestScalaJSModule extends ScalaJSModule with TestModule {

def scalaJSTestDeps = T {
resolveDeps(T.task {
val bridgeOrInterface =
if (ZincWorkerUtil.scalaJSUsesTestBridge(scalaJSVersion())) "bridge"
else "interface"
val bind = bindDependency()
Loose.Agg(
ivy"org.scala-js::scalajs-library:${scalaJSVersion()}",
ivy"org.scala-js::scalajs-test-bridge:${scalaJSVersion()}"
).map(_.withDottyCompat(scalaVersion()))
)
.map(_.withDottyCompat(scalaVersion()))
.map(bind)
})
}

Expand Down
37 changes: 24 additions & 13 deletions scalalib/src/CoursierModule.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package mill.scalalib

import coursier.cache.FileCache

import coursier.{Dependency, Repository, Resolve}
import coursier.core.Resolution
import mil.scalalib.BoundDep
import mill.{Agg, T}
import mill.define.Task
import mill.api.PathRef

import scala.annotation.nowarn

/**
* This module provides the capability to resolve (transitive) dependencies from (remote) repositories.
*
Expand All @@ -16,6 +18,15 @@ import mill.api.PathRef
*/
trait CoursierModule extends mill.Module {

/**
* Bind a dependency ([[Dep]]) to the actual module contetxt (e.g. the scala version and the platform suffix)
* @return The [[BoundDep]]
*/
def bindDependency: Task[Dep => BoundDep] = T.task { dep: Dep =>
BoundDep((resolveCoursierDependency() : @nowarn).apply(dep), dep.force)
}

@deprecated("To be replaced by bindDependency", "Mill after 0.11.0-M0")
def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task {
Lib.depToDependencyJava(_: Dep)
}
Expand All @@ -27,18 +38,18 @@ trait CoursierModule extends mill.Module {
* @param sources If `true`, resolve source dependencies instead of binary dependencies (JARs).
* @return The [[PathRef]]s to the resolved files.
*/
def resolveDeps(deps: Task[Agg[Dep]], sources: Boolean = false): Task[Agg[PathRef]] = T.task {
Lib.resolveDependencies(
repositories = repositoriesTask(),
depToDependency = resolveCoursierDependency().apply(_),
deps = deps(),
sources = sources,
mapDependencies = Some(mapDependencies()),
customizer = resolutionCustomizer(),
coursierCacheCustomizer = coursierCacheCustomizer(),
ctx = Some(implicitly[mill.api.Ctx.Log])
)
}
def resolveDeps(deps: Task[Agg[BoundDep]], sources: Boolean = false): Task[Agg[PathRef]] =
T.task {
Lib.resolveDependencies(
repositories = repositoriesTask(),
deps = deps(),
sources = sources,
mapDependencies = Some(mapDependencies()),
customizer = resolutionCustomizer(),
coursierCacheCustomizer = coursierCacheCustomizer(),
ctx = Some(implicitly[mill.api.Ctx.Log])
)
}

/**
* Map dependencies before resolving them.
Expand Down
21 changes: 19 additions & 2 deletions scalalib/src/Dep.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package mill.scalalib

import JsonFormatters._
import mill.scalalib.JsonFormatters._
import upickle.default.{macroRW, ReadWriter => RW}
import CrossVersion._
import mil.scalalib.BoundDep
import mill.scalalib.api.ZincWorkerUtil



case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
require(
!dep.module.name.value.contains("/") &&
Expand All @@ -21,7 +24,7 @@ case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
def forceVersion(): Dep = copy(force = true)
def exclude(exclusions: (String, String)*) = copy(
dep = dep.withExclusions(
dep.exclusions ++
dep.exclusions() ++
exclusions.map { case (k, v) => (coursier.Organization(k), coursier.ModuleName(v)) }
)
)
Expand All @@ -33,13 +36,27 @@ case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
coursier.ModuleName(artifactName(binaryVersion, fullVersion, platformSuffix))
)
)
def bindDep(binaryVersion: String, fullVersion: String, platformSuffix: String): BoundDep =
BoundDep(
dep.withModule(
dep.module.withName(
coursier.ModuleName(artifactName(binaryVersion, fullVersion, platformSuffix))
)
),
force
)

def withConfiguration(configuration: String): Dep = copy(
dep = dep.withConfiguration(coursier.core.Configuration(configuration))
)
def optional(optional: Boolean = true): Dep = copy(
dep = dep.withOptional(optional)
)

def organization = dep.module.organization.value
def name = dep.module.name.value
def version = dep.version

/**
* If scalaVersion is a Dotty version, replace the cross-version suffix
* by the Scala 2.x version that the Dotty version is retro-compatible with,
Expand Down
24 changes: 17 additions & 7 deletions scalalib/src/GenIdeaImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package mill.scalalib
import scala.collection.immutable
import scala.util.Try
import scala.xml.{Elem, MetaData, Node, NodeSeq, Null, UnprefixedAttribute}

import ammonite.runtime.SpecialClassLoader
import coursier.core.compatibility.xmlParseDom
import coursier.maven.Pom
import coursier.{LocalRepositories, Repositories, Repository}
import mil.scalalib.BoundDep

import java.nio.file.Paths
import mill.Agg
import mill.api.Ctx.{Home, Log}
Expand Down Expand Up @@ -102,10 +103,11 @@ case class GenIdeaImpl(
LocalRepositories.ivy2Local,
Repositories.central
)
val millDeps = BuildInfo.millEmbeddedDeps.map(d => ivy"$d")
val millDeps = BuildInfo.millEmbeddedDeps.map(d => ivy"$d").map(dep =>
BoundDep(Lib.depToDependency(dep, BuildInfo.scalaVersion, ""), dep.force)
)
val Result.Success(res) = scalalib.Lib.resolveDependencies(
repositories = repos.toList,
depToDependency = Lib.depToDependency(_, BuildInfo.scalaVersion, ""),
deps = millDeps,
sources = false,
mapDependencies = None,
Expand All @@ -118,7 +120,6 @@ case class GenIdeaImpl(
{
scalalib.Lib.resolveDependencies(
repositories = repos.toList,
depToDependency = Lib.depToDependency(_, BuildInfo.scalaVersion, ""),
deps = millDeps,
sources = true,
mapDependencies = None,
Expand Down Expand Up @@ -157,14 +158,20 @@ case class GenIdeaImpl(
}

val externalLibraryDependencies = T.task {
mod.resolveDeps(mod.mandatoryIvyDeps)()
mod.resolveDeps(T.task {
val bind = mod.bindDependency()
mod.mandatoryIvyDeps().map(bind) })()
}

val externalDependencies = T.task {
mod.resolvedIvyDeps() ++
T.traverse(mod.transitiveModuleDeps)(_.unmanagedClasspath)().flatten
}
val extCompileIvyDeps = mod.resolveDeps(mod.compileIvyDeps)
val extCompileIvyDeps =
mod.resolveDeps(T.task {
val bind = mod.bindDependency()
mod.compileIvyDeps().map(bind)
})
val extRunIvyDeps = mod.resolvedRunIvyDeps

val externalSources = T.task {
Expand All @@ -180,7 +187,10 @@ case class GenIdeaImpl(
}

val scalacPluginDependencies = T.task {
mod.resolveDeps(scalacPluginsIvyDeps)()
mod.resolveDeps(T.task {
val bind = mod.bindDependency()
scalacPluginsIvyDeps().map(bind)
})()
}

val facets = T.task {
Expand Down
Loading

0 comments on commit 72e38b4

Please sign in to comment.