From 962ef2a4aaf95a6fa0359799a5cbcfd397b18138 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sat, 20 Jun 2015 15:02:29 +0100 Subject: [PATCH 1/6] Allow specifying the max number of iterations on the command line --- cli/src/main/scala/coursier/cli/Coursier.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cli/src/main/scala/coursier/cli/Coursier.scala b/cli/src/main/scala/coursier/cli/Coursier.scala index 2e14c1926..118650904 100644 --- a/cli/src/main/scala/coursier/cli/Coursier.scala +++ b/cli/src/main/scala/coursier/cli/Coursier.scala @@ -12,7 +12,8 @@ import scalaz.{-\/, \/-} case class Coursier(scope: List[String], keepOptional: Boolean, - fetch: Boolean) extends App { + fetch: Boolean, + @ExtraName("N") maxIterations: Int) extends App { val scopes0 = if (scope.isEmpty) List(Scope.Compile, Scope.Runtime) @@ -85,9 +86,15 @@ case class Coursier(scope: List[String], val res = resolve( deps.toSet, fetchFrom(repositories), + maxIterations = Some(maxIterations).filter(_ > 0), filter = Some(dep => (keepOptional || !dep.optional) && scopes(dep.scope)) ).run + if (!res.isDone) { + println(s"Maximum number of iteration reached!") + sys exit 1 + } + def repr(dep: Dependency) = s"${dep.module.organization}:${dep.module.name}:${dep.`type`}:${Some(dep.classifier).filter(_.nonEmpty).map(_+":").mkString}${dep.version}" From a325a8fdf0230492c0452ad55e6298bff1b1c6f9 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sat, 20 Jun 2015 15:02:34 +0100 Subject: [PATCH 2/6] Minor fix --- web/src/main/scala/coursier/web/Backend.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/web/src/main/scala/coursier/web/Backend.scala b/web/src/main/scala/coursier/web/Backend.scala index b41b95161..0a880c2dc 100644 --- a/web/src/main/scala/coursier/web/Backend.scala +++ b/web/src/main/scala/coursier/web/Backend.scala @@ -4,11 +4,13 @@ package web import coursier.core.{Resolver, Logger, Remote} import japgolly.scalajs.react.vdom.{TagMod, Attr} import japgolly.scalajs.react.vdom.Attrs.dangerouslySetInnerHtml -import japgolly.scalajs.react.{ReactKeyboardEventI, ReactEventI, ReactComponentB, BackendScope} +import japgolly.scalajs.react.{ReactEventI, ReactComponentB, BackendScope} import japgolly.scalajs.react.vdom.prefix_<^._ import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue import org.scalajs.jquery.jQuery +import scala.concurrent.Future + import scala.scalajs.js import js.Dynamic.{global => g} @@ -117,13 +119,15 @@ class Backend($: BackendScope[Unit, State]) { } val s = $.state - val task = coursier.resolve( + 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)) ) - task.runF.foreach { res: Resolution => + // 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.) + Future(task)(scala.scalajs.concurrent.JSExecutionContext.Implicits.queue).flatMap(_.runF).foreach { res: Resolution => $.modState{ s => updateDepGraph(res); updateTree(res, "#deptree", reverse = s.reverseTree); s.copy(resolutionOpt = Some(res), resolving = false)} g.$("#resResTab a:last").tab("show") } From 9455a9906fa0f3a9be9b0bc64c9e0a681144fc23 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sat, 20 Jun 2015 15:02:35 +0100 Subject: [PATCH 3/6] Basic caching --- .../src/main/scala/coursier/core/Resolver.scala | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/coursier/core/Resolver.scala b/core/src/main/scala/coursier/core/Resolver.scala index f19f7fcc9..d39aabe33 100644 --- a/core/src/main/scala/coursier/core/Resolver.scala +++ b/core/src/main/scala/coursier/core/Resolver.scala @@ -3,6 +3,7 @@ package coursier.core import java.util.regex.Pattern.quote import scala.annotation.tailrec +import scala.collection.mutable import scalaz.concurrent.Task import scalaz.{EitherT, \/-, \/, -\/} @@ -378,6 +379,16 @@ object Resolver { filter: Option[Dependency => Boolean], profileActivation: Option[(String, Activation, Map[String, String]) => Boolean]) { + private val finalDependenciesCache = new mutable.HashMap[Dependency, Seq[Dependency]]() + private def finalDependencies0(dep: Dependency) = finalDependenciesCache.synchronized { + finalDependenciesCache.getOrElseUpdate(dep, + projectsCache.get(dep.moduleVersion) match { + case Some((_, proj)) => finalDependencies(dep, proj).filter(filter getOrElse defaultFilter) + case None => Nil + } + ) + } + /** * Transitive dependencies of the current dependencies, according to what there currently is in cache. * No attempt is made to solve version conflicts here. @@ -385,8 +396,7 @@ object Resolver { def transitiveDependencies = for { dep <- (dependencies -- conflicts).toList - (_, proj) <- projectsCache.get((dep.moduleVersion)).toSeq - trDep <- finalDependencies(dep, proj).filter(filter getOrElse defaultFilter) + trDep <- finalDependencies0(dep) } yield trDep /** @@ -436,8 +446,7 @@ object Resolver { val trDepsSeq = for { dep <- updatedDeps - (_, proj) <- projectsCache.get((dep.moduleVersion)).toList - trDep <- finalDependencies(dep, proj).filter(filter getOrElse defaultFilter) + trDep <- finalDependencies0(dep) } yield key(trDep) -> (key(dep), trDep.exclusions) val knownDeps = (updatedDeps ++ updatedConflicts).map(key).toSet From 57cf737852b2a83cf23c1c20f83585ebd77c844f Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sat, 20 Jun 2015 15:02:35 +0100 Subject: [PATCH 4/6] Fix Fixes resolutions wrongly reported as not done. Happened for org.apache.spark:spark-core_2.11:1.3.1 in particular. --- core/src/main/scala/coursier/core/Resolver.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/coursier/core/Resolver.scala b/core/src/main/scala/coursier/core/Resolver.scala index d39aabe33..7bdb6bbaf 100644 --- a/core/src/main/scala/coursier/core/Resolver.scala +++ b/core/src/main/scala/coursier/core/Resolver.scala @@ -403,6 +403,9 @@ object Resolver { * The "next" dependency set, made of the current dependencies and their transitive dependencies, * trying to solve version conflicts. Transitive dependencies are calculated with the current cache. * + * May contain dependencies added in previous iterations, but no more required. These are filtered below, see + * @newDependencies. + * * Returns a tuple made of the conflicting dependencies, and all the dependencies. */ def nextDependenciesAndConflicts = { @@ -426,8 +429,8 @@ object Resolver { */ def isDone: Boolean = { def isFixPoint = { - val (nextConflicts, nextDependencies) = nextDependenciesAndConflicts - dependencies == (nextDependencies ++ nextConflicts).toSet && conflicts == nextConflicts.toSet + val (nextConflicts, _) = nextDependenciesAndConflicts + dependencies == (newDependencies ++ nextConflicts) && conflicts == nextConflicts.toSet } missingFromCache.isEmpty && isFixPoint From 76c4b96bc16cf35faccd610830c79352bc6d4486 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sat, 20 Jun 2015 15:02:36 +0100 Subject: [PATCH 5/6] Cleaning --- core/src/main/scala/coursier/core/Resolver.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/coursier/core/Resolver.scala b/core/src/main/scala/coursier/core/Resolver.scala index 7bdb6bbaf..1b2d41756 100644 --- a/core/src/main/scala/coursier/core/Resolver.scala +++ b/core/src/main/scala/coursier/core/Resolver.scala @@ -416,8 +416,8 @@ object Resolver { * The modules we miss some info about. */ def missingFromCache: Set[ModuleVersion] = { - val modules = dependencies.map(dep => (dep.moduleVersion)) - val nextModules = nextDependenciesAndConflicts._2.map(dep => (dep.moduleVersion)) + val modules = dependencies.map(_.moduleVersion) + val nextModules = nextDependenciesAndConflicts._2.map(_.moduleVersion) (modules ++ nextModules) .filterNot(mod => projectsCache.contains(mod) || errors.contains(mod)) From d1249b02239132d983dca93eaf57551ac2a81ccf Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sat, 20 Jun 2015 15:02:37 +0100 Subject: [PATCH 6/6] Add comments / cleaning --- core/src/main/scala/coursier/core/Definitions.scala | 6 ++++-- core/src/main/scala/coursier/core/Resolver.scala | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/coursier/core/Definitions.scala b/core/src/main/scala/coursier/core/Definitions.scala index cffbb5fb2..3ecfaf01f 100644 --- a/core/src/main/scala/coursier/core/Definitions.scala +++ b/core/src/main/scala/coursier/core/Definitions.scala @@ -1,13 +1,15 @@ package coursier.core /** - * Identify a "module". + * Identifies a "module". * * During resolution, all dependencies having the same module * will be given the same version, if there are no version conflicts * between them. * - * Ivy attributes would land here, if support for Ivy is added. + * Using the same terminology as Ivy. + * + * Ivy attributes would land here, if support for it is added. */ case class Module(organization: String, name: String) { diff --git a/core/src/main/scala/coursier/core/Resolver.scala b/core/src/main/scala/coursier/core/Resolver.scala index 1b2d41756..dbede60c0 100644 --- a/core/src/main/scala/coursier/core/Resolver.scala +++ b/core/src/main/scala/coursier/core/Resolver.scala @@ -17,8 +17,8 @@ object Resolver { * Look at `repositories` from the left, one-by-one, and stop at first success. * Else, return all errors, in the same order. * - * The `module` field of the returned `Project` in case of success may not be - * equal to `module`, in case the version of the latter is not a specific + * The `version` field of the returned `Project` in case of success may not be + * equal to the provided one, in case the latter is not a specific * version (e.g. version interval). Which version get chosen depends on * the repository implementation. */ @@ -297,7 +297,7 @@ object Resolver { else dep /** - * Filters `deps` with `exclusions`. + * Filters `dependencies` with `exclusions`. */ def withExclusions(dependencies: Seq[Dependency], exclusions: Set[(String, String)]): Seq[Dependency] = { @@ -543,7 +543,7 @@ object Resolver { val modules = (project.dependencies ++ profileDependencies) - .collect{ case dep if dep.scope == Scope.Import => (dep.moduleVersion) } ++ + .collect{ case dep if dep.scope == Scope.Import => dep.moduleVersion } ++ project.parent modules.toSet