Gradle provides an extensive API for navigating, inspecting and post-processing metadata and artifacts of resolved dependencies.

The main entry point for this functionality is the Configuration API. To learn more about the fundamentals of configurations, see Managing Dependency Configurations.

Iterating over dependencies assigned to a configuration

Sometimes you’ll want to implement logic based on the dependencies declared in the build script of a project e.g. to inspect them in a Gradle plugin. You can iterate over the set of dependencies assigned to a configuration with the help of the method Configuration.getDependencies(). Alternatively, you can also use Configuration.getAllDependencies() to include the dependencies declared in superconfigurations. These APIs only return the declared dependencies and do not trigger dependency resolution. Therefore, the dependency sets do not include transitive dependencies. Calling the APIs during the configuration phase of the build lifecycle does not result in a significant performance impact.

Example 1. Iterating over the dependencies assigned to a configuration
build.gradle
task iterateDeclaredDependencies {
    doLast {
        DependencySet dependencySet = configurations.scm.dependencies

        dependencySet.each {
            logger.quiet "$it.group:$it.name:$it.version"
        }
    }
}
build.gradle.kts
task("iterateDeclaredDependencies") {
    doLast {
        val dependencySet = configurations["scm"].dependencies

        dependencySet.forEach {
            logger.quiet("${it.group}:${it.name}:${it.version}")
        }
    }
}

Iterating over artifacts resolved for a module

None of the dependency reporting helps you with inspecting or further processing the underlying, resolved artifacts of a module. A typical use case for accessing the artifacts is to copy them into a specific directory or filter out files of interest based on a specific file extension.

You can iterate over the complete set of artifacts resolved for a module with the help of the method FileCollection.getFiles(). Every file instance returned from the method points to its location in the dependency cache. Using this method on a Configuration instance is possible as the interface extends FileCollection.

Example 2. Iterating over the artifacts resolved for a module
build.gradle
task iterateResolvedArtifacts {
    dependsOn configurations.scm

    doLast {
        configurations.scm.each {
            logger.quiet it.absolutePath
        }
    }
}
build.gradle.kts
task("iterateResolvedArtifacts") {
    val scm = configurations["scm"]
    dependsOn(scm)

    doLast {
        scm.forEach {
            logger.quiet(it.absolutePath)
        }
    }
}

Iterating over the artifacts of a module automatically resolves the configuration. A resolved configuration becomes immutable and cannot add or remove dependencies. If needed you can copy a configuration for further modification via Configuration.copy().

As a plugin developer, you may want to navigate the full graph of dependencies assigned to a configuration e.g. for turning the dependency graph into a visualization. You can access the full graph of dependencies for a configuration with the help of the ResolutionResult.

The resolution result provides various methods for accessing the resolved and unresolved dependencies. For demonstration purposes the sample code uses ResolutionResult.getRoot() to access the root node the resolved dependency graph. Each dependency of this component returns an instance of ResolvedDependencyResult or UnresolvedDependencyResult providing detailed information about the node.

Example 3. Walking the resolved and unresolved dependencies of a configuration
build.gradle
task walkDependencyGraph(type: DependencyGraphWalk) {
    dependsOn configurations.scm
}

class DependencyGraphWalk extends DefaultTask {
    @TaskAction
    void walk() {
        Configuration configuration = project.configurations.scm
        ResolutionResult resolutionResult = configuration.incoming.resolutionResult
        ResolvedComponentResult root = resolutionResult.root
        logger.quiet configuration.name
        traverseDependencies(0, root.dependencies)
    }

    private void traverseDependencies(int level, Set<? extends DependencyResult> results) {
        for (DependencyResult result : results) {
            if (result instanceof ResolvedDependencyResult) {
                ResolvedComponentResult componentResult = result.selected
                ComponentIdentifier componentIdentifier = componentResult.id
                String node = calculateIndentation(level) + "- $componentIdentifier.displayName ($componentResult.selectionReason)"
                logger.quiet node
                traverseDependencies(level + 1, componentResult.dependencies)
            } else if (result instanceof UnresolvedDependencyResult) {
                ComponentSelector componentSelector = result.attempted
                String node = calculateIndentation(level) + "- $componentSelector.displayName (failed)"
                logger.quiet node
            }
        }
    }

    private String calculateIndentation(int level) {
        '     ' * level
    }
}
build.gradle.kts
task<DependencyGraphWalk>("walkDependencyGraph") {
    dependsOn(configurations["scm"])
}

open class DependencyGraphWalk: DefaultTask() {
    @TaskAction
    fun walk() {
        val configuration: Configuration = project.configurations.getByName("scm")
        val resolutionResult: ResolutionResult = configuration.incoming.resolutionResult
        val root: ResolvedComponentResult = resolutionResult.root
        logger.quiet(configuration.name)
        traverseDependencies(0, root.dependencies)
    }

    private fun traverseDependencies(level: Int, results: Set<DependencyResult>) {
        results.forEach { result ->
            if (result is ResolvedDependencyResult) {
                val componentResult: ResolvedComponentResult = result.selected
                val componentIdentifier: ComponentIdentifier = componentResult.id
                val node: String = "${calculateIndentation(level)}- ${componentIdentifier.displayName} (${componentResult.selectionReason})"
                logger.quiet(node)
                traverseDependencies(level + 1, componentResult.dependencies)
            } else if (result is UnresolvedDependencyResult) {
                val componentSelector: ComponentSelector = result.attempted
                val node: String = "${calculateIndentation(level)}- ${componentSelector.displayName} (failed)"
                logger.quiet(node)
            }
        }
    }

    private fun calculateIndentation(level: Int) = "     ".repeat(level)
}

Accessing a module’s metadata file

As part of the dependency resolution process, Gradle downloads the metadata file of a module and stores it in the dependency cache. Some organizations enforce strong restrictions on accessing repositories outside of internal network. Instead of downloading artifacts, those organizations prefer to provide an "installable" Gradle cache with all artifacts contained in it to fulfill the build’s dependency requirements.

The artifact query API provides access to the raw files of a module. Currently, it allows getting a handle to the metadata file and some selected, additional artifacts (e.g. a JVM-based module’s source and Javadoc files). The main API entry point is ArtifactResolutionQuery.

Let’s say you wanted to post-process the metadata file of a Maven module. The group, name and version of the module component serve as input to the artifact resolution query. After executing the query, you get a handle to all components that match the criteria and their underlying files. Additionally, it’s very easy to post-process the metadata file. The example code uses Groovy’s XmlSlurper to ask for POM element values.

Example 4. Accessing a Maven module’s metadata artifact
build.gradle
plugins {
    id 'java-library'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.google.guava:guava:18.0'
}

task printGuavaMetadata {
    dependsOn configurations.compileClasspath

    doLast {
        ArtifactResolutionQuery query = dependencies.createArtifactResolutionQuery()
            .forModule('com.google.guava', 'guava', '18.0')
            .withArtifacts(MavenModule, MavenPomArtifact)
        ArtifactResolutionResult result = query.execute()

        for(component in result.resolvedComponents) {
            Set<ArtifactResult> mavenPomArtifacts = component.getArtifacts(MavenPomArtifact)
            ArtifactResult guavaPomArtifact = mavenPomArtifacts.find { it.file.name == 'guava-18.0.pom' }
            def xml = new XmlSlurper().parse(guavaPomArtifact.file)
            println guavaPomArtifact.file.name
            println xml.name
            println xml.description
        }
    }
}
build.gradle.kts
import groovy.util.XmlSlurper

plugins {
    `java-library`
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.google.guava:guava:18.0")
}

task("printGuavaMetadata") {
    dependsOn(configurations.compileClasspath)

    doLast {
        val query: ArtifactResolutionQuery = dependencies.createArtifactResolutionQuery()
            .forModule("com.google.guava", "guava", "18.0")
            .withArtifacts(MavenModule::class, MavenPomArtifact::class)
        val result: ArtifactResolutionResult = query.execute()

        result.resolvedComponents.forEach { component ->
            val mavenPomArtifacts: Set<ArtifactResult> = component.getArtifacts(MavenPomArtifact::class)
            val guavaPomArtifact =
                mavenPomArtifacts.find { it is ResolvedArtifactResult && it.file.name == "guava-18.0.pom" } as ResolvedArtifactResult
            val xml = XmlSlurper().parse(guavaPomArtifact.file)
            println(guavaPomArtifact.file.name)
            println(xml.getProperty("name"))
            println(xml.getProperty("description"))
        }
    }
}