mirror of https://github.com/sbt/sbt.git
Merge pull request #2129 from sbt/wip/cached-resolution-circular
[0.13.9-RC3] cached resolution: stack overflow when circular dependency is found
This commit is contained in:
commit
c693ac6fb4
|
|
@ -396,58 +396,74 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
|
||||||
def mergeOrganizationArtifactReports(rootModuleConf: String, reports0: Vector[OrganizationArtifactReport], os: Vector[IvyOverride], log: Logger): Vector[OrganizationArtifactReport] =
|
def mergeOrganizationArtifactReports(rootModuleConf: String, reports0: Vector[OrganizationArtifactReport], os: Vector[IvyOverride], log: Logger): Vector[OrganizationArtifactReport] =
|
||||||
{
|
{
|
||||||
// group by takes up too much memory. trading space with time.
|
// group by takes up too much memory. trading space with time.
|
||||||
val orgNamePairs = (reports0 map { oar => (oar.organization, oar.name) }).distinct
|
val orgNamePairs: Vector[(String, String)] = (reports0 map { oar => (oar.organization, oar.name) }).distinct
|
||||||
// this might take up some memory, but it's limited to a single
|
// this might take up some memory, but it's limited to a single
|
||||||
val reports1 = reports0 map { filterOutCallers }
|
val reports1 = reports0 map { filterOutCallers }
|
||||||
val allModules: ListMap[(String, String), Vector[OrganizationArtifactReport]] =
|
val allModules0: Map[(String, String), Vector[OrganizationArtifactReport]] =
|
||||||
ListMap(orgNamePairs map {
|
Map(orgNamePairs map {
|
||||||
case (organization, name) =>
|
case (organization, name) =>
|
||||||
val xs = reports1 filter { oar => oar.organization == organization && oar.name == name }
|
val xs = reports1 filter { oar => oar.organization == organization && oar.name == name }
|
||||||
((organization, name), xs)
|
((organization, name), xs)
|
||||||
}: _*)
|
}: _*)
|
||||||
val stackGuard = reports0.size * reports0.size * 2
|
|
||||||
// sort the all modules such that less called modules comes earlier
|
// sort the all modules such that less called modules comes earlier
|
||||||
def sortModules(cs: ListMap[(String, String), Vector[OrganizationArtifactReport]],
|
@tailrec def sortModules(cs: Vector[(String, String)],
|
||||||
n: Int): ListMap[(String, String), Vector[OrganizationArtifactReport]] =
|
acc: Vector[(String, String)], extra: Vector[(String, String)],
|
||||||
|
n: Int, guard: Int): Vector[(String, String)] =
|
||||||
{
|
{
|
||||||
val keys = cs.keySet
|
// println(s"sortModules: $n / $guard")
|
||||||
val (called, notCalled) = cs partition {
|
val keys = cs.toSet
|
||||||
case (k, oas) =>
|
val (called, notCalled) = cs partition { k =>
|
||||||
oas exists {
|
val reports = allModules0(k)
|
||||||
_.modules.exists {
|
reports exists {
|
||||||
_.callers exists { caller =>
|
_.modules.exists {
|
||||||
val m = caller.caller
|
_.callers exists { caller =>
|
||||||
keys((m.organization, m.name))
|
val m = caller.caller
|
||||||
}
|
keys((m.organization, m.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
notCalled ++
|
lazy val result0 = acc ++ notCalled ++ called ++ extra
|
||||||
(if (called.isEmpty || n > stackGuard) called
|
def warnCircular(): Unit = {
|
||||||
else sortModules(called, n + 1))
|
log.warn(s"""avoid circular dependency while using cached resolution: ${cs.mkString(",")}""")
|
||||||
|
}
|
||||||
|
(if (n > guard) {
|
||||||
|
warnCircular
|
||||||
|
result0
|
||||||
|
} else if (called.isEmpty) result0
|
||||||
|
else if (notCalled.isEmpty) {
|
||||||
|
warnCircular
|
||||||
|
sortModules(cs.tail, acc, extra :+ cs.head, n + 1, guard)
|
||||||
|
} else sortModules(called, acc ++ notCalled, extra, 0, called.size * called.size + 1))
|
||||||
}
|
}
|
||||||
def resolveConflicts(cs: List[((String, String), Vector[OrganizationArtifactReport])]): List[OrganizationArtifactReport] =
|
def resolveConflicts(cs: List[(String, String)],
|
||||||
|
allModules: Map[(String, String), Vector[OrganizationArtifactReport]]): List[OrganizationArtifactReport] =
|
||||||
cs match {
|
cs match {
|
||||||
case Nil => Nil
|
case Nil => Nil
|
||||||
case (k, Vector()) :: rest => resolveConflicts(rest)
|
case (organization, name) :: rest =>
|
||||||
case (k, Vector(oa)) :: rest if (oa.modules.size == 0) => resolveConflicts(rest)
|
val reports = allModules((organization, name))
|
||||||
case (k, Vector(oa)) :: rest if (oa.modules.size == 1 && !oa.modules.head.evicted) =>
|
reports match {
|
||||||
log.debug(s":: no conflict $rootModuleConf: ${oa.organization}:${oa.name}")
|
case Vector() => resolveConflicts(rest, allModules)
|
||||||
oa :: resolveConflicts(rest)
|
case Vector(oa) if (oa.modules.size == 0) => resolveConflicts(rest, allModules)
|
||||||
case ((organization, name), oas) :: rest =>
|
case Vector(oa) if (oa.modules.size == 1 && !oa.modules.head.evicted) =>
|
||||||
(mergeModuleReports(rootModuleConf, oas flatMap { _.modules }, os, log) match {
|
log.debug(s":: no conflict $rootModuleConf: ${oa.organization}:${oa.name}")
|
||||||
case (survivor, newlyEvicted) =>
|
oa :: resolveConflicts(rest, allModules)
|
||||||
val evicted = (survivor ++ newlyEvicted) filter { m => m.evicted }
|
case oas =>
|
||||||
val notEvicted = (survivor ++ newlyEvicted) filter { m => !m.evicted }
|
(mergeModuleReports(rootModuleConf, oas flatMap { _.modules }, os, log) match {
|
||||||
log.debug("::: adds " + (notEvicted map { _.module }).mkString(", "))
|
case (survivor, newlyEvicted) =>
|
||||||
log.debug("::: evicted " + (evicted map { _.module }).mkString(", "))
|
val evicted = (survivor ++ newlyEvicted) filter { m => m.evicted }
|
||||||
val x = new OrganizationArtifactReport(organization, name, survivor ++ newlyEvicted)
|
val notEvicted = (survivor ++ newlyEvicted) filter { m => !m.evicted }
|
||||||
val next = transitivelyEvict(rootModuleConf, rest, evicted, log)
|
log.debug("::: adds " + (notEvicted map { _.module }).mkString(", "))
|
||||||
x :: resolveConflicts(next)
|
log.debug("::: evicted " + (evicted map { _.module }).mkString(", "))
|
||||||
})
|
val x = new OrganizationArtifactReport(organization, name, survivor ++ newlyEvicted)
|
||||||
|
val nextModules = transitivelyEvict(rootModuleConf, rest, allModules, evicted, log)
|
||||||
|
x :: resolveConflicts(rest, nextModules)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val sorted = sortModules(allModules, 0)
|
val guard0 = (orgNamePairs.size * orgNamePairs.size) + 1
|
||||||
val result = resolveConflicts(sorted.toList)
|
val sorted: Vector[(String, String)] = sortModules(orgNamePairs, Vector(), Vector(), 0, guard0)
|
||||||
|
val result = resolveConflicts(sorted.toList, allModules0)
|
||||||
result.toVector
|
result.toVector
|
||||||
}
|
}
|
||||||
def filterOutCallers(report0: OrganizationArtifactReport): OrganizationArtifactReport =
|
def filterOutCallers(report0: OrganizationArtifactReport): OrganizationArtifactReport =
|
||||||
|
|
@ -490,13 +506,15 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
|
||||||
/**
|
/**
|
||||||
* This transitively evicts any non-evicted modules whose only callers are newly evicted.
|
* This transitively evicts any non-evicted modules whose only callers are newly evicted.
|
||||||
*/
|
*/
|
||||||
def transitivelyEvict(rootModuleConf: String, reports0: List[((String, String), Vector[OrganizationArtifactReport])],
|
def transitivelyEvict(rootModuleConf: String, pairs: List[(String, String)],
|
||||||
evicted0: Vector[ModuleReport], log: Logger): List[((String, String), Vector[OrganizationArtifactReport])] =
|
reports0: Map[(String, String), Vector[OrganizationArtifactReport]],
|
||||||
|
evicted0: Vector[ModuleReport], log: Logger): Map[(String, String), Vector[OrganizationArtifactReport]] =
|
||||||
{
|
{
|
||||||
val em = (evicted0 map { _.module }).toSet
|
val em = (evicted0 map { _.module }).toSet
|
||||||
def isTransitivelyEvicted(mr: ModuleReport): Boolean =
|
def isTransitivelyEvicted(mr: ModuleReport): Boolean =
|
||||||
mr.callers forall { c => em(c.caller) }
|
mr.callers forall { c => em(c.caller) }
|
||||||
val reports: List[((String, String), Vector[OrganizationArtifactReport])] = reports0 map {
|
val reports: Seq[((String, String), Vector[OrganizationArtifactReport])] = reports0.toSeq flatMap {
|
||||||
|
case (k, v) if !(pairs contains k) => Seq()
|
||||||
case ((organization, name), oars0) =>
|
case ((organization, name), oars0) =>
|
||||||
val oars = oars0 map { oar =>
|
val oars = oars0 map { oar =>
|
||||||
val (affected, unaffected) = oar.modules partition { mr =>
|
val (affected, unaffected) = oar.modules partition { mr =>
|
||||||
|
|
@ -510,9 +528,9 @@ private[sbt] trait CachedResolutionResolveEngine extends ResolveEngine {
|
||||||
if (affected.isEmpty) oar
|
if (affected.isEmpty) oar
|
||||||
else new OrganizationArtifactReport(organization, name, unaffected ++ newlyEvicted)
|
else new OrganizationArtifactReport(organization, name, unaffected ++ newlyEvicted)
|
||||||
}
|
}
|
||||||
((organization, name), oars)
|
Seq(((organization, name), oars))
|
||||||
}
|
}
|
||||||
reports
|
Map(reports: _*)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* resolves dependency resolution conflicts in which multiple candidates are found for organization+name combos.
|
* resolves dependency resolution conflicts in which multiple candidates are found for organization+name combos.
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class CachedResolutionSpec extends BaseIvySpecification {
|
||||||
// second resolution reads from the minigraph
|
// second resolution reads from the minigraph
|
||||||
val report = ivyUpdate(m)
|
val report = ivyUpdate(m)
|
||||||
val modules = report.configurations.head.modules
|
val modules = report.configurations.head.modules
|
||||||
modules must containMatch("""org\.jboss\.netty:netty:3\.2\.0.Final""")
|
(modules must containMatch("""org\.jboss\.netty:netty:3\.2\.0.Final""")) and
|
||||||
|
(modules must not containMatch ("""org\.jboss\.netty:netty:3\.2\.1.Final"""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue