Merge pull request #1491 from sbt/fix/528

Unresolved dependency warning includes source position. Fixes #528
This commit is contained in:
Josh Suereth 2014-08-11 07:51:33 -04:00
commit d20bfa50ef
1 changed files with 83 additions and 24 deletions

View File

@ -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: Map[ModuleID, SourcePosition])
object UnresolvedWarningConfiguration {
def apply(): UnresolvedWarningConfiguration = apply(Map())
def apply(modulePositions: Map[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.
@ -130,43 +139,37 @@ 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, UnresolvedWarningConfiguration(), log) match {
case Right(r) => r
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,
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)
throw x
Left(UnresolvedWarning(x, uwconfig))
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)
@ -303,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
}
}