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.
This commit is contained in:
Eugene Yokota 2014-08-03 02:26:50 -04:00
parent 58b7c63f84
commit dc2ae51d73
5 changed files with 138 additions and 102 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: 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
}
}

View File

@ -55,6 +55,7 @@ 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
implicit lazy val updateReportFormat: Format[UpdateReport] =
{
@ -83,6 +84,21 @@ 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))
}
)
implicit def unresolvedWarningConfigurationFormat: Format[UnresolvedWarningConfiguration] =
wrap[UnresolvedWarningConfiguration, (Seq[(ModuleID, SourcePosition)])](c => (c.modulePositions), { case ps => UnresolvedWarningConfiguration(ps) })
private[this] final val DisabledValue = 0
private[this] final val BinaryValue = 1

View File

@ -1101,6 +1101,7 @@ object Classpaths {
transitiveUpdate <<= transitiveUpdateTask,
updateCacheName := "update_cache" + (if (crossPaths.value) s"_${scalaBinaryVersion.value}" else ""),
evictionWarningOptions in update := EvictionWarningOptions.default,
unresolvedWarningConfiguration in update <<= unresolvedWarningConfigurationTask,
update <<= updateTask tag (Tags.Update, Tags.Network),
update := {
import ShowLines._
@ -1214,7 +1215,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,
unresolvedWarningConfigurationFormat
}
def withExcludes(out: File, classifiers: Seq[String], lock: xsbti.GlobalLock)(f: Map[ModuleID, Set[String]] => UpdateReport): UpdateReport =
{
@ -1251,38 +1258,32 @@ 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,
unresolvedHandler = { r =>
import ShowLines._
UnresolvedDependencyWarning(r, Some(state.value)).lines foreach { s.log.warn(_) }
},
log = s.log)
uwConfig = uwConfig, log = s.log)
}
@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)
UnresolvedWarningConfiguration(), 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 =
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.updateEither(module, config, log) match {
val r = IvyActions.updateEither(module, config, uwConfig, log) match {
case Right(ur) => ur
case Left(re) =>
unresolvedHandler(re)
throw re
case Left(uw) =>
import ShowLines._
uw.lines foreach { log.warn(_) }
throw uw.resolveException
}
log.info("Done updating.")
transform(r)
@ -1313,6 +1314,39 @@ object Classpaths {
}
private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean =
stamps.get(file).forall(_ == file.lastModified)
private[sbt] def unresolvedWarningConfigurationTask: Initialize[Task[UnresolvedWarningConfiguration]] = Def.task {
val projRef = thisProjectRef.value
val st = state.value
val s = streams.value
val cacheFile = s.cacheDirectory / updateCacheName.value
implicit val uwConfigCache = moduleIDSeqIC
def modulePositions: Seq[(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))
}
settings flatMap {
case s: Setting[Seq[ModuleID]] @unchecked =>
s.init.evaluate(empty) map { _ -> s.pos }
}
} catch {
case _: Throwable => Seq()
}
val outCacheFile = cacheFile / "output_uwc"
val f = Tracked.inputChanged(cacheFile / "input_uwc") { (inChanged: Boolean, in: Seq[ModuleID]) =>
val outCache = Tracked.lastOutput[Seq[ModuleID], UnresolvedWarningConfiguration](outCacheFile) {
case (_, Some(out)) if !inChanged => out
case _ => UnresolvedWarningConfiguration(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 =

View File

@ -243,6 +243,7 @@ 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 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)

View File

@ -1,81 +0,0 @@
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
}
}