# Dependency Graph Resolution

version 8.12.1

The output of the graph resolution phase is a fully resolved dependency graph, which is used as the input to the artifact resolution phase.

The `ResolutionResult` API provides access to the resolved dependency graph without triggering artifact resolution. This API presents the resolved dependency graph, where each node in the graph is a variant of a component.

Raw access to the dependency graph can be useful for a number of use cases:

- Visualizing the dependency graph, for example generating a `.dot` file for Graphviz.

- Exposing diagnostics about a given resolution, similar to the `dependencies` or `dependencyInsight` tasks.

- Resolving a subset of the artifacts for a dependency graph when used in conjunction with the `ArtifactView` API.

Consider the following function that traverses a dependency graph, starting from the root node. Callbacks are notified for each node and edge in the graph. This function can be used as a base for any use case that requires traversing a dependency graph:

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)
                }
            }
        }
    }

This function starts at the root variant, and performs a breadth-first traversal of the graph. The `ResolutionResult` API is lenient, so it is important to check whether a visited edge is unresolved (failed) or resolved. With this function, the node callback is always called before the edge callback for any given node.

Below, we leverage the above traversal function to transform a dependency graph into a `.dot` file for visualization:

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}\""
        }
    }

| A proper implementation would not use `println` but would write to an output file. For more details on declaring task inputs and outputs, see the Writing Tasks section.

When we register the task, we use the `ResolutionResult` API to access the root component and root variant of the `runtimeClasspath` configuration:

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
    }

| This example uses incubating APIs.

Running this task, we get the following output:

    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"
    }

Compare this to the output of the `dependencies` task:

    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

Notice how the graph is the same for both representations.