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.
This commit is contained in:
Eugene Yokota 2014-08-02 06:15:28 -04:00
parent 4a83fcaeb1
commit 58b7c63f84
4 changed files with 124 additions and 29 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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