mirror of https://github.com/sbt/sbt.git
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:
parent
58b7c63f84
commit
dc2ae51d73
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue