Warn and optionally error when multiple cross version suffixes for a module are detected. Fixes #639.

This commit is contained in:
Mark Harrah 2013-01-15 08:21:53 -05:00
parent a9ac6b3983
commit dd008e4dc5
5 changed files with 70 additions and 5 deletions

View File

@ -8,24 +8,80 @@ object ConflictWarning
def disable: ConflictWarning = ConflictWarning("", (_: ModuleID) => false, org, Level.Warn, false)
private[this] def org = (_: ModuleID).organization
private[this] def idString(org: String, name: String) = s"$org:$name"
def default(label: String): ConflictWarning = ConflictWarning(label, moduleFilter(organization = GlobFilter(SbtArtifacts.Organization) | GlobFilter(ScalaArtifacts.Organization)), org, Level.Warn, false)
def strict(label: String): ConflictWarning = ConflictWarning(label, (id: ModuleID) => true, (id: ModuleID) => id.organization + ":" + id.name, Level.Error, true)
def strict(label: String): ConflictWarning = ConflictWarning(label, (id: ModuleID) => true, (id: ModuleID) => idString(id.organization, id.name), Level.Error, true)
def apply(config: ConflictWarning, report: UpdateReport, log: Logger)
{
processEvicted(config, report, log)
processCrossVersioned(config, report, log)
}
private[this] def processEvicted(config: ConflictWarning, report: UpdateReport, log: Logger)
{
val conflicts = IvyActions.groupedConflicts(config.filter, config.group)(report)
if(!conflicts.isEmpty)
{
val prefix = if(config.failOnConflict) "Incompatible" else "Potentially incompatible"
val msg = prefix + " versions of dependencies of " + config.label + ":\n "
val msg = s"$prefix versions of dependencies of ${config.label}:\n "
val conflictMsgs =
for( (label, versions) <- conflicts ) yield
label + ": " + versions.mkString(", ")
log.log(config.level, conflictMsgs.mkString(msg, "\n ", ""))
if(config.failOnConflict)
error("Conflicts in " + conflicts.map(_._1).mkString(", ") )
}
if(config.failOnConflict && !conflicts.isEmpty)
error("Conflicts in " + conflicts.map(_._1).mkString(", ") )
}
private[this] def processCrossVersioned(config: ConflictWarning, report: UpdateReport, log: Logger)
{
val crossMismatches = crossVersionMismatches(report)
if(!crossMismatches.isEmpty)
{
val pre = "Modules were resolved with conflicting cross-version suffixes:\n "
val conflictMsgs =
for( ((org,rawName), fullNames) <- crossMismatches ) yield
{
val suffixes = fullNames.map(n => "_" + getCrossSuffix(n)).mkString(", ")
s"${idString(org,rawName)} $suffixes"
}
log.log(config.level, conflictMsgs.mkString(pre, "\n ", ""))
if(config.failOnConflict) {
val summary = crossMismatches.map{ case ((org,raw),_) => idString(org,raw)}.mkString(", ")
error("Conflicting cross-version suffixes in: " + summary)
}
}
}
/** Map from (organization, rawName) to set of multiple full names. */
def crossVersionMismatches(report: UpdateReport): Map[(String,String), Set[String]] =
{
val mismatches = report.configurations.flatMap { confReport =>
groupByRawName(confReport.allModules).mapValues { modules =>
val differentFullNames = modules.map(_.name).toSet
if(differentFullNames.size > 1) differentFullNames else Set.empty[String]
}
}
(Map.empty[(String,String),Set[String]] /: mismatches)(merge)
}
private[this] def merge[A,B](m: Map[A, Set[B]], b: (A, Set[B])): Map[A, Set[B]] =
if(b._2.isEmpty) m else
m.updated(b._1, m.getOrElse(b._1, Set.empty) ++ b._2)
private[this] def groupByRawName(ms: Seq[ModuleID]): Map[(String,String), Seq[ModuleID]] =
ms.groupBy(m => (m.organization, dropCrossSuffix(m.name)))
private[this] val CrossSuffixPattern = """(.+)_(\d+\.\d+(?:\.\d+)?(?:-.+)?)""".r
private[this] def dropCrossSuffix(s: String): String = s match {
case CrossSuffixPattern(raw, _) => raw
case _ => s
}
private[this] def getCrossSuffix(s: String): String = s match {
case CrossSuffixPattern(_, v) => v
case _ => ""
}
}

View File

@ -157,7 +157,6 @@ object IvyActions
def grouped[T](grouping: ModuleID => T)(mods: Seq[ModuleID]): Map[T, Set[String]] =
mods groupBy(grouping) mapValues(_.map(_.revision).toSet)
def transitiveScratch(ivySbt: IvySbt, label: String, config: GetClassifiersConfiguration, log: Logger): UpdateReport =
{
import config.{configuration => c, ivyScala, module => mod}

View File

@ -0,0 +1,4 @@
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "1.9.1" % "test",
"org.scalamock" %% "scalamock-scalatest-support" % "3.0" % "test"
)

View File

@ -0,0 +1 @@
conflictWarning ~= { _.copy(failOnConflict = true) }

View File

@ -0,0 +1,5 @@
> update
$ copy-file changes/conflict-error.sbt conflict-error.sbt
> reload
-> update