# 7. Variant Aware Dependency Resolution

version 8.12

In Gradle, dependency resolution is often thought of from the standpoint of a consumer and a producer. The consumer declares dependencies and performs dependency resolution, while producers satisfy those dependencies by exposing variants.

Gradle’s resolution engine follows a dynamic approach to dependency resolution called variant-aware resolution, where the consumer defines requirements using attributes, which are matched with the attributes declared by the producer.

Variant-aware resolution allows Gradle to automatically select the correct variant from a producer without the consumer explicitly specifying which one to use.

For instance, if you’re working with different architectures (like `arm64` and `i386`), Gradle can choose the appropriate version of a library (`myLib`) for each architecture:

1. The producer, `myLib`, exposes variants (`arm64Elements`, `i386Elements`) with specific attributes (e.g., `ArchType.ARM64`, `ArchType.I386`).

2. The consumer, `myApp`, specifies the required attributes (e.g., `ArchType.ARM64`) in its resolvable configuration (`runtimeClasspath`).

3. If the consumer, `myApp`, requires dependencies for the `arm64` architecture, Gradle will automatically pick the `arm64Elements` variant from the `myLib` producer and use its corresponding artifact.

## A coded example

Consider a Java library where you create a new variant called `instrumentedJars` and want to ensure it’s selected for testing:

1. Producer Project: Creates a specialized `instrumentedJars` variant marked with specific attributes.

2. Consumer Project: Configured to request the `instrumented-jar` attribute for testing.

Let’s look at the build files of the producer and consumer.

## The producer side

### 1. Create an instrumented JAR:

Our Java library has a task called `instrumentedJar` which produces a JAR file. We expect other projects to consume this JAR file.

producer/build.gradle.kts

    val instrumentedJar = tasks.register("instrumentedJar", Jar::class) {
        archiveClassifier = "instrumented"
    }

producer/build.gradle

    def instrumentedJar = tasks.register("instrumentedJar", Jar) {
        archiveClassifier = "instrumented"
    }

### 2. Create a custom outgoing configuration:

We want our instrumented classes to be used when executing tests, so we need to define proper attributes on our variant. We create a new configuration named `instrumentedJars`. This configuration:

- Can be consumed by other projects.

- Cannot be resolved (i.e., it’s meant to be used as an output, not an input).

- Has specific attributes, including `LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE` set to "instrumented-jar", which explains what the variant contains.

producer/build.gradle.kts

    val instrumentedJars by configurations.creating {
        isCanBeConsumed = true
        isCanBeResolved = false
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
            attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, JavaVersion.current().majorVersion.toInt())
            attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named("instrumented-jar"))
        }
    }

producer/build.gradle

    configurations {
        instrumentedJars {
            canBeConsumed = true
            canBeResolved = false
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
                attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
                attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL))
                attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, JavaVersion.current().majorVersion.toInteger())
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-jar'))
            }
        }
    }

### 3. Attach the Artifact:

The `instrumentedJar` task’s output is added to the `instrumentedJars` configuration as an artifact. When this variant is included in a dependency graph, this artifact will be resolved during artifact resolution.

producer/build.gradle.kts

    artifacts {
        add("instrumentedJars", instrumentedJar)
    }

producer/build.gradle

    artifacts {
        instrumentedJars(instrumentedJar)
    }

What we have done here is that we have added a new variant, which can be used at runtime, but contains instrumented classes instead of the normal classes. However, it now means that for runtime, the consumer has to choose between two variants:

1. `runtimeElements`, the regular variant offered by the `java-library` plugin

2. `instrumentedJars`, the variant we have created

## The consumer side

### 1. Add dependencies:

First, on the consumer side, like any other project, we define the Java library as a dependency:

consumer/build.gradle.kts

    dependencies {
        testImplementation("junit:junit:4.13")
        testImplementation(project(":producer"))
    }

consumer/build.gradle

    dependencies {
        testImplementation 'junit:junit:4.13'
        testImplementation project(':producer')
    }

At this point, Gradle will still select the default `runtimeElements` variant for your dependencies. This is because the `testRuntimeClasspath` configuration is requesting artifacts with the `jar` library elements attribute, while the producer defines the `instrumentedJars` variant with a different attribute.

### 2. Adjust the requested attributes:

The `testRuntimeClasspath` configuration is modified to ask for "instrumented-jar" versions of the dependencies. This means that when Gradle resolves dependencies for this configuration, it will prefer JAR files that are marked as "instrumented":

consumer/build.gradle.kts

    configurations {
        testRuntimeClasspath {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class.java, "instrumented-jar"))
            }
        }
    }

consumer/build.gradle

    configurations {
        testRuntimeClasspath {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-jar'))
            }
        }
    }

By following these steps, Gradle will intelligently select the correct variants based on the configuration and attributes, while also handling cases where specialized variants are not available.