依赖关系图解析
版本 8.13
图解析 阶段的输出是一个完全解析的 依赖关系图,它被用作 工件解析 阶段的输入。
您可以在 理解依赖关系解析模型 中了解图是如何构建的。
ResolutionResult
API 提供了对已解析依赖关系图的访问,而无需触发工件解析。此 API 展示了已解析的依赖关系图,其中图中的每个节点都是组件的变体。
对依赖关系图的原始访问对于许多用例可能很有用
-
可视化依赖关系图,例如为 Graphviz 生成
.dot
文件。 -
公开关于给定解析的 诊断信息,类似于
dependencies
或dependencyInsight
任务。 -
当与
ArtifactView
API 结合使用时,解析依赖关系图的工件子集。
考虑以下函数,该函数遍历依赖关系图,从根节点开始。回调函数会为图中的每个节点和边收到通知。此函数可以用作任何需要遍历依赖关系图的用例的基础
build.gradle.kts
fun traverseGraph(
rootComponent: ResolvedComponentResult,
rootVariant: ResolvedVariantResult,
nodeCallback: (ResolvedVariantResult) -> Unit,
edgeCallback: (ResolvedVariantResult, ResolvedVariantResult) -> Unit
) {
val seen = mutableSetOf<ResolvedVariantResult>(rootVariant)
nodeCallback(rootVariant)
val queue = ArrayDeque(listOf(rootVariant to rootComponent))
while (queue.isNotEmpty()) {
val (variant, component) = queue.removeFirst()
// Traverse this variant's dependencies
component.getDependenciesForVariant(variant).forEach { dependency ->
val resolved = when (dependency) {
is ResolvedDependencyResult -> dependency
is UnresolvedDependencyResult -> throw dependency.failure
else -> throw AssertionError("Unknown dependency type: $dependency")
}
if (!resolved.isConstraint) {
val toVariant = resolved.resolvedVariant
if (seen.add(toVariant)) {
nodeCallback(toVariant)
queue.addLast(toVariant to resolved.selected)
}
edgeCallback(variant, toVariant)
}
}
}
}
build.gradle
void traverseGraph(
ResolvedComponentResult rootComponent,
ResolvedVariantResult rootVariant,
Consumer<ResolvedVariantResult> nodeCallback,
BiConsumer<ResolvedVariantResult, ResolvedVariantResult> edgeCallback
) {
Set<ResolvedVariantResult> seen = new HashSet<>()
seen.add(rootVariant)
nodeCallback(rootVariant)
def queue = new ArrayDeque<Tuple2<ResolvedVariantResult, ResolvedComponentResult>>()
queue.add(new Tuple2(rootVariant, rootComponent))
while (!queue.isEmpty()) {
def entry = queue.removeFirst()
def variant = entry.v1
def component = entry.v2
// Traverse this variant's dependencies
component.getDependenciesForVariant(variant).each { dependency ->
if (dependency instanceof UnresolvedDependencyResult) {
throw dependency.failure
}
if ((!dependency instanceof ResolvedDependencyResult)) {
throw new RuntimeException("Unknown dependency type: $dependency")
}
def resolved = dependency as ResolvedDependencyResult
if (!dependency.constraint) {
def toVariant = resolved.resolvedVariant
if (seen.add(toVariant)) {
nodeCallback(toVariant)
queue.add(new Tuple2(toVariant, resolved.selected))
}
edgeCallback(variant, toVariant)
}
}
}
}
此函数从根变体开始,并执行图的广度优先遍历。ResolutionResult
API 是宽松的,因此检查访问的边是否未解析(失败)或已解析非常重要。使用此函数,节点回调始终在任何给定节点的边回调之前调用。
下面,我们利用上述遍历函数将依赖关系图转换为用于可视化的 .dot
文件
build.gradle.kts
abstract class GenerateDot : DefaultTask() {
@get:Input
abstract val rootComponent: Property<ResolvedComponentResult>
@get:Input
abstract val rootVariant: Property<ResolvedVariantResult>
@TaskAction
fun traverse() {
println("digraph {")
traverseGraph(
rootComponent.get(),
rootVariant.get(),
{ node -> println(" ${toNodeId(node)} [shape=box]") },
{ from, to -> println(" ${toNodeId(from)} -> ${toNodeId(to)}") }
)
println("}")
}
fun toNodeId(variant: ResolvedVariantResult): String {
return "\"${variant.owner.displayName}:${variant.displayName}\""
}
}
build.gradle
abstract class GenerateDot extends DefaultTask {
@Input
abstract Property<ResolvedComponentResult> getRootComponent()
@Input
abstract Property<ResolvedVariantResult> getRootVariant()
@TaskAction
void traverse() {
println("digraph {")
traverseGraph(
rootComponent.get(),
rootVariant.get(),
node -> { println(" ${toNodeId(node)} [shape=box]") },
(from, to) -> { println(" ${toNodeId(from)} -> ${toNodeId(to)}") }
)
println("}")
}
String toNodeId(ResolvedVariantResult variant) {
return "\"${variant.owner.displayName}:${variant.displayName}\""
}
}
一个正确的实现不会使用 println ,而是会写入输出文件。有关声明任务输入和输出的更多详细信息,请参阅 编写 Task 部分。 |
当我们注册任务时,我们使用 ResolutionResult
API 访问 runtimeClasspath
配置的根组件和根变体
build.gradle.kts
tasks.register<GenerateDot>("generateDot") {
rootComponent = runtimeClasspath.flatMap {
it.incoming.resolutionResult.rootComponent
}
rootVariant = runtimeClasspath.flatMap {
it.incoming.resolutionResult.rootVariant
}
}
build.gradle
tasks.register("generateDot", GenerateDot) {
rootComponent = configurations.runtimeClasspath.incoming.resolutionResult.rootComponent
rootVariant = configurations.runtimeClasspath.incoming.resolutionResult.rootVariant
}
此示例使用孵化中的 API。 |
运行此任务,我们得到以下输出
digraph { "root project ::runtimeClasspath" [shape=box] "com.google.guava:guava:33.2.1-jre:jreRuntimeElements" [shape=box] "root project ::runtimeClasspath" -> "com.google.guava:guava:33.2.1-jre:jreRuntimeElements" "com.google.guava:failureaccess:1.0.2:runtime" [shape=box] "com.google.guava:guava:33.2.1-jre:jreRuntimeElements" -> "com.google.guava:failureaccess:1.0.2:runtime" "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:runtime" [shape=box] "com.google.guava:guava:33.2.1-jre:jreRuntimeElements" -> "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:runtime" "com.google.code.findbugs:jsr305:3.0.2:runtime" [shape=box] "com.google.guava:guava:33.2.1-jre:jreRuntimeElements" -> "com.google.code.findbugs:jsr305:3.0.2:runtime" "org.checkerframework:checker-qual:3.42.0:runtimeElements" [shape=box] "com.google.guava:guava:33.2.1-jre:jreRuntimeElements" -> "org.checkerframework:checker-qual:3.42.0:runtimeElements" "com.google.errorprone:error_prone_annotations:2.26.1:runtime" [shape=box] "com.google.guava:guava:33.2.1-jre:jreRuntimeElements" -> "com.google.errorprone:error_prone_annotations:2.26.1:runtime" }
将此与 dependencies
任务的输出进行比较
runtimeClasspath \--- com.google.guava:guava:33.2.1-jre +--- com.google.guava:failureaccess:1.0.2 +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava +--- com.google.code.findbugs:jsr305:3.0.2 +--- org.checkerframework:checker-qual:3.42.0 \--- com.google.errorprone:error_prone_annotations:2.26.1
请注意,对于两种表示形式,图都是相同的。
下一步: 了解工件解析 >>