Merge pull request #1686 from sbt/wip/fix-1684

Fixes #1684. cached resolution applies overrides on conflict
This commit is contained in:
Josh Suereth 2014-10-23 07:35:52 -04:00
commit 2ce85b2dcd
3 changed files with 93 additions and 26 deletions

View File

@ -12,9 +12,11 @@ import core.resolve._
import core.module.id.{ ModuleRevisionId, ModuleId => IvyModuleId }
import core.report.{ ResolveReport, ConfigurationResolveReport, DownloadReport }
import core.module.descriptor.{ DefaultModuleDescriptor, ModuleDescriptor, DependencyDescriptor, Configuration => IvyConfiguration, ExcludeRule, IncludeRule }
import core.module.descriptor.OverrideDependencyDescriptorMediator
import core.{ IvyPatternHelper, LogOptions }
import org.apache.ivy.util.Message
import org.apache.ivy.plugins.latest.{ ArtifactInfo => IvyArtifactInfo }
import org.apache.ivy.plugins.matcher.{ MapMatcher, PatternMatcher }
private[sbt] object CachedResolutionResolveCache {
def createID(organization: String, name: String, revision: String) =
@ -58,9 +60,10 @@ private[sbt] class CachedResolutionResolveCache() {
} getOrElse Vector(dep)
val expanded = directDependencies(md0) flatMap expandInternalDeps
val rootModuleConfigs = md0.getConfigurations.toVector
expanded map { buildArtificialModuleDescriptor(_, rootModuleConfigs, prOpt) }
expanded map { buildArtificialModuleDescriptor(_, rootModuleConfigs, md0, prOpt) }
}
def buildArtificialModuleDescriptor(dd: DependencyDescriptor, rootModuleConfigs: Vector[IvyConfiguration], prOpt: Option[ProjectResolver]): (DefaultModuleDescriptor, Boolean) =
def buildArtificialModuleDescriptor(dd: DependencyDescriptor, rootModuleConfigs: Vector[IvyConfiguration], parent: ModuleDescriptor, prOpt: Option[ProjectResolver]): (DefaultModuleDescriptor, Boolean) =
{
def excludeRuleString(rule: ExcludeRule): String =
s"""Exclude(${rule.getId},${rule.getConfigurations.mkString(",")},${rule.getMatcher})"""
@ -82,8 +85,10 @@ private[sbt] class CachedResolutionResolveCache() {
case rules => Some(conf + "->(" + (rules map includeRuleString).mkString(",") + ")")
}
})
val os = extractOverrides(parent)
val moduleLevel = s"""dependencyOverrides=${os.mkString(",")}"""
val depsString = s"""$mrid;${confMap.mkString(",")};isForce=${dd.isForce};isChanging=${dd.isChanging};isTransitive=${dd.isTransitive};""" +
s"""exclusions=${exclusions.mkString(",")};inclusions=${inclusions.mkString(",")};"""
s"""exclusions=${exclusions.mkString(",")};inclusions=${inclusions.mkString(",")};$moduleLevel;"""
val sha1 = Hash.toHex(Hash(depsString))
val md1 = new DefaultModuleDescriptor(createID(sbtOrgTemp, "temp-resolve-" + sha1, "1.0"), "release", null, false) with ArtificialModuleDescriptor {
def targetModuleRevisionId: ModuleRevisionId = mrid
@ -92,8 +97,25 @@ private[sbt] class CachedResolutionResolveCache() {
conf <- rootModuleConfigs
} yield md1.addConfiguration(conf)
md1.addDependency(dd)
os foreach { ovr =>
md1.addDependencyDescriptorMediator(ovr.moduleId, ovr.pm, ovr.ddm)
}
(md1, IvySbt.isChanging(dd))
}
def extractOverrides(md0: ModuleDescriptor): Vector[IvyOverride] =
{
import scala.collection.JavaConversions._
(md0.getAllDependencyDescriptorMediators.getAllRules).toSeq.toVector sortBy {
case (k, v) =>
k.toString
} collect {
case (k: MapMatcher, v: OverrideDependencyDescriptorMediator) =>
val attr: Map[Any, Any] = k.getAttributes.toMap
val module = IvyModuleId.newInstance(attr(IvyPatternHelper.ORGANISATION_KEY).toString, attr(IvyPatternHelper.MODULE_KEY).toString)
val pm = k.getPatternMatcher
IvyOverride(module, pm, v)
}
}
def getOrElseUpdateMiniGraph(md: ModuleDescriptor, changing0: Boolean, logicalClock: LogicalClock, miniGraphPath: File, cachedDescriptor: File, log: Logger)(f: => Either[ResolveException, UpdateReport]): Either[ResolveException, UpdateReport] =
{
import Path._
@ -206,6 +228,7 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
val cachedDescriptor = getSettings.getResolutionCacheManager.getResolvedIvyFileInCache(md0.getModuleRevisionId)
val cache = cachedResolutionResolveCache
cache.directDependencyCache.remove(md0.getModuleRevisionId)
val os = cache.extractOverrides(md0)
val mds = cache.buildArtificialModuleDescriptors(md0, projectResolver)
def doWork(md: ModuleDescriptor): Either[ResolveException, UpdateReport] =
{
@ -233,7 +256,7 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
doWork(md)
}
}
val uReport = mergeResults(md0, results, missingOk, System.currentTimeMillis - start, log)
val uReport = mergeResults(md0, results, missingOk, System.currentTimeMillis - start, os, log)
val cacheManager = getSettings.getResolutionCacheManager
cacheManager.saveResolvedModuleDescriptor(md0)
val prop0 = ""
@ -241,9 +264,10 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
IO.write(ivyPropertiesInCache0, prop0)
uReport
}
def mergeResults(md0: ModuleDescriptor, results: Vector[Either[ResolveException, UpdateReport]], missingOk: Boolean, resolveTime: Long, log: Logger): Either[ResolveException, UpdateReport] =
def mergeResults(md0: ModuleDescriptor, results: Vector[Either[ResolveException, UpdateReport]], missingOk: Boolean, resolveTime: Long,
os: Vector[IvyOverride], log: Logger): Either[ResolveException, UpdateReport] =
if (!missingOk && (results exists { _.isLeft })) Left(mergeErrors(md0, results collect { case Left(re) => re }, log))
else Right(mergeReports(md0, results collect { case Right(ur) => ur }, resolveTime, log))
else Right(mergeReports(md0, results collect { case Right(ur) => ur }, resolveTime, os, log))
def mergeErrors(md0: ModuleDescriptor, errors: Vector[ResolveException], log: Logger): ResolveException =
{
val messages = errors flatMap { _.messages }
@ -257,7 +281,7 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
}
new ResolveException(messages, failed, ListMap(failedPaths: _*))
}
def mergeReports(md0: ModuleDescriptor, reports: Vector[UpdateReport], resolveTime: Long, log: Logger): UpdateReport =
def mergeReports(md0: ModuleDescriptor, reports: Vector[UpdateReport], resolveTime: Long, os: Vector[IvyOverride], log: Logger): UpdateReport =
{
val cachedDescriptor = getSettings.getResolutionCacheManager.getResolvedIvyFileInCache(md0.getModuleRevisionId)
val rootModuleConfigs = md0.getConfigurations.toVector
@ -265,14 +289,14 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
val stats = new UpdateStats(resolveTime, (cachedReports map { _.stats.downloadTime }).sum, (cachedReports map { _.stats.downloadSize }).sum, false)
val configReports = rootModuleConfigs map { conf =>
val crs = reports flatMap { _.configurations filter { _.configuration == conf.getName } }
mergeConfigurationReports(conf.getName, crs, log)
mergeConfigurationReports(conf.getName, crs, os, log)
}
new UpdateReport(cachedDescriptor, configReports, stats, Map.empty)
}
def mergeConfigurationReports(rootModuleConf: String, reports: Vector[ConfigurationReport], log: Logger): ConfigurationReport =
def mergeConfigurationReports(rootModuleConf: String, reports: Vector[ConfigurationReport], os: Vector[IvyOverride], log: Logger): ConfigurationReport =
{
// get the details right, and the rest could be derived
val details = mergeOrganizationArtifactReports(rootModuleConf, reports flatMap { _.details }, log)
val details = mergeOrganizationArtifactReports(rootModuleConf, reports flatMap { _.details }, os, log)
val modules = details flatMap {
_.modules filter { mr =>
!mr.evicted && mr.problem.isEmpty
@ -285,13 +309,13 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
} map { _.module }
new ConfigurationReport(rootModuleConf, modules, details, evicted)
}
def mergeOrganizationArtifactReports(rootModuleConf: String, reports0: Vector[OrganizationArtifactReport], log: Logger): Vector[OrganizationArtifactReport] =
def mergeOrganizationArtifactReports(rootModuleConf: String, reports0: Vector[OrganizationArtifactReport], os: Vector[IvyOverride], log: Logger): Vector[OrganizationArtifactReport] =
(reports0 groupBy { oar => (oar.organization, oar.name) }).toSeq.toVector flatMap {
case ((org, name), xs) =>
if (xs.size < 2) xs
else Vector(new OrganizationArtifactReport(org, name, mergeModuleReports(rootModuleConf, xs flatMap { _.modules }, log)))
else Vector(new OrganizationArtifactReport(org, name, mergeModuleReports(rootModuleConf, xs flatMap { _.modules }, os, log)))
}
def mergeModuleReports(rootModuleConf: String, modules: Vector[ModuleReport], log: Logger): Vector[ModuleReport] =
def mergeModuleReports(rootModuleConf: String, modules: Vector[ModuleReport], os: Vector[IvyOverride], log: Logger): Vector[ModuleReport] =
{
val merged = (modules groupBy { m => (m.module.organization, m.module.name, m.module.revision) }).toSeq.toVector flatMap {
case ((org, name, version), xs) =>
@ -300,12 +324,12 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
}
val conflicts = merged filter { m => !m.evicted && m.problem.isEmpty }
if (conflicts.size < 2) merged
else resolveConflict(rootModuleConf, conflicts, log) match {
else resolveConflict(rootModuleConf, conflicts, os, log) match {
case (survivor, evicted) =>
survivor ++ evicted ++ (merged filter { m => m.evicted || m.problem.isDefined })
}
}
def resolveConflict(rootModuleConf: String, conflicts: Vector[ModuleReport], log: Logger): (Vector[ModuleReport], Vector[ModuleReport]) =
def resolveConflict(rootModuleConf: String, conflicts: Vector[ModuleReport], os: Vector[IvyOverride], log: Logger): (Vector[ModuleReport], Vector[ModuleReport]) =
{
import org.apache.ivy.plugins.conflict.{ NoConflictManager, StrictConflictManager, LatestConflictManager }
val head = conflicts.head
@ -319,7 +343,7 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
m.callers.exists { _.isForceDependency }
})) match {
case Some(m) =>
log.debug(s"- forced dependency: $m")
log.debug(s"- forced dependency: $m ${m.callers}")
(Vector(m), conflicts filterNot { _ == m } map { _.copy(evicted = true, evictedReason = Some(lcm.toString)) }, lcm.toString)
case None =>
val strategy = lcm.getStrategy
@ -331,13 +355,26 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
}
}
def doResolveConflict: (Vector[ModuleReport], Vector[ModuleReport], String) =
getSettings.getConflictManager(IvyModuleId.newInstance(organization, name)) match {
case ncm: NoConflictManager => (conflicts, Vector(), ncm.toString)
case _: StrictConflictManager => sys.error((s"conflict was found in $rootModuleConf:$organization:$name " + (conflicts map { _.module }).mkString("(", ", ", ")")))
case lcm: LatestConflictManager => useLatest(lcm)
case conflictManager => sys.error(s"Unsupported conflict manager $conflictManager")
os find { ovr => ovr.moduleId.getOrganisation == organization && ovr.moduleId.getName == name } match {
case Some(ovr) if Option(ovr.ddm.getVersion).isDefined =>
val ovrVersion = ovr.ddm.getVersion
conflicts find { mr =>
mr.module.revision == ovrVersion
} match {
case Some(m) =>
(Vector(m), conflicts filterNot { _ == m } map { _.copy(evicted = true, evictedReason = Some("override")) }, "override")
case None =>
sys.error(s"override dependency specifies $ovrVersion but no candidates were found: " + (conflicts map { _.module }).mkString("(", ", ", ")"))
}
case None =>
getSettings.getConflictManager(IvyModuleId.newInstance(organization, name)) match {
case ncm: NoConflictManager => (conflicts, Vector(), ncm.toString)
case _: StrictConflictManager => sys.error((s"conflict was found in $rootModuleConf:$organization:$name " + (conflicts map { _.module }).mkString("(", ", ", ")")))
case lcm: LatestConflictManager => useLatest(lcm)
case conflictManager => sys.error(s"Unsupported conflict manager $conflictManager")
}
}
if (conflicts.size == 2) {
if (conflicts.size == 2 && os.isEmpty) {
val (cf0, cf1) = (conflicts(0).module, conflicts(1).module)
val cache = cachedResolutionResolveCache
cache.getOrElseUpdateConflict(cf0, cf1, conflicts) { doResolveConflict }
@ -345,7 +382,6 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
val (surviving, evicted, mgr) = doResolveConflict
(surviving, evicted)
}
}
}
@ -353,3 +389,6 @@ private[sbt] case class ModuleReportArtifactInfo(moduleReport: ModuleReport) ext
override def getLastModified: Long = moduleReport.publicationDate map { _.getTime } getOrElse 0L
override def getRevision: String = moduleReport.module.revision
}
private[sbt] case class IvyOverride(moduleId: IvyModuleId, pm: PatternMatcher, ddm: OverrideDependencyDescriptorMediator) {
override def toString: String = s"""IvyOverride($moduleId,$pm,${ddm.getVersion},${ddm.getBranch})"""
}

View File

@ -1,4 +1,5 @@
lazy val check = taskKey[Unit]("Runs the check")
lazy val check2 = taskKey[Unit]("Runs the check")
def commonSettings: Seq[Def.Setting[_]] =
Seq(
@ -11,7 +12,6 @@ def commonSettings: Seq[Def.Setting[_]] =
"commons-io" % "commons-io" % "1.3",
"com.typesafe" % "config" % "0.4.9-SNAPSHOT"
),
// dependencyOverrides += "commons-io" % "commons-io" % "1.4",
scalaVersion := "2.10.4",
resolvers += Resolver.sonatypeRepo("snapshots")
)
@ -37,6 +37,20 @@ lazy val c = project.
// libraryDependencies := Seq(organization.value %% "a" % version.value)
)
// overrides cached
lazy val d = project.
settings(consolidatedResolutionSettings: _*).
settings(
dependencyOverrides += "commons-io" % "commons-io" % "2.0"
)
// overrides plain
lazy val e = project.
settings(commonSettings: _*).
settings(
dependencyOverrides += "commons-io" % "commons-io" % "2.0"
)
lazy val root = (project in file(".")).
settings(
organization in ThisBuild := "org.example",
@ -47,8 +61,16 @@ lazy val root = (project in file(".")).
val ccp = (externalDependencyClasspath in Compile in c).value.sortBy {_.data.getName} filterNot {_.data.getName == "demo_2.10.jar"}
if (acp == bcp && acp == ccp) ()
else sys.error("Different classpaths are found:" +
"\n - a (consolidated) " + acp.toString +
"\n - a (cached) " + acp.toString +
"\n - b (plain) " + bcp.toString +
"\n - c (inter-project) " + ccp.toString)
},
check2 := {
val dcp = (externalDependencyClasspath in Compile in d).value.sortBy {_.data.getName}
val ecp = (externalDependencyClasspath in Compile in e).value.sortBy {_.data.getName}
if (dcp == ecp) ()
else sys.error("Different classpaths are found:" +
"\n - d (overrides + cached) " + dcp.toString +
"\n - e (overrides + plain) " + ecp.toString)
}
)

View File

@ -4,6 +4,12 @@
> check
> clean
> a/clean
> b/clean
> c/clean
> check
> check2