From 58b7c63f84f6ff68ada23782b36548e7ff4d5e20 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 2 Aug 2014 06:15:28 -0400 Subject: [PATCH] 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 ++++----- main/src/main/scala/sbt/Defaults.scala | 28 ++++++- .../sbt/UnresolvedDependencyWarning.scala | 81 +++++++++++++++++++ notes/0.13.6.md | 5 +- 4 files changed, 124 insertions(+), 29 deletions(-) create mode 100644 main/src/main/scala/sbt/UnresolvedDependencyWarning.scala 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) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index c6996918c..3cf0d25dd 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1253,17 +1253,37 @@ object Classpaths { val transform: UpdateReport => UpdateReport = r => substituteScalaFiles(scalaOrganization.value, r)(subScalaJars) val show = Reference.display(thisProjectRef.value) - cachedUpdate(s.cacheDirectory / updateCacheName.value, show, ivyModule.value, updateConfiguration.value, transform, skip = (skip in update).value, force = isRoot, depsUpdated = depsUpdated, log = s.log) + cachedUpdate(s.cacheDirectory / updateCacheName.value, show, ivyModule.value, updateConfiguration.value, transform, + skip = (skip in update).value, force = isRoot, depsUpdated = depsUpdated, + unresolvedHandler = { r => + import ShowLines._ + UnresolvedDependencyWarning(r, Some(state.value)).lines foreach { s.log.warn(_) } + }, + log = s.log) } - - def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, transform: UpdateReport => UpdateReport, skip: Boolean, force: Boolean, depsUpdated: Boolean, log: Logger): UpdateReport = + @deprecated("Use cachedUpdate with the variant that takes unresolvedHandler instead.", "0.13.6") + def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, + transform: UpdateReport => UpdateReport, skip: Boolean, force: Boolean, depsUpdated: Boolean, log: Logger): UpdateReport = + cachedUpdate(cacheFile, label, module, config, transform, skip, force, depsUpdated, + { r => + import ShowLines._ + UnresolvedDependencyWarning(r, None).lines foreach { log.warn(_) } + }, log) + def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, + transform: UpdateReport => UpdateReport, skip: Boolean, force: Boolean, depsUpdated: Boolean, + unresolvedHandler: ResolveException => Unit, log: Logger): UpdateReport = { implicit val updateCache = updateIC type In = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil def work = (_: In) match { case conf :+: settings :+: config :+: HNil => log.info("Updating " + label + "...") - val r = IvyActions.update(module, config, log) + val r = IvyActions.updateEither(module, config, log) match { + case Right(ur) => ur + case Left(re) => + unresolvedHandler(re) + throw re + } log.info("Done updating.") transform(r) } diff --git a/main/src/main/scala/sbt/UnresolvedDependencyWarning.scala b/main/src/main/scala/sbt/UnresolvedDependencyWarning.scala new file mode 100644 index 000000000..f37ec8bbe --- /dev/null +++ b/main/src/main/scala/sbt/UnresolvedDependencyWarning.scala @@ -0,0 +1,81 @@ +package sbt + +import java.io.File +import collection.mutable +import Def.Setting +import Scope.GlobalScope + +private[sbt] final class UnresolvedDependencyWarning( + val withExtra: Seq[ModuleID], + val failedPaths: Seq[Seq[(ModuleID, Option[String])]]) + +private[sbt] object UnresolvedDependencyWarning { + // This method used to live under IvyActions.scala, but it's moved here because it's now aware of State. + private[sbt] def apply(err: ResolveException, stateOpt: Option[State]): UnresolvedDependencyWarning = { + // This is a mapping between modules and original position, in which the module was introduced. + lazy val modulePositions: Seq[(ModuleID, SourcePosition)] = + try { + stateOpt map { state => + val extracted = (Project extract state) + val sk = (Keys.libraryDependencies in (GlobalScope in extracted.currentRef)).scopedKey + val empty = extracted.structure.data set (sk.scope, sk.key, Nil) + val settings = extracted.structure.settings filter { s: Setting[_] => + (s.key.key == Keys.libraryDependencies.key) && + (s.key.scope.project == Select(extracted.currentRef)) + } + settings flatMap { + case s: Setting[Seq[ModuleID]] @unchecked => + s.init.evaluate(empty) map { _ -> s.pos } + } + } getOrElse Seq() + } catch { + case _: Throwable => Seq() + } + def modulePosition(m0: ModuleID): Option[String] = + modulePositions.find { + case (m, p) => + (m.organization == m0.organization) && + (m0.name startsWith m.name) && + (m.revision == m0.revision) + } flatMap { + case (m, LinePosition(path, start)) => + Some(s" ($path#L$start)") + case (m, RangePosition(path, LineRange(start, end))) => + Some(s" ($path#L$start-$end)") + case _ => None + } + val withExtra = err.failed.filter(!_.extraDependencyAttributes.isEmpty) + val failedPaths = err.failed map { x: ModuleID => + err.failedPaths(x).toList.reverse map { id => + (id, modulePosition(id)) + } + } + UnresolvedDependencyWarning(withExtra, failedPaths) + } + + def apply(withExtra: Seq[ModuleID], + failedPaths: Seq[Seq[(ModuleID, Option[String])]]): UnresolvedDependencyWarning = + new UnresolvedDependencyWarning(withExtra, failedPaths) + + implicit val unresolvedDependencyWarningLines: ShowLines[UnresolvedDependencyWarning] = ShowLines { a => + val buffer = mutable.ListBuffer[String]() + if (!a.withExtra.isEmpty) { + buffer += "\n\tNote: Some unresolved dependencies have extra attributes. Check that these dependencies exist with the requested attributes." + a.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 + head._2.getOrElse("") + path.tail foreach { + case (m, pos) => + buffer += "\t\t +- " + m.toString + pos.getOrElse("") + } + } + } + } + buffer.toList + } +} diff --git a/notes/0.13.6.md b/notes/0.13.6.md index 48fcf27aa..a1e0984a6 100644 --- a/notes/0.13.6.md +++ b/notes/0.13.6.md @@ -1,4 +1,5 @@ [413]: https://github.com/sbt/sbt/issues/413 + [528]: https://github.com/sbt/sbt/issues/528 [856]: https://github.com/sbt/sbt/issues/856 [1036]: https://github.com/sbt/sbt/pull/1036 [1059]: https://github.com/sbt/sbt/issues/1059 @@ -105,10 +106,10 @@ This is an approximation, but it should help you figure out where the problemati [warn] Note: Unresolved dependencies path: [warn] foundrylogic.vpp:vpp:2.2.1 [warn] +- org.apache.cayenne:cayenne-tools:3.0.2 - [warn] +- org.apache.cayenne.plugins:maven-cayenne-plugin:3.0.2 + [warn] +- org.apache.cayenne.plugins:maven-cayenne-plugin:3.0.2 (/foo/some-test/build.sbt#L28) [warn] +- d:d_2.10:0.1-SNAPSHOT -[#1422][1422]/[#1447][1447] by [@eed3si9n][@eed3si9n] +[#528][528]/[#1422][1422]/[#1447][1447] by [@eed3si9n][@eed3si9n] ### Eviction warnings