Changes in Resolution API

This commit is contained in:
Alexandre Archambault 2015-06-25 00:18:50 +01:00
parent 510dd7b2d4
commit 27c8cc9929
7 changed files with 93 additions and 87 deletions

View File

@ -13,7 +13,7 @@ import scalaz.{-\/, \/-}
case class Coursier(scope: List[String],
keepOptional: Boolean,
fetch: Boolean,
@ExtraName("N") maxIterations: Int) extends App {
@ExtraName("N") maxIterations: Int = 100) extends App {
val scopes0 =
if (scope.isEmpty) List(Scope.Compile, Scope.Runtime)
@ -85,12 +85,12 @@ case class Coursier(scope: List[String],
Dependency(mod, ver, scope = Scope.Runtime)
}
val res = resolve(
val startRes = Resolution(
deps.toSet,
fetchFrom(repositories),
maxIterations = Some(maxIterations).filter(_ > 0),
filter = Some(dep => (keepOptional || !dep.optional) && scopes(dep.scope))
).run
)
val res = startRes.last(fetchFrom(repositories), maxIterations).run
if (!res.isDone) {
Console.err.println(s"Maximum number of iteration reached!")
@ -107,7 +107,7 @@ case class Coursier(scope: List[String],
s"${dep.module.organization}:${dep.module.name}:${dep.artifact.`type`}:${Some(dep.artifact.classifier).filter(_.nonEmpty).map(_+":").mkString}$version$extra"
}
val trDeps = res.dependencies.toList.sortBy(repr)
val trDeps = res.minDependencies.toList.sortBy(repr)
println("\n" + trDeps.map(repr).distinct.mkString("\n"))
@ -129,7 +129,7 @@ case class Coursier(scope: List[String],
val cachePolicy: CachePolicy = CachePolicy.Default
val m = res.dependencies.groupBy(dep => res.projectsCache.get(dep.moduleVersion).map(_._1))
val m = res.minDependencies.groupBy(dep => res.projectsCache.get(dep.moduleVersion).map(_._1))
val (notFound, remaining0) = m.partition(_._1.isEmpty)
if (notFound.nonEmpty) {
val notFound0 = notFound.values.flatten.toList.map(repr).sorted

View File

@ -312,52 +312,19 @@ object Resolution {
def defaultFilter(dep: Dependency): Boolean =
!dep.optional && dep.scope == Scope.Compile
/**
* Get all the transitive dependencies of `dependencies`, solving any dependency version mismatch.
*
* Iteratively fetches the missing info of the current dependencies / add newly discovered dependencies
* to the current ones. The maximum number of such iterations can be bounded with `maxIterations`.
*
* ...
*
*/
def resolve(dependencies: Set[Dependency],
fetch: ModuleVersion => EitherT[Task, List[String], (Repository, Project)],
maxIterations: Option[Int],
filter: Option[Dependency => Boolean],
profileActivation: Option[(String, Activation, Map[String, String]) => Boolean]): Task[Resolution] = {
val dependencies0 = dependencies.map(withDefaultScope)
val startResolution = Resolution(
dependencies0, Set.empty, Set.empty,
Map.empty, Map.empty,
filter,
profileActivation
)
def helper(resolution: Resolution, remainingIter: Option[Int]): Task[(Resolution, Option[Int])] = {
if (resolution.isDone || remainingIter.exists(_ <= 0))
Task.now((resolution, remainingIter))
else
resolution.next(fetch).flatMap(helper(_, remainingIter.map(_ - 1)))
}
helper(startResolution, maxIterations).map(_._1)
}
}
/**
* State of a dependency resolution.
*
* Done if method `isDone` returns `true`.
*
* @param dependencies: current set of dependencies
* @param conflicts: conflicting dependencies
* @param projectsCache: cache of known projects
* @param errors: keeps track of the modules whose project definition could not be found
*/
/**
* State of a dependency resolution.
*
* Done if method `isDone` returns `true`.
*
* @param dependencies: current set of dependencies
* @param conflicts: conflicting dependencies
* @param projectsCache: cache of known projects
* @param errors: keeps track of the modules whose project definition could not be found
*/
case class Resolution(rootDependencies: Set[Dependency],
dependencies: Set[Dependency],
conflicts: Set[Dependency],
@ -382,10 +349,9 @@ case class Resolution(rootDependencies: Set[Dependency],
* No attempt is made to solve version conflicts here.
*/
def transitiveDependencies: Seq[Dependency] =
for {
dep <- (dependencies -- conflicts).toList
trDep <- finalDependencies0(dep)
} yield trDep
(dependencies -- conflicts)
.toList
.flatMap(finalDependencies0)
/**
* The "next" dependency set, made of the current dependencies and their transitive dependencies,
@ -397,7 +363,7 @@ case class Resolution(rootDependencies: Set[Dependency],
* Returns a tuple made of the conflicting dependencies, and all the dependencies.
*/
def nextDependenciesAndConflicts: (Seq[Dependency], Seq[Dependency]) = {
merge(rootDependencies ++ dependencies ++ transitiveDependencies)
merge(rootDependencies.map(withDefaultScope) ++ dependencies ++ transitiveDependencies)
}
/**
@ -456,7 +422,7 @@ case class Resolution(rootDependencies: Set[Dependency],
* The versions of all the dependencies returned are erased (emptied).
*/
def remainingDependencies: Set[Dependency] = {
val rootDependencies0 = rootDependencies.map(eraseVersion)
val rootDependencies0 = rootDependencies.map(withDefaultScope).map(eraseVersion)
@tailrec
def helper(reverseDeps: Map[Dependency, Vector[Dependency]]): Map[Dependency, Vector[Dependency]] = {
@ -632,4 +598,14 @@ case class Resolution(rootDependencies: Set[Dependency],
}
}
def last(fetchModule: ModuleVersion => EitherT[Task, List[String], (Repository, Project)], maxIterations: Int = -1): Task[Resolution] = {
if (maxIterations == 0 || isDone) Task.now(this)
else {
next(fetchModule)
.flatMap(_.last(fetchModule, if (maxIterations > 0) maxIterations - 1 else maxIterations))
}
}
def minDependencies: Set[Dependency] =
Orders.minDependencies(dependencies)
}

View File

@ -88,6 +88,14 @@ package object coursier {
maxIterations: Option[Int] = Some(200),
filter: Option[Dependency => Boolean] = None,
profileActivation: Option[(String, Profile.Activation, Map[String, String]) => Boolean] = None): Task[Resolution] = {
core.Resolution.resolve(dependencies, fetch, maxIterations, filter, profileActivation)
val startResolution = Resolution(
dependencies, Set.empty, Set.empty,
Map.empty, Map.empty,
filter,
profileActivation
)
startResolution.last(fetch, maxIterations.getOrElse(-1))
}
}

View File

@ -38,7 +38,7 @@ object CentralTests extends TestSuite {
.copy(projectsCache = Map.empty, errors = Map.empty) // No validating these here
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(
dep.withCompileScope,
Dependency(Module("ch.qos.logback", "logback-core"), "1.1.3").withCompileScope,
@ -54,7 +54,7 @@ object CentralTests extends TestSuite {
.copy(projectsCache = Map.empty, errors = Map.empty) // No validating these here
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(
dep.withCompileScope,
Dependency(Module("org.ow2.asm", "asm-tree"), "5.0.2").withCompileScope,
@ -70,7 +70,7 @@ object CentralTests extends TestSuite {
val res = res0.copy(projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(
dep.withCompileScope))

View File

@ -166,7 +166,7 @@ object ResolutionTests extends TestSuite {
).runF)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope),
errors = Map(dep.moduleVersion -> Seq("Not found"))
)
@ -183,7 +183,7 @@ object ResolutionTests extends TestSuite {
).runF)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope),
projectsCache = Map(dep.moduleVersion -> (testRepository, projectsMap(dep.moduleVersion)))
)
@ -201,7 +201,7 @@ object ResolutionTests extends TestSuite {
).runF)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope, trDep.withCompileScope),
projectsCache = Map(
projectsMap(dep.moduleVersion).kv,
@ -225,7 +225,7 @@ object ResolutionTests extends TestSuite {
).runF)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope),
projectsCache = Map(
projectsMap(dep.moduleVersion).kv
@ -250,7 +250,7 @@ object ResolutionTests extends TestSuite {
).runF)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope),
projectsCache = Map(
projectsMap(dep.moduleVersion).kv
@ -275,7 +275,7 @@ object ResolutionTests extends TestSuite {
).runF)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope),
projectsCache = Map(
projectsMap(dep.moduleVersion).kv
@ -295,7 +295,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope),
projectsCache = Map(
projectsMap(dep.moduleVersion).kv
@ -319,7 +319,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope)
)
@ -339,7 +339,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope)
)
@ -358,7 +358,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope)
)
@ -375,7 +375,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope)
)
@ -394,7 +394,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope)
)
@ -415,7 +415,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope)
)
@ -438,7 +438,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope)
)
@ -460,7 +460,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep.withCompileScope),
rootDependencies = Set(dep),
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope)
)
@ -484,7 +484,7 @@ object ResolutionTests extends TestSuite {
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
val expected = Resolution(
rootDependencies = deps.map(_.withCompileScope),
rootDependencies = deps,
dependencies = (deps ++ trDeps).map(_.withCompileScope)
)

View File

@ -1,9 +1,26 @@
package coursier
import scalaz.EitherT
import scalaz.concurrent.Task
package object test {
implicit class DependencyOps(val underlying: Dependency) extends AnyVal {
def withCompileScope: Dependency = underlying.copy(scope = Scope.Compile)
}
def resolve(dependencies: Set[Dependency],
fetch: ModuleVersion => EitherT[Task, List[String], (Repository, Project)],
maxIterations: Option[Int] = Some(200),
filter: Option[Dependency => Boolean] = None,
profileActivation: Option[(String, Profile.Activation, Map[String, String]) => Boolean] = None): Task[Resolution] = {
val startResolution = Resolution(
dependencies,
filter = filter,
profileActivation = profileActivation
)
startResolution.last(fetch, maxIterations.getOrElse(-1))
}
}

View File

@ -1,7 +1,7 @@
package coursier
package web
import coursier.core.{Resolver, Logger, Remote}
import coursier.core.{Logger, Remote}
import japgolly.scalajs.react.vdom.{TagMod, Attr}
import japgolly.scalajs.react.vdom.Attrs.dangerouslySetInnerHtml
import japgolly.scalajs.react.{ReactEventI, ReactComponentB, BackendScope}
@ -71,13 +71,15 @@ class Backend($: BackendScope[Unit, State]) {
def updateTree(resolution: Resolution, target: String, reverse: Boolean) = {
def depsOf(dep: Dependency) =
resolution.projectsCache.get(dep.moduleVersion).toSeq.flatMap(t => Resolver.finalDependencies(dep, t._2).filter(resolution.filter getOrElse Resolver.defaultFilter))
resolution.projectsCache.get(dep.moduleVersion).toSeq.flatMap(t => core.Resolution.finalDependencies(dep, t._2).filter(resolution.filter getOrElse core.Resolution.defaultFilter))
val minDependencies = resolution.minDependencies
lazy val reverseDeps = {
var m = Map.empty[Module, Seq[Dependency]]
for {
dep <- resolution.dependencies
dep <- minDependencies
trDep <- depsOf(dep)
} {
m += trDep.module -> (m.getOrElse(trDep.module, Nil) :+ dep)
@ -95,8 +97,8 @@ class Backend($: BackendScope[Unit, State]) {
else Seq("nodes" -> js.Array(deps.map(tree): _*))
}: _*)
println(resolution.dependencies.toList.map(tree).map(js.JSON.stringify(_)))
g.$(target).treeview(js.Dictionary("data" -> js.Array(resolution.dependencies.toList.map(tree): _*)))
println(minDependencies.toList.map(tree).map(js.JSON.stringify(_)))
g.$(target).treeview(js.Dictionary("data" -> js.Array(minDependencies.toList.map(tree): _*)))
}
def resolve(action: => Unit = ()) = {
@ -119,11 +121,14 @@ class Backend($: BackendScope[Unit, State]) {
}
val s = $.state
def task = coursier.resolve(
s.modules.toSet,
fetchFrom(s.repositories.map(_.copy(logger = Some(logger)))),
filter = Some(dep => (s.options.followOptional || !dep.optional) && (s.options.keepTest || dep.scope != Scope.Test))
)
def task = {
val res = coursier.Resolution(
s.modules.toSet,
filter = Some(dep => (s.options.followOptional || !dep.optional) && (s.options.keepTest || dep.scope != Scope.Test))
)
res.last(fetchFrom(s.repositories.map(_.copy(logger = Some(logger)))), 100)
}
// For reasons that are unclear to me, not delaying this when using the runNow execution context
// somehow discards the $.modState above. (Not a major problem as queue is used by default.)
@ -258,7 +263,7 @@ object App {
)
}
val sortedDeps = res.dependencies.toList
val sortedDeps = res.minDependencies.toList
.sortBy(dep => coursier.core.Module.unapply(dep.module).get)
<.table(^.`class` := "table",