图解析 阶段的输出是一个完全解析的 依赖关系图,它被用作 工件解析 阶段的输入。

您可以在 理解依赖关系解析模型 中了解图是如何构建的。

ResolutionResult API 提供了对已解析依赖关系图的访问,而无需触发工件解析。此 API 展示了已解析的依赖关系图,其中图中的每个节点都是组件的变体。

对依赖关系图的原始访问对于许多用例可能很有用

  • 可视化依赖关系图,例如为 Graphviz 生成 .dot 文件。

  • 公开关于给定解析的 诊断信息,类似于 dependenciesdependencyInsight 任务。

  • 当与 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

请注意,对于两种表示形式,图都是相同的。

下一步: 了解工件解析 >>