相依性圖表解析
版本 8.13
圖表解析階段的輸出是一個完全解析的 相依性圖表,它被用作Artifact 解析階段的輸入。
您可以在了解相依性解析模型中了解圖表是如何建構的。
ResolutionResult
API 提供了對已解析相依性圖表的訪問權限,而無需觸發 Artifact 解析。此 API 呈現已解析的相依性圖表,其中圖表中的每個節點都是組件的變體。
原始存取相依性圖表對於許多用例可能很有用
-
視覺化相依性圖表,例如為 Graphviz 產生
.dot
檔案。 -
公開關於給定解析的診斷資訊,類似於
dependencies
或dependencyInsight
任務。 -
當與
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 解析 >>