mirror of https://github.com/sbt/sbt.git
Expand ModuleReport into the full Ivy resolution report. #1200
Currently sbt's update task generates UpdateReport from Ivy's resolution report. For each configuration there's ConfigurationReport, which contains just enough information on the resolved module/revision/artifact. Speaking of module, in Ivy module means organization and name, and organization, name, and version is called module revision. In sbt, module revision is called Module. This is relevant because to talk about evictions, we need a terminology for organization and name combo. In any case ConfigurationReport is expanded to have `details` field, which contains Seq[ModuleDetailReport], which represents organization and name combo plus all the modules just like Ivy's resolution report XML. Furthermore, ModuleReport is expanded to include licenses, eviction, callers, etc.
This commit is contained in:
parent
5b070b9dcc
commit
855e7f176b
|
|
@ -2,6 +2,11 @@ package sbt
|
|||
|
||||
import DependencyFilter._
|
||||
|
||||
/**
|
||||
* Provide warnings for cross version conflicts.
|
||||
* A library foo_2.10 and foo_2.11 can potentially be both included on the
|
||||
* library dependency graph by mistake, but it won't be caught by eviction.
|
||||
*/
|
||||
final case class ConflictWarning(label: String, level: Level.Value, failOnConflict: Boolean) {
|
||||
@deprecated("`filter` is no longer used", "0.13.0")
|
||||
val filter: ModuleFilter = (_: ModuleID) => false
|
||||
|
|
|
|||
|
|
@ -4,35 +4,121 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import java.{ util => ju }
|
||||
import collection.mutable
|
||||
import java.net.URL
|
||||
import org.apache.ivy.core.{ module, report, resolve }
|
||||
import module.descriptor.{ Artifact => IvyArtifact }
|
||||
import module.id.ModuleRevisionId
|
||||
import resolve.IvyNode
|
||||
import module.descriptor.{ Artifact => IvyArtifact, License => IvyLicense }
|
||||
import module.id.{ ModuleRevisionId, ModuleId => IvyModuleId }
|
||||
import report.{ ArtifactDownloadReport, ConfigurationResolveReport, ResolveReport }
|
||||
import resolve.{ IvyNode, IvyNodeCallers }
|
||||
import IvyNodeCallers.{ Caller => IvyCaller }
|
||||
|
||||
object IvyRetrieve {
|
||||
def reports(report: ResolveReport): Seq[ConfigurationResolveReport] =
|
||||
report.getConfigurations map report.getConfigurationReport
|
||||
|
||||
def moduleReports(confReport: ConfigurationResolveReport): Seq[ModuleReport] =
|
||||
for (revId <- confReport.getModuleRevisionIds.toArray collect { case revId: ModuleRevisionId => revId }) yield artifactReports(toModuleID(revId), confReport getDownloadReports revId)
|
||||
for {
|
||||
revId <- confReport.getModuleRevisionIds.toArray.toVector collect { case revId: ModuleRevisionId => revId }
|
||||
} yield moduleRevisionDetail(confReport, confReport.getDependency(revId))
|
||||
|
||||
@deprecated("Internal only. No longer in use.", "0.13.6")
|
||||
def artifactReports(mid: ModuleID, artReport: Seq[ArtifactDownloadReport]): ModuleReport =
|
||||
{
|
||||
val (resolved, missing) = artifacts(mid, artReport)
|
||||
ModuleReport(mid, resolved, missing)
|
||||
}
|
||||
|
||||
private[sbt] def artifacts(mid: ModuleID, artReport: Seq[ArtifactDownloadReport]): (Seq[(Artifact, File)], Seq[Artifact]) =
|
||||
{
|
||||
val missing = new mutable.ListBuffer[Artifact]
|
||||
val resolved = new mutable.ListBuffer[(Artifact, File)]
|
||||
for (r <- artReport) {
|
||||
val file = r.getLocalFile
|
||||
val fileOpt = Option(r.getLocalFile)
|
||||
val art = toArtifact(r.getArtifact)
|
||||
if (file eq null)
|
||||
missing += art
|
||||
else
|
||||
resolved += ((art, file))
|
||||
fileOpt match {
|
||||
case Some(file) => resolved += ((art, file))
|
||||
case None => missing += art
|
||||
}
|
||||
}
|
||||
new ModuleReport(mid, resolved.toSeq, missing.toSeq)
|
||||
(resolved.toSeq, missing.toSeq)
|
||||
}
|
||||
|
||||
// We need this because current module report used as part of UpdateReport/ConfigurationReport contains
|
||||
// only the revolved modules.
|
||||
// Sometimes the entire module can be excluded via rules etc.
|
||||
private[sbt] def details(confReport: ConfigurationResolveReport): Seq[ModuleDetailReport] = {
|
||||
val dependencies = confReport.getModuleRevisionIds.toArray.toVector collect { case revId: ModuleRevisionId => revId }
|
||||
val moduleIds = confReport.getModuleIds.toArray.toVector collect { case mId: IvyModuleId => mId }
|
||||
def moduleDetail(mid: IvyModuleId): ModuleDetailReport = {
|
||||
val deps = confReport.getNodes(mid).toArray.toVector collect { case node: IvyNode => node }
|
||||
new ModuleDetailReport(mid.getOrganisation, mid.getName, deps map { moduleRevisionDetail(confReport, _) })
|
||||
}
|
||||
moduleIds map { moduleDetail }
|
||||
}
|
||||
|
||||
private[sbt] def moduleRevisionDetail(confReport: ConfigurationResolveReport, dep: IvyNode): ModuleReport = {
|
||||
def toExtraAttributes(ea: ju.Map[_, _]): Map[String, String] =
|
||||
Map(ea.entrySet.toArray collect {
|
||||
case entry: ju.Map.Entry[_, _] => (entry.getKey.toString, entry.getValue.toString)
|
||||
}: _*)
|
||||
def toCaller(caller: IvyCaller): Caller = {
|
||||
val m = toModuleID(caller.getModuleRevisionId)
|
||||
val callerConfigurations = caller.getCallerConfigurations.toArray.toVector
|
||||
val extraAttributes = toExtraAttributes(caller.getDependencyDescriptor.getExtraAttributes)
|
||||
new Caller(m, callerConfigurations, extraAttributes)
|
||||
}
|
||||
val revId = dep.getResolvedId
|
||||
val moduleId = toModuleID(revId)
|
||||
val branch = Some(revId.getBranch)
|
||||
val (status, publicationDate, resolver, artifactResolver) = dep.isLoaded match {
|
||||
case true =>
|
||||
(Some(dep.getDescriptor.getStatus),
|
||||
Some(new ju.Date(dep.getPublication)),
|
||||
Some(dep.getModuleRevision.getResolver.getName),
|
||||
Some(dep.getModuleRevision.getArtifactResolver.getName))
|
||||
case _ => (None, None, None, None)
|
||||
}
|
||||
val (evicted, evictedData, evictedReason) = dep.isEvicted(confReport.getConfiguration) match {
|
||||
case true =>
|
||||
val ed = dep.getEvictedData(confReport.getConfiguration)
|
||||
(true,
|
||||
Some(Option(ed.getConflictManager) map { _.toString } getOrElse { "transitive" }),
|
||||
Option(ed.getDetail))
|
||||
case _ => (false, None, None)
|
||||
}
|
||||
val problem = dep.hasProblem match {
|
||||
case true => Some(dep.getProblem.getMessage)
|
||||
case _ => None
|
||||
}
|
||||
val mdOpt = for {
|
||||
mr <- Option(dep.getModuleRevision)
|
||||
md <- Option(mr.getDescriptor)
|
||||
} yield md
|
||||
val homepage = mdOpt match {
|
||||
case Some(md) =>
|
||||
Option(md.getHomePage)
|
||||
case _ => None
|
||||
}
|
||||
val extraAttributes: Map[String, String] = toExtraAttributes(mdOpt match {
|
||||
case Some(md) => md.getExtraAttributes
|
||||
case _ => dep.getResolvedId.getExtraAttributes
|
||||
})
|
||||
val isDefault = Option(dep.getDescriptor) map { _.isDefault }
|
||||
val configurations = dep.getConfigurations(confReport.getConfiguration).toArray.toList
|
||||
val licenses: Seq[(String, URL)] = mdOpt match {
|
||||
case Some(md) => md.getLicenses.toArray.toVector collect { case lic: IvyLicense => (lic.getName, new URL(lic.getUrl)) }
|
||||
case _ => Nil
|
||||
}
|
||||
val callers = dep.getCallers(confReport.getConfiguration).toArray.toVector map { toCaller }
|
||||
val (resolved, missing) = artifacts(moduleId, confReport getDownloadReports revId)
|
||||
|
||||
new ModuleReport(moduleId, resolved, missing, status, publicationDate, resolver, artifactResolver,
|
||||
evicted, evictedData, evictedReason, problem, homepage, extraAttributes, isDefault, branch,
|
||||
configurations, licenses, callers)
|
||||
}
|
||||
|
||||
def evicted(confReport: ConfigurationResolveReport): Seq[ModuleID] =
|
||||
confReport.getEvictedNodes.map(node => toModuleID(node.getId))
|
||||
|
||||
|
|
@ -50,7 +136,7 @@ object IvyRetrieve {
|
|||
def updateStats(report: ResolveReport): UpdateStats =
|
||||
new UpdateStats(report.getResolveTime, report.getDownloadTime, report.getDownloadSize, false)
|
||||
def configurationReport(confReport: ConfigurationResolveReport): ConfigurationReport =
|
||||
new ConfigurationReport(confReport.getConfiguration, moduleReports(confReport), evicted(confReport))
|
||||
new ConfigurationReport(confReport.getConfiguration, moduleReports(confReport), details(confReport), evicted(confReport))
|
||||
|
||||
/**
|
||||
* Tries to find Ivy graph path the from node to target.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.{ util => ju }
|
||||
|
||||
/**
|
||||
* Provides information about dependency resolution.
|
||||
|
|
@ -37,9 +39,18 @@ final class UpdateReport(val cachedDescriptor: File, val configurations: Seq[Con
|
|||
/**
|
||||
* Provides information about resolution of a single configuration.
|
||||
* @param configuration the configuration this report is for.
|
||||
* @param modules a seqeuence containing one report for each module resolved for this configuration.
|
||||
* @param modules a sequence containing one report for each module resolved for this configuration.
|
||||
* @param details a sequence containing one report for each org/name, which may or may not be part of the final resolution.
|
||||
* @param evicted a sequence of evicted modules
|
||||
*/
|
||||
final class ConfigurationReport(val configuration: String, val modules: Seq[ModuleReport], val evicted: Seq[ModuleID]) {
|
||||
final class ConfigurationReport(
|
||||
val configuration: String,
|
||||
val modules: Seq[ModuleReport],
|
||||
val details: Seq[ModuleDetailReport],
|
||||
val evicted: Seq[ModuleID]) {
|
||||
// def this(configuration: String, modules: Seq[ModuleReport], evicted: Seq[ModuleID]) =
|
||||
// this(configuration, modules, Nil, evicted)
|
||||
|
||||
override def toString = "\t" + configuration + ":\n" + modules.mkString + evicted.map("\t\t(EVICTED) " + _ + "\n").mkString
|
||||
|
||||
/**
|
||||
|
|
@ -50,25 +61,94 @@ final class ConfigurationReport(val configuration: String, val modules: Seq[Modu
|
|||
private[this] def addConfiguration(mod: ModuleID): ModuleID = if (mod.configurations.isEmpty) mod.copy(configurations = Some(configuration)) else mod
|
||||
|
||||
def retrieve(f: (String, ModuleID, Artifact, File) => File): ConfigurationReport =
|
||||
new ConfigurationReport(configuration, modules map { _.retrieve((mid, art, file) => f(configuration, mid, art, file)) }, evicted)
|
||||
new ConfigurationReport(configuration, modules map { _.retrieve((mid, art, file) => f(configuration, mid, art, file)) }, details, evicted)
|
||||
}
|
||||
|
||||
/**
|
||||
* In sbt's terminology, "module" consists of organization, name, and version.
|
||||
* In Ivy's, "module" means just organization and name, and the one including version numbers
|
||||
* are called revisions.
|
||||
*/
|
||||
final class ModuleDetailReport(
|
||||
val organization: String,
|
||||
val name: String,
|
||||
val modules: Seq[ModuleReport]) {
|
||||
override def toString: String =
|
||||
{ s"$organization:$name" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides information about the resolution of a module.
|
||||
* This information is in the context of a specific configuration.
|
||||
* @param module the `ModuleID` this report is for.
|
||||
* @param artifacts the resolved artifacts for this module, paired with the File the artifact was retrieved to. This File may be in the
|
||||
* @param artifacts the resolved artifacts for this module, paired with the File the artifact was retrieved to.
|
||||
* @param missingArtifacts the missing artifacts for this module.
|
||||
*/
|
||||
final class ModuleReport(val module: ModuleID, val artifacts: Seq[(Artifact, File)], val missingArtifacts: Seq[Artifact]) {
|
||||
override def toString =
|
||||
{
|
||||
val arts = artifacts.map(_.toString) ++ missingArtifacts.map(art => "(MISSING) " + art)
|
||||
"\t\t" + module + ": " +
|
||||
(if (arts.size <= 1) "" else "\n\t\t\t") + arts.mkString("\n\t\t\t") + "\n"
|
||||
}
|
||||
final class ModuleReport(
|
||||
val module: ModuleID,
|
||||
val artifacts: Seq[(Artifact, File)],
|
||||
val missingArtifacts: Seq[Artifact],
|
||||
val status: Option[String],
|
||||
val publicationDate: Option[ju.Date],
|
||||
val resolver: Option[String],
|
||||
val artifactResolver: Option[String],
|
||||
val evicted: Boolean,
|
||||
val evictedData: Option[String],
|
||||
val evictedReason: Option[String],
|
||||
val problem: Option[String],
|
||||
val homepage: Option[String],
|
||||
val extraAttributes: Map[String, String],
|
||||
val isDefault: Option[Boolean],
|
||||
val branch: Option[String],
|
||||
val configurations: Seq[String],
|
||||
val licenses: Seq[(String, URL)],
|
||||
val callers: Seq[Caller]) {
|
||||
|
||||
override def toString = {
|
||||
val arts = artifacts.map(_.toString) ++ missingArtifacts.map(art => "(MISSING) " + art)
|
||||
s"\t\t$module: " +
|
||||
(if (arts.size <= 1) "" else "\n\t\t\t") + arts.mkString("\n\t\t\t") + "\n"
|
||||
}
|
||||
def retrieve(f: (ModuleID, Artifact, File) => File): ModuleReport =
|
||||
new ModuleReport(module, artifacts.map { case (art, file) => (art, f(module, art, file)) }, missingArtifacts)
|
||||
copy(artifacts = artifacts.map { case (art, file) => (art, f(module, art, file)) })
|
||||
|
||||
private[sbt] def copy(
|
||||
module: ModuleID = module,
|
||||
artifacts: Seq[(Artifact, File)] = artifacts,
|
||||
missingArtifacts: Seq[Artifact] = missingArtifacts,
|
||||
status: Option[String] = status,
|
||||
publicationDate: Option[ju.Date] = publicationDate,
|
||||
resolver: Option[String] = resolver,
|
||||
artifactResolver: Option[String] = artifactResolver,
|
||||
evicted: Boolean = evicted,
|
||||
evictedData: Option[String] = evictedData,
|
||||
evictedReason: Option[String] = evictedReason,
|
||||
problem: Option[String] = problem,
|
||||
homepage: Option[String] = homepage,
|
||||
extraAttributes: Map[String, String] = extraAttributes,
|
||||
isDefault: Option[Boolean] = isDefault,
|
||||
branch: Option[String] = branch,
|
||||
configurations: Seq[String] = configurations,
|
||||
licenses: Seq[(String, URL)] = licenses,
|
||||
callers: Seq[Caller] = callers): ModuleReport =
|
||||
new ModuleReport(module, artifacts, missingArtifacts, status, publicationDate, resolver, artifactResolver,
|
||||
evicted, evictedData, evictedReason, problem, homepage, extraAttributes, isDefault, branch, configurations, licenses, callers)
|
||||
}
|
||||
|
||||
object ModuleReport {
|
||||
def apply(module: ModuleID, artifacts: Seq[(Artifact, File)], missingArtifacts: Seq[Artifact]): ModuleReport =
|
||||
new ModuleReport(module, artifacts, missingArtifacts, None, None, None, None,
|
||||
false, None, None, None, None, Map(), None, None, Nil, Nil, Nil)
|
||||
}
|
||||
|
||||
final class Caller(
|
||||
val caller: ModuleID,
|
||||
val callerConfigurations: Seq[String],
|
||||
val callerExtraAttributes: Map[String, String]) {
|
||||
override def toString: String =
|
||||
s"$caller"
|
||||
}
|
||||
|
||||
object UpdateReport {
|
||||
implicit def richUpdateReport(report: UpdateReport): RichUpdateReport = new RichUpdateReport(report)
|
||||
|
||||
|
|
@ -101,15 +181,18 @@ object UpdateReport {
|
|||
/** Constructs a new report that only contains files matching the specified filter.*/
|
||||
def filter(f: DependencyFilter): UpdateReport =
|
||||
moduleReportMap { (configuration, modReport) =>
|
||||
import modReport._
|
||||
val newArtifacts = artifacts filter { case (art, file) => f(configuration, module, art) }
|
||||
val newMissing = missingArtifacts filter { art => f(configuration, module, art) }
|
||||
new ModuleReport(module, newArtifacts, newMissing)
|
||||
modReport.copy(
|
||||
artifacts = modReport.artifacts filter { case (art, file) => f(configuration, modReport.module, art) },
|
||||
missingArtifacts = modReport.missingArtifacts filter { art => f(configuration, modReport.module, art) }
|
||||
)
|
||||
}
|
||||
def substitute(f: (String, ModuleID, Seq[(Artifact, File)]) => Seq[(Artifact, File)]): UpdateReport =
|
||||
moduleReportMap { (configuration, modReport) =>
|
||||
val newArtifacts = f(configuration, modReport.module, modReport.artifacts)
|
||||
new ModuleReport(modReport.module, newArtifacts, Nil)
|
||||
modReport.copy(
|
||||
artifacts = f(configuration, modReport.module, modReport.artifacts),
|
||||
missingArtifacts = Nil
|
||||
)
|
||||
}
|
||||
|
||||
def toSeq: Seq[(String, ModuleID, Artifact, File)] =
|
||||
|
|
@ -120,8 +203,9 @@ object UpdateReport {
|
|||
|
||||
def addMissing(f: ModuleID => Seq[Artifact]): UpdateReport =
|
||||
moduleReportMap { (configuration, modReport) =>
|
||||
import modReport._
|
||||
new ModuleReport(module, artifacts, (missingArtifacts ++ f(module)).distinct)
|
||||
modReport.copy(
|
||||
missingArtifacts = (modReport.missingArtifacts ++ f(modReport.module)).distinct
|
||||
)
|
||||
}
|
||||
|
||||
def moduleReportMap(f: (String, ModuleReport) => ModuleReport): UpdateReport =
|
||||
|
|
@ -129,7 +213,7 @@ object UpdateReport {
|
|||
val newConfigurations = report.configurations.map { confReport =>
|
||||
import confReport._
|
||||
val newModules = modules map { modReport => f(configuration, modReport) }
|
||||
new ConfigurationReport(configuration, newModules, evicted)
|
||||
new ConfigurationReport(configuration, newModules, details, evicted)
|
||||
}
|
||||
new UpdateReport(report.cachedDescriptor, newConfigurations, report.stats, report.stamps)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Predef.{ Map, Set, implicitly } // excludes *both 2.10.x conforms and 2.1
|
|||
|
||||
import FileInfo.{ exists, hash }
|
||||
import java.io.File
|
||||
import java.{ util => ju }
|
||||
import java.net.URL
|
||||
import Types.{ :+:, idFun }
|
||||
import scala.xml.NodeSeq
|
||||
|
|
@ -62,16 +63,23 @@ object CacheIvy {
|
|||
}
|
||||
implicit def updateStatsFormat: Format[UpdateStats] =
|
||||
wrap[UpdateStats, (Long, Long, Long)](us => (us.resolveTime, us.downloadTime, us.downloadSize), { case (rt, dt, ds) => new UpdateStats(rt, dt, ds, true) })
|
||||
implicit def confReportFormat(implicit m: Format[String], mr: Format[Seq[ModuleReport]], mi: Format[Seq[ModuleID]]): Format[ConfigurationReport] =
|
||||
wrap[ConfigurationReport, (String, Seq[ModuleReport], Seq[ModuleID])](r => (r.configuration, r.modules, r.evicted), { case (c, m, v) => new ConfigurationReport(c, m, v) })
|
||||
implicit def moduleReportFormat(implicit ff: Format[File]): Format[ModuleReport] =
|
||||
wrap[ModuleReport, (ModuleID, Seq[(Artifact, File)], Seq[Artifact])](m => (m.module, m.artifacts, m.missingArtifacts), { case (m, as, ms) => new ModuleReport(m, as, ms) })
|
||||
implicit def confReportFormat(implicit m: Format[String], mr: Format[Seq[ModuleReport]], mdr: Format[Seq[ModuleDetailReport]], mi: Format[Seq[ModuleID]]): Format[ConfigurationReport] =
|
||||
wrap[ConfigurationReport, (String, Seq[ModuleReport], Seq[ModuleDetailReport], Seq[ModuleID])](r => (r.configuration, r.modules, r.details, r.evicted), { case (c, m, d, v) => new ConfigurationReport(c, m, d, v) })
|
||||
implicit def moduleReportFormat(implicit cf: Format[Seq[Caller]], ff: Format[File]): Format[ModuleReport] = {
|
||||
wrap[ModuleReport, (ModuleID, Seq[(Artifact, File)], Seq[Artifact], Option[String], Option[Long], Option[String], Option[String], Boolean, Option[String], Option[String], Option[String], Option[String], Map[String, String], Option[Boolean], Option[String], Seq[String], Seq[(String, URL)], Seq[Caller])](
|
||||
m => (m.module, m.artifacts, m.missingArtifacts, m.status, m.publicationDate map { _.getTime }, m.resolver, m.artifactResolver, m.evicted, m.evictedData, m.evictedReason, m.problem, m.homepage, m.extraAttributes, m.isDefault, m.branch, m.configurations, m.licenses, m.callers),
|
||||
{ case (m, as, ms, s, pd, r, a, e, ed, er, p, h, ea, d, b, cs, ls, ks) => new ModuleReport(m, as, ms, s, pd map { new ju.Date(_) }, r, a, e, ed, er, p, h, ea, d, b, cs, ls, ks) })
|
||||
}
|
||||
implicit def artifactFormat(implicit sf: Format[String], uf: Format[Option[URL]]): Format[Artifact] = {
|
||||
wrap[Artifact, (String, String, String, Option[String], Seq[Configuration], Option[URL], Map[String, String])](
|
||||
a => (a.name, a.`type`, a.extension, a.classifier, a.configurations.toSeq, a.url, a.extraAttributes),
|
||||
{ case (n, t, x, c, cs, u, e) => Artifact(n, t, x, c, cs, u, e) }
|
||||
)
|
||||
}
|
||||
implicit def moduleDetailReportFormat(implicit sf: Format[String], bf: Format[Boolean], df: Format[Seq[ModuleReport]]): Format[ModuleDetailReport] =
|
||||
wrap[ModuleDetailReport, (String, String, Seq[ModuleReport])](m => (m.organization, m.name, m.modules), { case (o, n, r) => new ModuleDetailReport(o, n, r) })
|
||||
implicit def callerFormat: Format[Caller] =
|
||||
wrap[Caller, (ModuleID, Seq[String], Map[String, String])](c => (c.caller, c.callerConfigurations, c.callerExtraAttributes), { case (c, cc, ea) => new Caller(c, cc, ea) })
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue