From ae13cd8412aeda022300f2923b29c0471936389e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 10 Jul 2014 17:49:52 -0400 Subject: [PATCH] Improves unresolved dependencies error by displaying the deps nodes. fixes #1422, #381 --- ivy/src/main/scala/sbt/IvyActions.scala | 30 ++++++++++++++++++--- ivy/src/main/scala/sbt/IvyRetrieve.scala | 34 ++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index c87666373..bdbf5fe1c 100644 --- a/ivy/src/main/scala/sbt/IvyActions.scala +++ b/ivy/src/main/scala/sbt/IvyActions.scala @@ -146,6 +146,17 @@ object IvyActions { withExtra foreach { id => log.warn("\t\t" + id) } log.warn("") } + err.failed foreach { x => + val failedPaths = err.failedPaths(x) + if (!failedPaths.isEmpty) { + log.warn("\n\tNote: Unresolved dependencies path:") + val reverseFailedPaths = (failedPaths.toList map { _.toString }).reverse + log.warn("\t\t" + reverseFailedPaths.head) + reverseFailedPaths.tail foreach { id => + log.warn("\t\t +- " + id) + } + } + } } def groupedConflicts[T](moduleFilter: ModuleFilter, grouping: ModuleID => T)(report: UpdateReport): Map[T, Set[String]] = report.configurations.flatMap { confReport => @@ -209,8 +220,15 @@ object IvyActions { val err = if (resolveReport.hasError) { val messages = resolveReport.getAllProblemMessages.toArray.map(_.toString).distinct - val failed = resolveReport.getUnresolvedDependencies.map(node => IvyRetrieve.toModuleID(node.getId)) - Some(new ResolveException(messages, failed)) + val failedPaths = Map(resolveReport.getUnresolvedDependencies map { node => + val m = IvyRetrieve.toModuleID(node.getId) + val path = IvyRetrieve.findPath(node, module.getModuleRevisionId) map { x => + IvyRetrieve.toModuleID(x.getId) + } + m -> path + }: _*) + val failed = failedPaths.keys.toSeq + Some(new ResolveException(messages, failed, failedPaths)) } else None (resolveReport, err) } @@ -269,4 +287,10 @@ object IvyActions { error("Missing files for publishing:\n\t" + missing.map(_._2.getAbsolutePath).mkString("\n\t")) } } -final class ResolveException(val messages: Seq[String], val failed: Seq[ModuleID]) extends RuntimeException(messages.mkString("\n")) +final class ResolveException( + val messages: Seq[String], + val failed: Seq[ModuleID], + val failedPaths: Map[ModuleID, Seq[ModuleID]]) extends RuntimeException(messages.mkString("\n")) { + def this(messages: Seq[String], failed: Seq[ModuleID]) = + this(messages, failed, Map(failed map { m => m -> Nil }: _*)) +} diff --git a/ivy/src/main/scala/sbt/IvyRetrieve.scala b/ivy/src/main/scala/sbt/IvyRetrieve.scala index 5d0f3f6ec..e863d6723 100644 --- a/ivy/src/main/scala/sbt/IvyRetrieve.scala +++ b/ivy/src/main/scala/sbt/IvyRetrieve.scala @@ -5,10 +5,10 @@ package sbt import java.io.File import collection.mutable - -import org.apache.ivy.core.{ module, report } +import org.apache.ivy.core.{ module, report, resolve } import module.descriptor.{ Artifact => IvyArtifact } import module.id.ModuleRevisionId +import resolve.IvyNode import report.{ ArtifactDownloadReport, ConfigurationResolveReport, ResolveReport } object IvyRetrieve { @@ -51,4 +51,34 @@ object IvyRetrieve { new UpdateStats(report.getResolveTime, report.getDownloadTime, report.getDownloadSize, false) def configurationReport(confReport: ConfigurationResolveReport): ConfigurationReport = new ConfigurationReport(confReport.getConfiguration, moduleReports(confReport), evicted(confReport)) + + /** + * Tries to find Ivy graph path the from node to target. + */ + def findPath(target: IvyNode, from: ModuleRevisionId): List[IvyNode] = { + def doFindPath(current: IvyNode, path: List[IvyNode]): List[IvyNode] = { + val callers = current.getAllRealCallers.toList + // Ivy actually returns non-direct callers here. + // that's why we have to calculate all possible paths below and pick the longest path. + val directCallers = callers filter { caller => + val md = caller.getModuleDescriptor + val dd = md.getDependencies.toList find { dd => + (dd.getDependencyRevisionId == current.getId) && + (dd.getParentRevisionId == caller.getModuleRevisionId) + } + dd.isDefined + } + val directCallersRevId = (directCallers map { _.getModuleRevisionId }).distinct + val paths: List[List[IvyNode]] = ((directCallersRevId map { revId => + val node = current.findNode(revId) + if (revId == from) node :: path + else if (node == node.getRoot) Nil + else if (path contains node) path + else doFindPath(node, node :: path) + }) sortBy { _.size }).reverse + paths.headOption getOrElse Nil + } + if (target.getId == from) List(target) + else doFindPath(target, List(target)) + } }