From 829c397ca8d89e21fa00b89274802ac324b0d7e2 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sun, 31 Jan 2016 21:06:06 +0100 Subject: [PATCH] Keep calculated dependencies accross resolutions --- .../main/scala/coursier/core/Resolution.scala | 64 +++++++++++++------ .../coursier/core/ResolutionProcess.scala | 4 +- .../src/main/scala/coursier/package.scala | 2 + .../scala/coursier/test/CentralTests.scala | 8 +-- .../scala/coursier/test/ResolutionTests.scala | 36 +++++------ .../test/scala/coursier/test/package.scala | 18 ++++++ 6 files changed, 88 insertions(+), 44 deletions(-) diff --git a/core/shared/src/main/scala/coursier/core/Resolution.scala b/core/shared/src/main/scala/coursier/core/Resolution.scala index ee7971f6d..1e3b91507 100644 --- a/core/shared/src/main/scala/coursier/core/Resolution.scala +++ b/core/shared/src/main/scala/coursier/core/Resolution.scala @@ -1,9 +1,10 @@ package coursier.core +import java.util.concurrent.ConcurrentHashMap import java.util.regex.Pattern.quote import scala.annotation.tailrec -import scala.collection.mutable +import scala.collection.JavaConverters._ import scalaz.{ \/-, -\/ } object Resolution { @@ -431,28 +432,53 @@ final case class Resolution( conflicts: Set[Dependency], projectCache: Map[Resolution.ModuleVersion, (Artifact.Source, Project)], errorCache: Map[Resolution.ModuleVersion, Seq[String]], + finalDependenciesCache: Map[Dependency, Seq[Dependency]], filter: Option[Dependency => Boolean], profileActivation: Option[(String, Activation, Map[String, String]) => Boolean] ) { + def copyWithCache( + rootDependencies: Set[Dependency] = rootDependencies, + dependencies: Set[Dependency] = dependencies, + forceVersions: Map[Module, String] = forceVersions, + conflicts: Set[Dependency] = conflicts, + projectCache: Map[Resolution.ModuleVersion, (Artifact.Source, Project)] = projectCache, + errorCache: Map[Resolution.ModuleVersion, Seq[String]] = errorCache, + filter: Option[Dependency => Boolean] = filter, + profileActivation: Option[(String, Activation, Map[String, String]) => Boolean] = profileActivation + ): Resolution = + copy( + rootDependencies, + dependencies, + forceVersions, + conflicts, + projectCache, + errorCache, + finalDependenciesCache ++ finalDependenciesCache0.asScala, + filter, + profileActivation + ) + import Resolution._ - private val finalDependenciesCache = - new mutable.HashMap[Dependency, Seq[Dependency]]() - private def finalDependencies0(dep: Dependency) = - finalDependenciesCache.synchronized { - finalDependenciesCache.getOrElseUpdate(dep, { - if (dep.transitive) - projectCache.get(dep.moduleVersion) match { - case Some((_, proj)) => - finalDependencies(dep, proj) - .filter(filter getOrElse defaultFilter) - case None => Nil - } - else - Nil - }) - } + private[core] val finalDependenciesCache0 = new ConcurrentHashMap[Dependency, Seq[Dependency]] + + private def finalDependencies0(dep: Dependency): Seq[Dependency] = + if (dep.transitive) { + val deps = finalDependenciesCache.getOrElse(dep, finalDependenciesCache0.get(dep)) + + if (deps == null) + projectCache.get(dep.moduleVersion) match { + case Some((_, proj)) => + val res = finalDependencies(dep, proj).filter(filter getOrElse defaultFilter) + finalDependenciesCache0.put(dep, res) + res + case None => Nil + } + else + deps + } else + Nil /** * Transitive dependencies of the current dependencies, according to @@ -592,7 +618,7 @@ final case class Resolution( private def nextNoMissingUnsafe: Resolution = { val (newConflicts, _, _) = nextDependenciesAndConflicts - copy( + copyWithCache( dependencies = newDependencies ++ newConflicts, conflicts = newConflicts.toSet ) @@ -856,7 +882,7 @@ final case class Resolution( newDeps } - copy( + copyWithCache( rootDependencies = dependencies, dependencies = helper(dependencies.map(updateVersion)) // don't know if something should be done about conflicts diff --git a/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala b/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala index af80981c4..c26d4608d 100644 --- a/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala +++ b/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala @@ -74,7 +74,7 @@ final case class Missing( def cont0(res: Resolution) = { val res0 = successes.foldLeft(res){case (acc, (modVer, (source, proj))) => - acc.copy(projectCache = acc.projectCache + ( + acc.copyWithCache(projectCache = acc.projectCache + ( modVer -> (source, acc.withDependencyManagement(proj)) )) } @@ -83,7 +83,7 @@ final case class Missing( } val current0 = current - .copy(errorCache = current.errorCache ++ errors) + .copyWithCache(errorCache = current.errorCache ++ errors) if (depMgmtMissing.isEmpty) cont0(current0) diff --git a/core/shared/src/main/scala/coursier/package.scala b/core/shared/src/main/scala/coursier/package.scala index d15ba3985..15ab6efb4 100644 --- a/core/shared/src/main/scala/coursier/package.scala +++ b/core/shared/src/main/scala/coursier/package.scala @@ -70,6 +70,7 @@ package object coursier { conflicts: Set[Dependency] = Set.empty, projectCache: Map[ModuleVersion, (Artifact.Source, Project)] = Map.empty, errorCache: Map[ModuleVersion, Seq[String]] = Map.empty, + finalDependencies: Map[Dependency, Seq[Dependency]] = Map.empty, filter: Option[Dependency => Boolean] = None, profileActivation: Option[(String, core.Activation, Map[String, String]) => Boolean] = None ): Resolution = @@ -80,6 +81,7 @@ package object coursier { conflicts, projectCache, errorCache, + finalDependencies, filter, profileActivation ) diff --git a/tests/shared/src/test/scala/coursier/test/CentralTests.scala b/tests/shared/src/test/scala/coursier/test/CentralTests.scala index db05027ee..76ef8b9ac 100644 --- a/tests/shared/src/test/scala/coursier/test/CentralTests.scala +++ b/tests/shared/src/test/scala/coursier/test/CentralTests.scala @@ -90,8 +90,7 @@ object CentralTests extends TestSuite { 'logback{ async { val dep = Dependency(Module("ch.qos.logback", "logback-classic"), "1.1.3") - val res = await(resolve(Set(dep))) - .copy(projectCache = Map.empty, errorCache = Map.empty) // No validating these here + val res = await(resolve(Set(dep))).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -106,8 +105,7 @@ object CentralTests extends TestSuite { 'asm{ async { val dep = Dependency(Module("org.ow2.asm", "asm-commons"), "5.0.2") - val res = await(resolve(Set(dep))) - .copy(projectCache = Map.empty, errorCache = Map.empty) // No validating these here + val res = await(resolve(Set(dep))).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -123,7 +121,7 @@ object CentralTests extends TestSuite { async { val dep = Dependency(Module("joda-time", "joda-time"), "[2.2,2.8]") val res0 = await(resolve(Set(dep))) - val res = res0.copy(projectCache = Map.empty, errorCache = Map.empty) + val res = res0.clearCaches val expected = Resolution( rootDependencies = Set(dep), diff --git a/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala b/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala index c83a9ddd5..a47c54967 100644 --- a/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala +++ b/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala @@ -203,7 +203,7 @@ object ResolutionTests extends TestSuite { val dep = Dependency(Module("acme", "config"), "1.3.0") val res = await(resolve0( Set(dep) - )) + )).clearFinalDependenciesCache val expected = Resolution( rootDependencies = Set(dep), @@ -220,7 +220,7 @@ object ResolutionTests extends TestSuite { val trDep = Dependency(Module("acme", "play-json"), "2.4.0") val res = await(resolve0( Set(dep) - )) + )).clearFinalDependenciesCache val expected = Resolution( rootDependencies = Set(dep), @@ -243,7 +243,7 @@ object ResolutionTests extends TestSuite { ) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -264,7 +264,7 @@ object ResolutionTests extends TestSuite { ) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -285,7 +285,7 @@ object ResolutionTests extends TestSuite { ) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -300,7 +300,7 @@ object ResolutionTests extends TestSuite { val dep = Dependency(Module("hudsucker", "mail"), "10.0") val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -319,7 +319,7 @@ object ResolutionTests extends TestSuite { ) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -337,7 +337,7 @@ object ResolutionTests extends TestSuite { Dependency(Module("org.gnome", "desktop"), "7.0")) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -354,7 +354,7 @@ object ResolutionTests extends TestSuite { Dependency(Module("gov.nsa", "secure-pgp"), "10.0", exclusions = Set(("*", "crypto")))) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -369,7 +369,7 @@ object ResolutionTests extends TestSuite { val dep = Dependency(Module("com.thoughtworks.paranamer", "paranamer"), "2.6") val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -386,7 +386,7 @@ object ResolutionTests extends TestSuite { Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -405,7 +405,7 @@ object ResolutionTests extends TestSuite { Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -426,7 +426,7 @@ object ResolutionTests extends TestSuite { Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) val res = await(resolve0( Set(dep) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = Set(dep), @@ -447,7 +447,7 @@ object ResolutionTests extends TestSuite { val res = await(resolve0( Set(dep), filter = Some(_ => true) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches.clearFilter val expected = Resolution( rootDependencies = Set(dep), @@ -470,7 +470,7 @@ object ResolutionTests extends TestSuite { val res = await(resolve0( deps, filter = Some(_ => true) - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches.clearFilter val expected = Resolution( rootDependencies = deps, @@ -492,7 +492,7 @@ object ResolutionTests extends TestSuite { val res = await(resolve0( deps, forceVersions = depOverrides - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = deps, @@ -516,7 +516,7 @@ object ResolutionTests extends TestSuite { val res = await(resolve0( deps, forceVersions = depOverrides - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = deps, @@ -542,7 +542,7 @@ object ResolutionTests extends TestSuite { val res = await(resolve0( deps, forceVersions = depOverrides - )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + )).clearCaches val expected = Resolution( rootDependencies = deps, diff --git a/tests/shared/src/test/scala/coursier/test/package.scala b/tests/shared/src/test/scala/coursier/test/package.scala index 30fa8f3ea..7fe13dce0 100644 --- a/tests/shared/src/test/scala/coursier/test/package.scala +++ b/tests/shared/src/test/scala/coursier/test/package.scala @@ -6,6 +6,24 @@ package object test { def withCompileScope: Dependency = underlying.copy(configuration = "compile") } + implicit class ResolutionOps(val underlying: Resolution) extends AnyVal { + + // The content of these fields is typically not validated in the tests. + // It can be cleared with these method to it easier to compare `underlying` + // to an expected value. + + def clearFinalDependenciesCache: Resolution = + underlying.copy(finalDependenciesCache = Map.empty) + def clearCaches: Resolution = + underlying.copy( + projectCache = Map.empty, + errorCache = Map.empty, + finalDependenciesCache = Map.empty + ) + def clearFilter: Resolution = + underlying.copy(filter = None) + } + object Profile { type Activation = core.Activation object Activation {