圖表解析階段的輸出是一個完全解析的 相依性圖表,它被用作Artifact 解析階段的輸入。

您可以在了解相依性解析模型中了解圖表是如何建構的。

ResolutionResult API 提供了對已解析相依性圖表的訪問權限,而無需觸發 Artifact 解析。此 API 呈現已解析的相依性圖表,其中圖表中的每個節點都是組件的變體。

原始存取相依性圖表對於許多用例可能很有用

  • 視覺化相依性圖表,例如為 Graphviz 產生 .dot 檔案。

  • 公開關於給定解析的診斷資訊,類似於 dependenciesdependencyInsight 任務。

  • 當與 ArtifactView API 結合使用時,解析相依性圖表的 Artifact 子集。

考慮以下函數,它遍歷相依性圖表,從根節點開始。回呼會針對圖表中的每個節點和邊緣發出通知。此函數可以用作任何需要遍歷相依性圖表的使用案例的基礎

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,而是會寫入輸出檔案。有關宣告任務輸入和輸出的更多詳細資訊,請參閱編寫任務章節。

當我們註冊任務時,我們使用 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

請注意,圖表對於兩種表示方式都是相同的。

下一步: 了解 Artifact 解析 >>