From 09bca754b5b0a91eb627274638e74fe47c33c3c8 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 9 Oct 2014 13:11:13 -0400 Subject: [PATCH] Fixes #1639. Fixes cached resolution interacting with force() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When conflicts are found for a given module, a forced one is selected before conflict manager kicks in. The problem is that DependencyDescriptor seems to mark transitive forced dependency as forced as well, so the actual forced dependency are sometimes not prioritized. To work around this, I’ve introduced a mixin called SbtDefaultDependencyDescriptor, which carries around ModuleID to detect direct dependencies. --- ivy/src/main/scala/sbt/Ivy.scala | 6 ++-- ivy/src/main/scala/sbt/IvyRetrieve.scala | 12 ++++--- ivy/src/main/scala/sbt/UpdateReport.scala | 3 +- .../CachedResolutionResolveEngine.scala | 11 +++++-- .../SbtDefaultDependencyDescriptor.scala | 9 ++++++ .../actions/src/main/scala/sbt/CacheIvy.scala | 4 +-- .../cached-resolution2/multi.sbt | 32 +++++++++++++++++-- 7 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 ivy/src/main/scala/sbt/ivyint/SbtDefaultDependencyDescriptor.scala diff --git a/ivy/src/main/scala/sbt/Ivy.scala b/ivy/src/main/scala/sbt/Ivy.scala index 4bf6ffad6..c0e7d508a 100644 --- a/ivy/src/main/scala/sbt/Ivy.scala +++ b/ivy/src/main/scala/sbt/Ivy.scala @@ -4,7 +4,7 @@ package sbt import Resolver.PluginPattern -import ivyint.{ CachedResolutionResolveEngine, CachedResolutionResolveCache } +import ivyint.{ CachedResolutionResolveEngine, CachedResolutionResolveCache, SbtDefaultDependencyDescriptor } import java.io.File import java.net.URI @@ -570,7 +570,9 @@ private[sbt] object IvySbt { /** Transforms an sbt ModuleID into an Ivy DefaultDependencyDescriptor.*/ def convertDependency(moduleID: DefaultModuleDescriptor, dependency: ModuleID, parser: CustomXmlParser.CustomParser): DefaultDependencyDescriptor = { - val dependencyDescriptor = new DefaultDependencyDescriptor(moduleID, toID(dependency), dependency.isForce, dependency.isChanging, dependency.isTransitive) + val dependencyDescriptor = new DefaultDependencyDescriptor(moduleID, toID(dependency), dependency.isForce, dependency.isChanging, dependency.isTransitive) with SbtDefaultDependencyDescriptor { + def dependencyModuleId = dependency + } dependency.configurations match { case None => // The configuration for this dependency was not explicitly specified, so use the default parser.parseDepsConfs(parser.getDefaultConf, dependencyDescriptor) diff --git a/ivy/src/main/scala/sbt/IvyRetrieve.scala b/ivy/src/main/scala/sbt/IvyRetrieve.scala index 1e74f3955..f453efb1f 100644 --- a/ivy/src/main/scala/sbt/IvyRetrieve.scala +++ b/ivy/src/main/scala/sbt/IvyRetrieve.scala @@ -13,6 +13,7 @@ import module.id.{ ModuleRevisionId, ModuleId => IvyModuleId } import report.{ ArtifactDownloadReport, ConfigurationResolveReport, ResolveReport } import resolve.{ IvyNode, IvyNodeCallers } import IvyNodeCallers.{ Caller => IvyCaller } +import ivyint.SbtDefaultDependencyDescriptor object IvyRetrieve { def reports(report: ResolveReport): Seq[ConfigurationResolveReport] = @@ -77,11 +78,14 @@ object IvyRetrieve { case x if nonEmptyString(x).isDefined => x } val ddOpt = Option(caller.getDependencyDescriptor) - val (extraAttributes, isForce, isChanging, isTransitive) = ddOpt match { - case Some(dd) => (toExtraAttributes(dd.getExtraAttributes), dd.isForce, dd.isChanging, dd.isTransitive) - case None => (Map.empty[String, String], false, false, true) + val (extraAttributes, isForce, isChanging, isTransitive, isDirectlyForce) = ddOpt match { + case Some(dd: SbtDefaultDependencyDescriptor) => + val mod = dd.dependencyModuleId + (toExtraAttributes(dd.getExtraAttributes), mod.isForce, mod.isChanging, mod.isTransitive, mod.isForce) + case Some(dd) => (toExtraAttributes(dd.getExtraAttributes), dd.isForce, dd.isChanging, dd.isTransitive, false) + case None => (Map.empty[String, String], false, false, true, false) } - new Caller(m, callerConfigurations, extraAttributes, isForce, isChanging, isTransitive) + new Caller(m, callerConfigurations, extraAttributes, isForce, isChanging, isTransitive, isDirectlyForce) } val revId = dep.getResolvedId val moduleId = toModuleID(revId) diff --git a/ivy/src/main/scala/sbt/UpdateReport.scala b/ivy/src/main/scala/sbt/UpdateReport.scala index 443c84ed3..696fa4d20 100644 --- a/ivy/src/main/scala/sbt/UpdateReport.scala +++ b/ivy/src/main/scala/sbt/UpdateReport.scala @@ -190,7 +190,8 @@ final class Caller( val callerExtraAttributes: Map[String, String], val isForceDependency: Boolean, val isChangingDependency: Boolean, - val isTransitiveDependency: Boolean) { + val isTransitiveDependency: Boolean, + val isDirectlyForceDependency: Boolean) { override def toString: String = s"$caller" } diff --git a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala index 3ee26964e..850f1b61d 100644 --- a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala +++ b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala @@ -36,6 +36,9 @@ private[sbt] class CachedResolutionResolveCache() { val mds = if (mrid0.getOrganisation == sbtOrgTemp) Vector(md0) else buildArtificialModuleDescriptors(md0, prOpt) map { _._1 } + + updateReportCache.remove(md0.getModuleRevisionId) + directDependencyCache.remove(md0.getModuleRevisionId) mds foreach { md => updateReportCache.remove(md.getModuleRevisionId) directDependencyCache.remove(md.getModuleRevisionId) @@ -186,6 +189,7 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine { val miniGraphPath = depDir / "module" val cachedDescriptor = getSettings.getResolutionCacheManager.getResolvedIvyFileInCache(md0.getModuleRevisionId) val cache = cachedResolutionResolveCache + cache.directDependencyCache.remove(md0.getModuleRevisionId) val mds = cache.buildArtificialModuleDescriptors(md0, projectResolver) def doWork(md: ModuleDescriptor): Either[ResolveException, UpdateReport] = { @@ -292,10 +296,13 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine { val name = head.module.name log.debug(s"- conflict in $rootModuleConf:$organization:$name " + (conflicts map { _.module }).mkString("(", ", ", ")")) def useLatest(lcm: LatestConflictManager): (Vector[ModuleReport], Vector[ModuleReport], String) = - conflicts find { m => + (conflicts find { m => + m.callers.exists { _.isDirectlyForceDependency } + } orElse (conflicts find { m => m.callers.exists { _.isForceDependency } - } match { + })) match { case Some(m) => + log.debug(s"- forced dependency: $m") (Vector(m), conflicts filterNot { _ == m } map { _.copy(evicted = true, evictedReason = Some(lcm.toString)) }, lcm.toString) case None => val strategy = lcm.getStrategy diff --git a/ivy/src/main/scala/sbt/ivyint/SbtDefaultDependencyDescriptor.scala b/ivy/src/main/scala/sbt/ivyint/SbtDefaultDependencyDescriptor.scala new file mode 100644 index 000000000..0a3338fac --- /dev/null +++ b/ivy/src/main/scala/sbt/ivyint/SbtDefaultDependencyDescriptor.scala @@ -0,0 +1,9 @@ +package sbt +package ivyint + +import org.apache.ivy.core +import core.module.descriptor.DefaultDependencyDescriptor + +trait SbtDefaultDependencyDescriptor { self: DefaultDependencyDescriptor => + def dependencyModuleId: ModuleID +} diff --git a/main/actions/src/main/scala/sbt/CacheIvy.scala b/main/actions/src/main/scala/sbt/CacheIvy.scala index 17e260450..91e82107e 100644 --- a/main/actions/src/main/scala/sbt/CacheIvy.scala +++ b/main/actions/src/main/scala/sbt/CacheIvy.scala @@ -81,8 +81,8 @@ object CacheIvy { implicit def organizationArtifactReportFormat(implicit sf: Format[String], bf: Format[Boolean], df: Format[Seq[ModuleReport]]): Format[OrganizationArtifactReport] = wrap[OrganizationArtifactReport, (String, String, Seq[ModuleReport])](m => (m.organization, m.name, m.modules), { case (o, n, r) => OrganizationArtifactReport(o, n, r) }) implicit def callerFormat: Format[Caller] = - wrap[Caller, (ModuleID, Seq[String], Map[String, String], Boolean, Boolean, Boolean)](c => (c.caller, c.callerConfigurations, c.callerExtraAttributes, c.isForceDependency, c.isChangingDependency, c.isTransitiveDependency), - { case (c, cc, ea, fd, cd, td) => new Caller(c, cc, ea, fd, cd, td) }) + wrap[Caller, (ModuleID, Seq[String], Map[String, String], Boolean, Boolean, Boolean, Boolean)](c => (c.caller, c.callerConfigurations, c.callerExtraAttributes, c.isForceDependency, c.isChangingDependency, c.isTransitiveDependency, c.isDirectlyForceDependency), + { case (c, cc, ea, fd, cd, td, df) => new Caller(c, cc, ea, fd, cd, td, df) }) implicit def exclusionRuleFormat(implicit sf: Format[String]): Format[ExclusionRule] = wrap[ExclusionRule, (String, String, String, Seq[String])](e => (e.organization, e.name, e.artifact, e.configurations), { case (o, n, a, cs) => ExclusionRule(o, n, a, cs) }) implicit def crossVersionFormat: Format[CrossVersion] = wrap(crossToInt, crossFromInt) diff --git a/sbt/src/sbt-test/dependency-management/cached-resolution2/multi.sbt b/sbt/src/sbt-test/dependency-management/cached-resolution2/multi.sbt index e106a7693..a3c3d5a87 100644 --- a/sbt/src/sbt-test/dependency-management/cached-resolution2/multi.sbt +++ b/sbt/src/sbt-test/dependency-management/cached-resolution2/multi.sbt @@ -5,8 +5,10 @@ def commonSettings: Seq[Def.Setting[_]] = ivyPaths := new IvyPaths( (baseDirectory in ThisBuild).value, Some((baseDirectory in LocalRootProject).value / "ivy-cache")), dependencyCacheDirectory := (baseDirectory in LocalRootProject).value / "dependency", libraryDependencies := Seq( - "org.springframework" % "spring-core" % "3.2.2.RELEASE" force() exclude("org.springframework", "spring-asm"), - "org.springframework" % "spring-context" % "4.0.3.RELEASE" exclude("org.springframework", "spring-asm") + "org.springframework" % "spring-core" % "3.2.2.RELEASE" force() exclude("org.springframework", "spring-asm"), + "org.springframework" % "spring-tx" % "3.1.2.RELEASE" force() exclude("org.springframework", "spring-asm"), + "org.springframework" % "spring-beans" % "3.2.2.RELEASE" force() exclude("org.springframework", "spring-asm"), + "org.springframework" % "spring-context" % "3.1.2.RELEASE" force() exclude("org.springframework", "spring-asm") ), scalaVersion := "2.10.4", resolvers += Resolver.sonatypeRepo("snapshots") @@ -25,7 +27,12 @@ lazy val b = project. lazy val c = project. dependsOn(a). - settings(cachedResolutionSettings: _*) + settings(cachedResolutionSettings: _*). + settings( + libraryDependencies := Seq( + "org.springframework" % "spring-core" % "4.0.3.RELEASE" exclude("org.springframework", "spring-asm") + ) + ) lazy val root = (project in file(".")). aggregate(a, b, c). @@ -37,6 +44,25 @@ lazy val root = (project in file(".")). val acp = (externalDependencyClasspath in Compile in a).value.sortBy {_.data.getName} val bcp = (externalDependencyClasspath in Compile in b).value.sortBy {_.data.getName} val ccp = (externalDependencyClasspath in Compile in c).value.sortBy {_.data.getName} + + if (!(acp exists {_.data.getName contains "spring-core-3.2.2.RELEASE"})) { + sys.error("spring-core-3.2.2 is not found") + } + if (!(bcp exists {_.data.getName contains "spring-core-3.2.2.RELEASE"})) { + sys.error("spring-core-3.2.2 is not found") + } + if (!(ccp exists {_.data.getName contains "spring-core-3.2.2.RELEASE"})) { + sys.error("spring-core-3.2.2 is not found") + } + if (!(acp exists {_.data.getName contains "spring-tx-3.1.2.RELEASE"})) { + sys.error("spring-tx-3.1.2 is not found") + } + if (!(bcp exists {_.data.getName contains "spring-tx-3.1.2.RELEASE"})) { + sys.error("spring-tx-3.1.2 is not found") + } + if (!(ccp exists {_.data.getName contains "spring-tx-3.1.2.RELEASE"})) { + sys.error("spring-tx-3.1.2 is not found") + } if (acp == bcp && acp == ccp) () else sys.error("Different classpaths are found:" + "\n - a (consolidated) " + acp.toString +