mirror of https://github.com/sbt/sbt.git
Merge pull request #1491 from sbt/fix/528
Unresolved dependency warning includes source position. Fixes #528
This commit is contained in:
commit
f7c4898a97
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ object CacheIvy {
|
|||
lazy val updateIC: InputCache[IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil] = implicitly
|
||||
/* def deliverIC: InputCache[IvyConfiguration :+: ModuleSettings :+: DeliverConfiguration :+: HNil] = implicitly
|
||||
def publishIC: InputCache[IvyConfiguration :+: ModuleSettings :+: PublishConfiguration :+: HNil] = implicitly*/
|
||||
lazy val moduleIDSeqIC: InputCache[Seq[ModuleID]] = implicitly
|
||||
lazy val modulePositionMapFormat: Format[Map[ModuleID, SourcePosition]] = implicitly
|
||||
|
||||
implicit lazy val updateReportFormat: Format[UpdateReport] =
|
||||
{
|
||||
|
|
@ -83,7 +85,19 @@ object CacheIvy {
|
|||
implicit def exclusionRuleFormat(implicit sf: Format[String]): Format[ExclusionRule] =
|
||||
wrap[ExclusionRule, (String, String, String, Seq[String])](e => (e.organization, e.name, e.artifact, e.configurations), { case (o, n, a, cs) => ExclusionRule(o, n, a, cs) })
|
||||
implicit def crossVersionFormat: Format[CrossVersion] = wrap(crossToInt, crossFromInt)
|
||||
|
||||
implicit def sourcePositionFormat: Format[SourcePosition] =
|
||||
wrap[SourcePosition, (Int, String, Int, Int)](
|
||||
{
|
||||
case NoPosition => (0, "", 0, 0)
|
||||
case LinePosition(p, s) => (1, p, s, 0)
|
||||
case RangePosition(p, LineRange(s, e)) => (2, p, s, e)
|
||||
},
|
||||
{
|
||||
case (0, _, _, _) => NoPosition
|
||||
case (1, p, s, _) => LinePosition(p, s)
|
||||
case (2, p, s, e) => RangePosition(p, LineRange(s, e))
|
||||
}
|
||||
)
|
||||
private[this] final val DisabledValue = 0
|
||||
private[this] final val BinaryValue = 1
|
||||
private[this] final val FullValue = 2
|
||||
|
|
|
|||
|
|
@ -1101,6 +1101,8 @@ object Classpaths {
|
|||
transitiveUpdate <<= transitiveUpdateTask,
|
||||
updateCacheName := "update_cache" + (if (crossPaths.value) s"_${scalaBinaryVersion.value}" else ""),
|
||||
evictionWarningOptions in update := EvictionWarningOptions.default,
|
||||
dependencyPositions <<= dependencyPositionsTask,
|
||||
unresolvedWarningConfiguration in update := UnresolvedWarningConfiguration(dependencyPositions.value),
|
||||
update <<= updateTask tag (Tags.Update, Tags.Network),
|
||||
update := {
|
||||
import ShowLines._
|
||||
|
|
@ -1214,7 +1216,13 @@ object Classpaths {
|
|||
} tag (Tags.Publish, Tags.Network)
|
||||
|
||||
import Cache._
|
||||
import CacheIvy.{ classpathFormat, /*publishIC,*/ updateIC, updateReportFormat, excludeMap }
|
||||
import CacheIvy.{
|
||||
classpathFormat, /*publishIC,*/ updateIC,
|
||||
updateReportFormat,
|
||||
excludeMap,
|
||||
moduleIDSeqIC,
|
||||
modulePositionMapFormat
|
||||
}
|
||||
|
||||
def withExcludes(out: File, classifiers: Seq[String], lock: xsbti.GlobalLock)(f: Map[ModuleID, Set[String]] => UpdateReport): UpdateReport =
|
||||
{
|
||||
|
|
@ -1251,19 +1259,33 @@ object Classpaths {
|
|||
case None => sv => if (scalaProvider.version == sv) scalaProvider.jars else Nil
|
||||
}
|
||||
val transform: UpdateReport => UpdateReport = r => substituteScalaFiles(scalaOrganization.value, r)(subScalaJars)
|
||||
|
||||
val uwConfig = (unresolvedWarningConfiguration in update).value
|
||||
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,
|
||||
uwConfig = uwConfig, 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,
|
||||
UnresolvedWarningConfiguration(), log)
|
||||
def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration,
|
||||
transform: UpdateReport => UpdateReport, skip: Boolean, force: Boolean, depsUpdated: Boolean,
|
||||
uwConfig: UnresolvedWarningConfiguration, 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, uwConfig, log) match {
|
||||
case Right(ur) => ur
|
||||
case Left(uw) =>
|
||||
import ShowLines._
|
||||
uw.lines foreach { log.warn(_) }
|
||||
throw uw.resolveException
|
||||
}
|
||||
log.info("Done updating.")
|
||||
transform(r)
|
||||
}
|
||||
|
|
@ -1303,6 +1325,41 @@ object Classpaths {
|
|||
}
|
||||
private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean =
|
||||
stamps.get(file).forall(_ == file.lastModified)
|
||||
private[sbt] def dependencyPositionsTask: Initialize[Task[Map[ModuleID, SourcePosition]]] = Def.task {
|
||||
val projRef = thisProjectRef.value
|
||||
val st = state.value
|
||||
val s = streams.value
|
||||
val cacheFile = s.cacheDirectory / updateCacheName.value
|
||||
implicit val depSourcePosCache = moduleIDSeqIC
|
||||
implicit val outFormat = modulePositionMapFormat
|
||||
def modulePositions: Map[ModuleID, SourcePosition] =
|
||||
try {
|
||||
val extracted = (Project extract st)
|
||||
val sk = (libraryDependencies in (GlobalScope in projRef)).scopedKey
|
||||
val empty = extracted.structure.data set (sk.scope, sk.key, Nil)
|
||||
val settings = extracted.structure.settings filter { s: Setting[_] =>
|
||||
(s.key.key == libraryDependencies.key) &&
|
||||
(s.key.scope.project == Select(projRef))
|
||||
}
|
||||
Map(settings flatMap {
|
||||
case s: Setting[Seq[ModuleID]] @unchecked =>
|
||||
s.init.evaluate(empty) map { _ -> s.pos }
|
||||
}: _*)
|
||||
} catch {
|
||||
case _: Throwable => Map()
|
||||
}
|
||||
|
||||
val outCacheFile = cacheFile / "output_dsp"
|
||||
val f = Tracked.inputChanged(cacheFile / "input_dsp") { (inChanged: Boolean, in: Seq[ModuleID]) =>
|
||||
val outCache = Tracked.lastOutput[Seq[ModuleID], Map[ModuleID, SourcePosition]](outCacheFile) {
|
||||
case (_, Some(out)) if !inChanged => out
|
||||
case _ => modulePositions
|
||||
}
|
||||
outCache(in)
|
||||
}
|
||||
f(libraryDependencies.value)
|
||||
}
|
||||
|
||||
/*
|
||||
// can't cache deliver/publish easily since files involved are hidden behind patterns. publish will be difficult to verify target-side anyway
|
||||
def cachedPublish(cacheFile: File)(g: (IvySbt#Module, PublishConfiguration) => Unit, module: IvySbt#Module, config: PublishConfiguration) => Unit =
|
||||
|
|
|
|||
|
|
@ -243,6 +243,8 @@ object Keys {
|
|||
val unmanagedBase = SettingKey[File]("unmanaged-base", "The default directory for manually managed libraries.", ASetting)
|
||||
val updateConfiguration = SettingKey[UpdateConfiguration]("update-configuration", "Configuration for resolving and retrieving managed dependencies.", DSetting)
|
||||
val updateOptions = SettingKey[UpdateOptions]("update-options", "Options for resolving managed dependencies.", DSetting)
|
||||
val unresolvedWarningConfiguration = TaskKey[UnresolvedWarningConfiguration]("unresolved-warning-configuration", "Configuration for unresolved dependency warning.", DTask)
|
||||
val dependencyPositions = TaskKey[Map[ModuleID, SourcePosition]]("dependency-positions", "Source positions where the dependencies are defined.", DTask)
|
||||
val ivySbt = TaskKey[IvySbt]("ivy-sbt", "Provides the sbt interface to Ivy.", CTask)
|
||||
val ivyModule = TaskKey[IvySbt#Module]("ivy-module", "Provides the sbt interface to a configured Ivy module.", CTask)
|
||||
val updateCacheName = TaskKey[String]("updateCacheName", "Defines the directory name used to store the update cache files (inside the streams cacheDirectory).", DTask)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -117,7 +118,7 @@ sbt 0.13.6 now allows `enablePlugins` and `disablePlugins` to be written directl
|
|||
### Unresolved dependencies error
|
||||
|
||||
sbt 0.13.6 will try to reconstruct dependencies tree when it fails to resolve a managed dependency.
|
||||
This is an approximation, but it should help you figure out where the problematic dependency is coming from:
|
||||
This is an approximation, but it should help you figure out where the problematic dependency is coming from. When possible sbt will display the source position next to the modules:
|
||||
|
||||
[warn] ::::::::::::::::::::::::::::::::::::::::::::::
|
||||
[warn] :: UNRESOLVED DEPENDENCIES ::
|
||||
|
|
@ -128,10 +129,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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue