fix: Handle relocated dependencies in dependencyTree (#8400) (#8489)

This commit is contained in:
calm 2026-01-11 18:27:11 -08:00 committed by GitHub
parent 21b5c3b8df
commit e7323171a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 151 additions and 1 deletions

View File

@ -50,7 +50,22 @@ object SbtUpdateReport {
val (nodes, edges) = report.details.flatMap(moduleEdges).unzip
val root = Module(rootInfo)
val allNodes = root +: nodes
val flatEdges = edges.flatten
val existingNodeIds = allNodes.map(_.id).toSet
ModuleGraph(root +: nodes, edges.flatten)
// Handle relocated dependencies where the caller node doesn't exist (#8400)
val fixedEdges = flatEdges.flatMap { case edge @ (from, to) =>
if (existingNodeIds.contains(from)) Seq(edge)
else {
val callersOfMissing = flatEdges.collect {
case (caller, target) if target == from => caller
}
if (callersOfMissing.isEmpty) Seq(Edge(root.id, to))
else callersOfMissing.map(caller => Edge(caller, to))
}
}
ModuleGraph(allNodes, fixedEdges.distinct)
}
}

View File

@ -0,0 +1,122 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.graph.backend
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import sbt.internal.graph.GraphModuleId
import sbt.librarymanagement.*
class SbtUpdateReportTest extends AnyFlatSpec with Matchers {
def caller(org: String, name: String, version: String): Caller =
Caller(
ModuleID(org, name, version),
Vector.empty,
Map.empty,
isForceDependency = false,
isChangingDependency = false,
isTransitiveDependency = false,
isDirectlyForceDependency = false
)
def moduleReport(
org: String,
name: String,
version: String,
callers: Vector[Caller] = Vector.empty
): ModuleReport =
ModuleReport(
ModuleID(org, name, version),
artifacts = Vector.empty,
missingArtifacts = Vector.empty
).withCallers(callers)
// #8400
"fromConfigurationReport" should "handle relocated direct dependencies" in {
val root = ModuleID("test", "test-project_2.12", "0.1.0-SNAPSHOT")
val relocatedReport = moduleReport(
"at.yawk.lz4",
"lz4-java",
"1.8.1",
callers = Vector(caller("org.lz4", "lz4-java", "1.8.1"))
)
val orgArtReport =
OrganizationArtifactReport("at.yawk.lz4", "lz4-java", Vector(relocatedReport))
val configReport = ConfigurationReport(
ConfigRef("compile"),
modules = Vector(relocatedReport),
details = Vector(orgArtReport)
)
val graph = SbtUpdateReport.fromConfigurationReport(configReport, root)
graph.nodes.size shouldBe 2
val rootId = GraphModuleId("test", "test-project_2.12", "0.1.0-SNAPSHOT")
val relocatedId = GraphModuleId("at.yawk.lz4", "lz4-java", "1.8.1")
graph.edges should contain((rootId, relocatedId))
graph.dependencyMap(rootId).map(_.id) should contain(relocatedId)
}
it should "handle normal dependencies without relocation" in {
val root = ModuleID("test", "test-project_2.12", "0.1.0-SNAPSHOT")
val normalReport = moduleReport(
"org.example",
"example-lib",
"1.0.0",
callers = Vector(caller("test", "test-project_2.12", "0.1.0-SNAPSHOT"))
)
val orgArtReport =
OrganizationArtifactReport("org.example", "example-lib", Vector(normalReport))
val configReport = ConfigurationReport(
ConfigRef("compile"),
modules = Vector(normalReport),
details = Vector(orgArtReport)
)
val graph = SbtUpdateReport.fromConfigurationReport(configReport, root)
graph.nodes.size shouldBe 2
val rootId = GraphModuleId("test", "test-project_2.12", "0.1.0-SNAPSHOT")
val depId = GraphModuleId("org.example", "example-lib", "1.0.0")
graph.edges should contain((rootId, depId))
}
it should "handle transitive relocated dependencies" in {
val root = ModuleID("test", "test-project_2.12", "0.1.0-SNAPSHOT")
val depA = moduleReport(
"org.example",
"dep-a",
"1.0.0",
callers = Vector(caller("test", "test-project_2.12", "0.1.0-SNAPSHOT"))
)
val relocatedB = moduleReport(
"new.group",
"dep-b",
"2.0.0",
callers = Vector(caller("old.group", "dep-b", "2.0.0"))
)
val orgArtReportA = OrganizationArtifactReport("org.example", "dep-a", Vector(depA))
val orgArtReportB = OrganizationArtifactReport("new.group", "dep-b", Vector(relocatedB))
val configReport = ConfigurationReport(
ConfigRef("compile"),
modules = Vector(depA, relocatedB),
details = Vector(orgArtReportA, orgArtReportB)
)
val graph = SbtUpdateReport.fromConfigurationReport(configReport, root)
graph.nodes.size shouldBe 3
val rootId = GraphModuleId("test", "test-project_2.12", "0.1.0-SNAPSHOT")
val depAId = GraphModuleId("org.example", "dep-a", "1.0.0")
val relocatedBId = GraphModuleId("new.group", "dep-b", "2.0.0")
graph.edges should contain((rootId, depAId))
graph.edges should contain((rootId, relocatedBId))
}
}

View File

@ -0,0 +1,11 @@
// #8400
scalaVersion := "2.12.21"
libraryDependencies += "org.lz4" % "lz4-java" % "1.8.1"
TaskKey[Unit]("check") := {
val content = IO.read(new File("target/tree.txt"))
assert(
content.contains("at.yawk.lz4:lz4-java:1.8.1"),
s"Expected relocated dependency in tree:\n$content"
)
}

View File

@ -0,0 +1,2 @@
> dependencyTree --out target/tree.txt
> check