From 44241ce97ccc941d2aeaebadcb6224f44fd21e4c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 2 Aug 2014 06:15:28 -0400 Subject: [PATCH 1/3] Unresolved dependency warning includes source position. Fixes #528 Unresolved dependency warning is moved to UnresolvedDependencyWarning class including the fail path that was added in #1467. To display the source position, I need to access the State, so I had to move the error processing out of IvyActions and add UnresolvedDependencyWarning, which is aware of State. --- ivy/src/main/scala/sbt/IvyActions.scala | 39 ++++++++++--------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index 4960c9a2d..646293b0e 100644 --- a/ivy/src/main/scala/sbt/IvyActions.scala +++ b/ivy/src/main/scala/sbt/IvyActions.scala @@ -130,43 +130,36 @@ object IvyActions { * Resolves and retrieves dependencies. 'ivyConfig' is used to produce an Ivy file and configuration. * 'updateConfig' configures the actual resolution and retrieval process. */ + @deprecated("Use updateEither instead.", "0.13.6") def update(module: IvySbt#Module, configuration: UpdateConfiguration, log: Logger): UpdateReport = + updateEither(module, configuration, log) match { + case Right(r) => r + case Left(e) => throw e + } + + /** + * Resolves and retrieves dependencies. 'ivyConfig' is used to produce an Ivy file and configuration. + * 'updateConfig' configures the actual resolution and retrieval process. + */ + def updateEither(module: IvySbt#Module, configuration: UpdateConfiguration, log: Logger): Either[ResolveException, UpdateReport] = module.withModule(log) { case (ivy, md, default) => val (report, err) = resolve(configuration.logging)(ivy, md, default) err match { case Some(x) if !configuration.missingOk => processUnresolved(x, log) - throw x + Left(x) case _ => val cachedDescriptor = ivy.getSettings.getResolutionCacheManager.getResolvedIvyFileInCache(md.getModuleRevisionId) val uReport = IvyRetrieve.updateReport(report, cachedDescriptor) configuration.retrieve match { - case Some(rConf) => retrieve(ivy, uReport, rConf) - case None => uReport + case Some(rConf) => Right(retrieve(ivy, uReport, rConf)) + case None => Right(uReport) } } } - - def processUnresolved(err: ResolveException, log: Logger) { - val withExtra = err.failed.filter(!_.extraDependencyAttributes.isEmpty) - if (!withExtra.isEmpty) { - log.warn("\n\tNote: Some unresolved dependencies have extra attributes. Check that these dependencies exist with the requested attributes.") - 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) - } - } - } - } + @deprecated("No longer used.", "0.13.6") + def processUnresolved(err: ResolveException, log: Logger): Unit = () def groupedConflicts[T](moduleFilter: ModuleFilter, grouping: ModuleID => T)(report: UpdateReport): Map[T, Set[String]] = report.configurations.flatMap { confReport => val evicted = confReport.evicted.filter(moduleFilter) From 4be04bf894f2ebac68322b4ed1c3a530ee0f1961 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 3 Aug 2014 02:26:50 -0400 Subject: [PATCH 2/3] Adds UnresolvedWarningConfiguration that caches ModuleID -> SourcePosition mapping. UnresolvedWarning is moved back to IvyActions.scala where it belongs. The mapping between ModuleID and SourcePosition is passed in as UnresolvedWarningConfiguration. This is calculated once in Defaults using State and is cached to filesystem. --- ivy/src/main/scala/sbt/IvyActions.scala | 76 +++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index 646293b0e..988809fb0 100644 --- a/ivy/src/main/scala/sbt/IvyActions.scala +++ b/ivy/src/main/scala/sbt/IvyActions.scala @@ -5,6 +5,7 @@ package sbt import java.io.File import scala.xml.{ Node => XNode, NodeSeq } +import collection.mutable import org.apache.ivy.Ivy import org.apache.ivy.core.{ IvyPatternHelper, LogOptions } @@ -29,6 +30,14 @@ final case class MakePomConfiguration(file: File, moduleInfo: ModuleInfo, config final case class GetClassifiersConfiguration(module: GetClassifiersModule, exclude: Map[ModuleID, Set[String]], configuration: UpdateConfiguration, ivyScala: Option[IvyScala]) final case class GetClassifiersModule(id: ModuleID, modules: Seq[ModuleID], configurations: Seq[Configuration], classifiers: Seq[String]) +final class UnresolvedWarningConfiguration private[sbt] ( + val modulePositions: Seq[(ModuleID, SourcePosition)]) +object UnresolvedWarningConfiguration { + def apply(): UnresolvedWarningConfiguration = apply(Seq()) + def apply(modulePositions: Seq[(ModuleID, SourcePosition)]): UnresolvedWarningConfiguration = + new UnresolvedWarningConfiguration(modulePositions) +} + /** * Configures logging during an 'update'. `level` determines the amount of other information logged. * `Full` is the default and logs the most. @@ -132,23 +141,24 @@ object IvyActions { */ @deprecated("Use updateEither instead.", "0.13.6") def update(module: IvySbt#Module, configuration: UpdateConfiguration, log: Logger): UpdateReport = - updateEither(module, configuration, log) match { + updateEither(module, configuration, UnresolvedWarningConfiguration(), log) match { case Right(r) => r - case Left(e) => throw e + case Left(w) => + throw w.resolveException } /** * Resolves and retrieves dependencies. 'ivyConfig' is used to produce an Ivy file and configuration. * 'updateConfig' configures the actual resolution and retrieval process. */ - def updateEither(module: IvySbt#Module, configuration: UpdateConfiguration, log: Logger): Either[ResolveException, UpdateReport] = + def updateEither(module: IvySbt#Module, configuration: UpdateConfiguration, + uwconfig: UnresolvedWarningConfiguration, log: Logger): Either[UnresolvedWarning, UpdateReport] = module.withModule(log) { case (ivy, md, default) => val (report, err) = resolve(configuration.logging)(ivy, md, default) err match { case Some(x) if !configuration.missingOk => - processUnresolved(x, log) - Left(x) + Left(UnresolvedWarning(x, uwconfig)) case _ => val cachedDescriptor = ivy.getSettings.getResolutionCacheManager.getResolvedIvyFileInCache(md.getModuleRevisionId) val uReport = IvyRetrieve.updateReport(report, cachedDescriptor) @@ -296,3 +306,59 @@ final class ResolveException( def this(messages: Seq[String], failed: Seq[ModuleID]) = this(messages, failed, Map(failed map { m => m -> Nil }: _*)) } +/** + * Represents unresolved dependency warning, which displays reconstructed dependency tree + * along with source position of each node. + */ +final class UnresolvedWarning private[sbt] ( + val resolveException: ResolveException, + val failedPaths: Seq[Seq[(ModuleID, Option[SourcePosition])]]) +object UnresolvedWarning { + private[sbt] def apply(err: ResolveException, config: UnresolvedWarningConfiguration): UnresolvedWarning = { + def modulePosition(m0: ModuleID): Option[SourcePosition] = + config.modulePositions.find { + case (m, p) => + (m.organization == m0.organization) && + (m0.name startsWith m.name) && + (m.revision == m0.revision) + } map { + case (m, p) => p + } + val failedPaths = err.failed map { x: ModuleID => + err.failedPaths(x).toList.reverse map { id => + (id, modulePosition(id)) + } + } + apply(err, failedPaths) + } + private[sbt] def apply(err: ResolveException, failedPaths: Seq[Seq[(ModuleID, Option[SourcePosition])]]): UnresolvedWarning = + new UnresolvedWarning(err, failedPaths) + private[sbt] def sourcePosStr(posOpt: Option[SourcePosition]): String = + posOpt match { + case Some(LinePosition(path, start)) => s" ($path#L$start)" + case Some(RangePosition(path, LineRange(start, end))) => s" ($path#L$start-$end)" + case _ => "" + } + implicit val unresolvedWarningLines: ShowLines[UnresolvedWarning] = ShowLines { a => + val withExtra = a.resolveException.failed.filter(!_.extraDependencyAttributes.isEmpty) + val buffer = mutable.ListBuffer[String]() + if (!withExtra.isEmpty) { + buffer += "\n\tNote: Some unresolved dependencies have extra attributes. Check that these dependencies exist with the requested attributes." + withExtra foreach { id => buffer += "\t\t" + id } + } + if (!a.failedPaths.isEmpty) { + buffer += "\n\tNote: Unresolved dependencies path:" + a.failedPaths foreach { path => + if (!path.isEmpty) { + val head = path.head + buffer += "\t\t" + head._1.toString + sourcePosStr(head._2) + path.tail foreach { + case (m, pos) => + buffer += "\t\t +- " + m.toString + sourcePosStr(pos) + } + } + } + } + buffer.toList + } +} From 302362fad4053fca4d6832996a768d4de046a18b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 8 Aug 2014 00:53:09 -0400 Subject: [PATCH 3/3] Adds `dependencyPositions` task to explicitly track dependency positions. --- ivy/src/main/scala/sbt/IvyActions.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index 988809fb0..4d69ae2c7 100644 --- a/ivy/src/main/scala/sbt/IvyActions.scala +++ b/ivy/src/main/scala/sbt/IvyActions.scala @@ -31,10 +31,10 @@ final case class GetClassifiersConfiguration(module: GetClassifiersModule, exclu final case class GetClassifiersModule(id: ModuleID, modules: Seq[ModuleID], configurations: Seq[Configuration], classifiers: Seq[String]) final class UnresolvedWarningConfiguration private[sbt] ( - val modulePositions: Seq[(ModuleID, SourcePosition)]) + val modulePositions: Map[ModuleID, SourcePosition]) object UnresolvedWarningConfiguration { - def apply(): UnresolvedWarningConfiguration = apply(Seq()) - def apply(modulePositions: Seq[(ModuleID, SourcePosition)]): UnresolvedWarningConfiguration = + def apply(): UnresolvedWarningConfiguration = apply(Map()) + def apply(modulePositions: Map[ModuleID, SourcePosition]): UnresolvedWarningConfiguration = new UnresolvedWarningConfiguration(modulePositions) }