mirror of https://github.com/sbt/sbt.git
Fix UpdateReport to be compatible with dependency-graph (#156)
Fixes https://github.com/coursier/coursier/issues/1375 Ref https://github.com/sbt/sbt/issues/4706 / https://github.com/sbt/sbt/issues/4688 Ref https://github.com/jrudolph/sbt-dependency-graph/issues/178 Currently the UpdateReport returned by Coursier is missing callers from the direct dependencies. This is evident from the fact that `thisModule`'s information is not passed. Another missing information in the UpdateReport is ModuleReport that originates from subproject dependencies (aka inter-project dependencies). These two missing info result in broken rendering for sbt-dependency-graph. This commit attemps to fix them by passing them through to SbtUpdateReport. See the scripted test for confirmation.
This commit is contained in:
parent
de3b4f0458
commit
dc5b9ecd12
|
|
@ -65,10 +65,8 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen
|
||||||
val sbv = module0.scalaModuleInfo.map(_.scalaBinaryVersion).getOrElse {
|
val sbv = module0.scalaModuleInfo.map(_.scalaBinaryVersion).getOrElse {
|
||||||
sv.split('.').take(2).mkString(".")
|
sv.split('.').take(2).mkString(".")
|
||||||
}
|
}
|
||||||
|
val (mod, ver) = FromSbt.moduleVersion(module0.module, sv, sbv, optionalCrossVer = true)
|
||||||
val interProjectDependencies = {
|
val interProjectDependencies = {
|
||||||
val (mod, ver) = FromSbt.moduleVersion(module0.module, sv, sbv, optionalCrossVer = true)
|
|
||||||
|
|
||||||
val needed = conf.interProjectDependencies.exists { p =>
|
val needed = conf.interProjectDependencies.exists { p =>
|
||||||
p.module == mod && p.version == ver
|
p.module == mod && p.version == ver
|
||||||
}
|
}
|
||||||
|
|
@ -192,11 +190,13 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen
|
||||||
artifacts: Map[Artifact, File]
|
artifacts: Map[Artifact, File]
|
||||||
) =
|
) =
|
||||||
UpdateParams(
|
UpdateParams(
|
||||||
|
thisModule = (ToCoursier.module(mod), ver),
|
||||||
shadedConfigOpt = None,
|
shadedConfigOpt = None,
|
||||||
artifacts = artifacts,
|
artifacts = artifacts,
|
||||||
classifiers = classifiers,
|
classifiers = classifiers,
|
||||||
configs = configs,
|
configs = configs,
|
||||||
dependencies = dependencies,
|
dependencies = dependencies,
|
||||||
|
interProjectDependencies = interProjectDependencies,
|
||||||
res = resolutions,
|
res = resolutions,
|
||||||
includeSignatures = false,
|
includeSignatures = false,
|
||||||
sbtBootJarOverrides = sbtBootJarOverrides
|
sbtBootJarOverrides = sbtBootJarOverrides
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ private[internal] object SbtUpdateReport {
|
||||||
.withExtraAttributes(module.attributes ++ extraProperties)
|
.withExtraAttributes(module.attributes ++ extraProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val moduleReport = caching[(Dependency, Seq[(Dependency, Project)], Project, Seq[(Publication, Artifact, Option[File])]), ModuleReport] {
|
private val moduleReport = caching[(Dependency, Seq[(Dependency, ProjectInfo)], Project, Seq[(Publication, Artifact, Option[File])]), ModuleReport] {
|
||||||
case (dependency, dependees, project, artifacts) =>
|
case (dependency, dependees, project, artifacts) =>
|
||||||
|
|
||||||
val sbtArtifacts = artifacts.collect {
|
val sbtArtifacts = artifacts.collect {
|
||||||
|
|
@ -95,7 +95,7 @@ private[internal] object SbtUpdateReport {
|
||||||
Caller(
|
Caller(
|
||||||
moduleId((dependee, dependeeProj.version, Map.empty)),
|
moduleId((dependee, dependeeProj.version, Map.empty)),
|
||||||
// FIXME Shouldn't we only keep the configurations pulling dependency?
|
// FIXME Shouldn't we only keep the configurations pulling dependency?
|
||||||
dependeeProj.configurations.keys.toVector.map(c => ConfigRef(c.value)),
|
dependeeProj.configs,
|
||||||
dependee.module.attributes ++ dependeeProj.properties,
|
dependee.module.attributes ++ dependeeProj.properties,
|
||||||
// FIXME Set better values here
|
// FIXME Set better values here
|
||||||
isForceDependency = false,
|
isForceDependency = false,
|
||||||
|
|
@ -131,7 +131,10 @@ private[internal] object SbtUpdateReport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private def moduleReports(
|
private def moduleReports(
|
||||||
|
thisModule: (Module, String),
|
||||||
|
config: Configuration,
|
||||||
res: Resolution,
|
res: Resolution,
|
||||||
|
interProjectDependencies: Seq[Project],
|
||||||
classifiersOpt: Option[Seq[Classifier]],
|
classifiersOpt: Option[Seq[Classifier]],
|
||||||
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
|
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
|
||||||
log: Logger,
|
log: Logger,
|
||||||
|
|
@ -175,11 +178,15 @@ private[internal] object SbtUpdateReport {
|
||||||
.groupBy(_._1)
|
.groupBy(_._1)
|
||||||
.mapValues(_.map { case (_, attr, a) => (attr, a) })
|
.mapValues(_.map { case (_, attr, a) => (attr, a) })
|
||||||
.iterator
|
.iterator
|
||||||
.toMap
|
.toMap ++
|
||||||
|
Map(interProjectDependencies
|
||||||
|
.filter(p => p.module != thisModule._1)
|
||||||
|
.map(p => Dependency(p.module, p.version) -> Nil): _*)
|
||||||
|
|
||||||
val versions = res.dependencies.toVector.map { dep =>
|
val versions = (Vector(Dependency(thisModule._1, thisModule._2)) ++ res.dependencies.toVector ++ res.rootDependencies.toVector)
|
||||||
dep.module -> dep.version
|
.map { dep =>
|
||||||
}.toMap
|
dep.module -> dep.version
|
||||||
|
}.toMap
|
||||||
|
|
||||||
def clean(dep: Dependency): Dependency =
|
def clean(dep: Dependency): Dependency =
|
||||||
dep
|
dep
|
||||||
|
|
@ -187,6 +194,22 @@ private[internal] object SbtUpdateReport {
|
||||||
.withExclusions(Set.empty)
|
.withExclusions(Set.empty)
|
||||||
.withOptional(false)
|
.withOptional(false)
|
||||||
|
|
||||||
|
def lookupProject(mv: coursier.core.Resolution.ModuleVersion): Option[Project] =
|
||||||
|
res.projectCache.get(mv) match {
|
||||||
|
case Some((_, p)) => Some(p)
|
||||||
|
case _ =>
|
||||||
|
interProjectDependencies.find( p =>
|
||||||
|
mv == (p.module, p.version)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val m = Dependency(thisModule._1, "")
|
||||||
|
val directReverseDependencies = res.rootDependencies.toSet.map(clean).map(_.withVersion(""))
|
||||||
|
.map(
|
||||||
|
dep => dep -> Vector(m)
|
||||||
|
)
|
||||||
|
.toMap
|
||||||
|
|
||||||
val reverseDependencies = res.reverseDependencies
|
val reverseDependencies = res.reverseDependencies
|
||||||
.toVector
|
.toVector
|
||||||
.map { case (k, v) =>
|
.map { case (k, v) =>
|
||||||
|
|
@ -195,22 +218,28 @@ private[internal] object SbtUpdateReport {
|
||||||
.groupBy(_._1)
|
.groupBy(_._1)
|
||||||
.mapValues(_.flatMap(_._2))
|
.mapValues(_.flatMap(_._2))
|
||||||
.toVector
|
.toVector
|
||||||
.toMap
|
.toMap ++ directReverseDependencies
|
||||||
|
|
||||||
groupedDepArtifacts.map {
|
groupedDepArtifacts.map {
|
||||||
case (dep, artifacts) =>
|
case (dep, artifacts) =>
|
||||||
val (_, proj) = res.projectCache(dep.moduleVersion)
|
val proj = lookupProject(dep.moduleVersion).get
|
||||||
|
|
||||||
// FIXME Likely flaky...
|
// FIXME Likely flaky...
|
||||||
val dependees = reverseDependencies
|
val dependees = reverseDependencies
|
||||||
.getOrElse(clean(dep.withVersion("")), Vector.empty)
|
.getOrElse(clean(dep.withVersion("")), Vector.empty)
|
||||||
.map { dependee0 =>
|
.flatMap { dependee0 =>
|
||||||
val version = versions(dependee0.module)
|
val version = versions(dependee0.module)
|
||||||
val dependee = dependee0.withVersion(version)
|
val dependee = dependee0.withVersion(version)
|
||||||
val (_, dependeeProj) = res.projectCache(dependee.moduleVersion)
|
lookupProject(dependee.moduleVersion) match {
|
||||||
(dependee, dependeeProj)
|
case Some(dependeeProj) =>
|
||||||
|
Vector((dependee, ProjectInfo(
|
||||||
|
dependeeProj.version,
|
||||||
|
dependeeProj.configurations.keys.toVector.map(c => ConfigRef(c.value)),
|
||||||
|
dependeeProj.properties)))
|
||||||
|
case _ =>
|
||||||
|
Vector.empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleReport((
|
moduleReport((
|
||||||
dep,
|
dep,
|
||||||
dependees,
|
dependees,
|
||||||
|
|
@ -221,8 +250,10 @@ private[internal] object SbtUpdateReport {
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
|
thisModule: (Module, String),
|
||||||
configDependencies: Map[Configuration, Seq[Dependency]],
|
configDependencies: Map[Configuration, Seq[Dependency]],
|
||||||
resolutions: Map[Configuration, Resolution],
|
resolutions: Map[Configuration, Resolution],
|
||||||
|
interProjectDependencies: Vector[Project],
|
||||||
configs: Map[Configuration, Set[Configuration]],
|
configs: Map[Configuration, Set[Configuration]],
|
||||||
classifiersOpt: Option[Seq[Classifier]],
|
classifiersOpt: Option[Seq[Classifier]],
|
||||||
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
|
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
|
||||||
|
|
@ -241,7 +272,10 @@ private[internal] object SbtUpdateReport {
|
||||||
val subRes = resolutions(config).subset(configDeps)
|
val subRes = resolutions(config).subset(configDeps)
|
||||||
|
|
||||||
val reports = moduleReports(
|
val reports = moduleReports(
|
||||||
|
thisModule,
|
||||||
|
config,
|
||||||
subRes,
|
subRes,
|
||||||
|
interProjectDependencies,
|
||||||
classifiersOpt,
|
classifiersOpt,
|
||||||
artifactFileOpt,
|
artifactFileOpt,
|
||||||
log,
|
log,
|
||||||
|
|
@ -279,11 +313,12 @@ private[internal] object SbtUpdateReport {
|
||||||
val dep = Dependency(c.module, c.wantedVersion)
|
val dep = Dependency(c.module, c.wantedVersion)
|
||||||
val dependee = Dependency(c.dependeeModule, c.dependeeVersion)
|
val dependee = Dependency(c.dependeeModule, c.dependeeVersion)
|
||||||
val dependeeProj = subRes.projectCache
|
val dependeeProj = subRes.projectCache
|
||||||
.get((c.dependeeModule, c.dependeeVersion))
|
.get((c.dependeeModule, c.dependeeVersion)) match {
|
||||||
.map(_._2)
|
case Some((_, p)) =>
|
||||||
.getOrElse {
|
ProjectInfo(p.version, p.configurations.keys.toVector.map(c => ConfigRef(c.value)), p.properties)
|
||||||
// should not happen
|
case _ =>
|
||||||
Project(c.dependeeModule, c.dependeeVersion, Nil, Map(), None, Nil, Nil, Nil, None, None, None, false, None, Nil, coursier.core.Info.empty)
|
// should not happen
|
||||||
|
ProjectInfo(c.dependeeVersion, Vector.empty, Vector.empty)
|
||||||
}
|
}
|
||||||
val rep = moduleReport((dep, Seq((dependee, dependeeProj)), proj.withVersion(c.wantedVersion), Nil))
|
val rep = moduleReport((dep, Seq((dependee, dependeeProj)), proj.withVersion(c.wantedVersion), Nil))
|
||||||
.withEvicted(true)
|
.withEvicted(true)
|
||||||
|
|
@ -316,4 +351,5 @@ private[internal] object SbtUpdateReport {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private case class ProjectInfo(version: String, configs: Vector[ConfigRef], properties: Seq[(String, String)])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ import coursier.util.Artifact
|
||||||
|
|
||||||
// private[coursier]
|
// private[coursier]
|
||||||
final case class UpdateParams(
|
final case class UpdateParams(
|
||||||
|
thisModule: (Module, String),
|
||||||
shadedConfigOpt: Option[(String, Configuration)],
|
shadedConfigOpt: Option[(String, Configuration)],
|
||||||
artifacts: Map[Artifact, File],
|
artifacts: Map[Artifact, File],
|
||||||
classifiers: Option[Seq[Classifier]],
|
classifiers: Option[Seq[Classifier]],
|
||||||
configs: Map[Configuration, Set[Configuration]],
|
configs: Map[Configuration, Set[Configuration]],
|
||||||
dependencies: Seq[(Configuration, Dependency)],
|
dependencies: Seq[(Configuration, Dependency)],
|
||||||
|
interProjectDependencies: Seq[Project],
|
||||||
res: Map[Set[Configuration], Resolution],
|
res: Map[Set[Configuration], Resolution],
|
||||||
includeSignatures: Boolean,
|
includeSignatures: Boolean,
|
||||||
sbtBootJarOverrides: Map[(Module, String), File]
|
sbtBootJarOverrides: Map[(Module, String), File]
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,10 @@ object UpdateRun {
|
||||||
}
|
}
|
||||||
|
|
||||||
SbtUpdateReport(
|
SbtUpdateReport(
|
||||||
|
params.thisModule,
|
||||||
depsByConfig,
|
depsByConfig,
|
||||||
configResolutions,
|
configResolutions,
|
||||||
|
params.interProjectDependencies.toVector,
|
||||||
params.configs,
|
params.configs,
|
||||||
params.classifiers,
|
params.classifiers,
|
||||||
params.artifactFileOpt,
|
params.artifactFileOpt,
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,8 @@ object UpdateTasks {
|
||||||
val log = streams.value.log
|
val log = streams.value.log
|
||||||
|
|
||||||
val verbosityLevel = coursierVerbosity.value
|
val verbosityLevel = coursierVerbosity.value
|
||||||
|
val p = ToCoursier.project(currentProjectTask.value)
|
||||||
val dependencies = ToCoursier.project(currentProjectTask.value).dependencies
|
val dependencies = p.dependencies
|
||||||
val res = resTask.value
|
val res = resTask.value
|
||||||
|
|
||||||
val key = SbtCoursierCache.ReportKey(
|
val key = SbtCoursierCache.ReportKey(
|
||||||
|
|
@ -114,6 +114,8 @@ object UpdateTasks {
|
||||||
includeSignatures
|
includeSignatures
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val interProjectDependencies = coursierInterProjectDependencies.value.map(ToCoursier.project)
|
||||||
|
|
||||||
SbtCoursierCache.default.reportOpt(key) match {
|
SbtCoursierCache.default.reportOpt(key) match {
|
||||||
case Some(report) =>
|
case Some(report) =>
|
||||||
Def.task(report)
|
Def.task(report)
|
||||||
|
|
@ -125,11 +127,13 @@ object UpdateTasks {
|
||||||
val configs = configsTask.value
|
val configs = configsTask.value
|
||||||
|
|
||||||
val params = UpdateParams(
|
val params = UpdateParams(
|
||||||
|
(p.module, p.version),
|
||||||
shadedConfigOpt,
|
shadedConfigOpt,
|
||||||
artifactFilesOrErrors0,
|
artifactFilesOrErrors0,
|
||||||
classifiers,
|
classifiers,
|
||||||
configs,
|
configs,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
interProjectDependencies,
|
||||||
res,
|
res,
|
||||||
includeSignatures,
|
includeSignatures,
|
||||||
sbtBootJarOverrides
|
sbtBootJarOverrides
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
lazy val check = taskKey[Unit]("")
|
||||||
|
|
||||||
|
scalaVersion in ThisBuild := "2.12.8"
|
||||||
|
organization in ThisBuild := "com.example"
|
||||||
|
|
||||||
|
lazy val app = (project in file("app"))
|
||||||
|
.dependsOn(util)
|
||||||
|
.settings(
|
||||||
|
name := "app",
|
||||||
|
libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.3",
|
||||||
|
check := {
|
||||||
|
val ur = update.value
|
||||||
|
val cr = ur.configuration(Compile).get
|
||||||
|
// configuration report must include a module report for subproject dependency
|
||||||
|
val coreReport = cr.modules.find(m =>
|
||||||
|
m.module.name == "core_2.12"
|
||||||
|
).getOrElse(sys.error("report for core is missing"))
|
||||||
|
assert(coreReport.callers.exists(c => c.caller.name == "util_2.12"),
|
||||||
|
s"caller on core is missing util: ${coreReport.callers}")
|
||||||
|
|
||||||
|
// configuration report must include a module report for library dependency
|
||||||
|
val shapelessReport = cr.modules.find(m =>
|
||||||
|
m.module.name == "shapeless_2.12"
|
||||||
|
).getOrElse(sys.error("report for shapeless is missing"))
|
||||||
|
assert(shapelessReport.callers.exists(c => c.caller.name == "app_2.12"),
|
||||||
|
s"caller on shapeless is missing self module (app): ${shapelessReport.callers}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
lazy val util = (project in file("util"))
|
||||||
|
.dependsOn(core)
|
||||||
|
.settings(
|
||||||
|
name := "util"
|
||||||
|
)
|
||||||
|
|
||||||
|
lazy val core = (project in file("core"))
|
||||||
|
.settings(
|
||||||
|
name := "core"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
addSbtPlugin {
|
||||||
|
|
||||||
|
val name = sys.props.getOrElse(
|
||||||
|
"plugin.name",
|
||||||
|
sys.error("plugin.name Java property not set")
|
||||||
|
)
|
||||||
|
val version = sys.props.getOrElse(
|
||||||
|
"plugin.version",
|
||||||
|
sys.error("plugin.version Java property not set")
|
||||||
|
)
|
||||||
|
|
||||||
|
"io.get-coursier" % name % version
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
> check
|
||||||
Loading…
Reference in New Issue