Fixes #1639. Fixes cached resolution interacting with force()

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.
This commit is contained in:
Eugene Yokota 2014-10-09 13:11:13 -04:00
parent 54032ddd5f
commit 09bca754b5
7 changed files with 63 additions and 14 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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"
}

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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 +