Variants represent different versions or aspects of a component, like api
vs implementation
. Attributes define which variant is selected based on the consumer’s requirements.
For example, a library may have an api
and an implementation
variant. Here, the consumer wants an external implementation
variant:
configurations {
implementation {
attributes {
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
}
}
}
For example, a build might have debug
and release
variants. This selects the debug
variant based on the attribute.
configurations {
compileClasspath {
attributes {
attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))
}
}
}
Attributes help Gradle match the right variant by comparing the requested attributes with what’s available:
attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))
This sets the TargetConfiguration.TARGET_ATTRIBUTE
to "debug"
, meaning Gradle will attempt to resolve dependencies that have a "debug" variant, instead of other available variants (like "release").
As a user of Gradle, attributes are often hidden as implementation details. But it might be useful to understand the standard attributes defined by Gradle and its core plugins.
As a plugin author, these attributes, and the way they are defined, can serve as a basis for building your own set of attributes in your ecosystem plugin.
Ecosystem-independent standard attributes Attribute name Description Values compatibility and disambiguation rulesIndicates main purpose of variant
Usage
values built from constants defined in Usage
Following ecosystem semantics (e.g. java-runtime
can be used in place of java-api
but not the opposite)
Indicates the category of this software component
Category
values built from constants defined in Category
Following ecosystem semantics (e.g. library
is default on the JVM, no compatibility otherwise)
Indicates the contents of a org.gradle.category=library
variant
LibraryElements
values built from constants defined in LibraryElements
Following ecosystem semantics(e.g. in the JVM world, jar
is the default and is compatible with classes
)
Indicates the contents of a org.gradle.category=documentation
variant
DocsType
values built from constants defined in DocsType
No default, no compatibility
org.gradle.dependency.bundling
Indicates how dependencies of a variant are accessed.
Bundling
values built from constants defined in Bundling
Following ecosystem semantics (e.g. in the JVM world, embedded
is compatible with external
)
Indicates what kind of verification task produced this output.
VerificationType
values built from constants defined in VerificationType
No default, no compatibility
When the Category
attribute is present with the incubating value org.gradle.category=verification
on a variant, that variant is considered to be a verification-time only variant.
These variants are meant to contain only the results of running verification tasks, such as test results or code coverage reports. They are not publishable, and will produce an error if added to a component which is published.
Attribute name Description Values compatibility and disambiguation rulesorg.gradle.status
Component level attribute, derived
Based on a status scheme, with a default one existing based on the source repository.
Based on the scheme in use
JVM ecosystem specific attributesIn addition to the ecosystem independent attributes defined above, the JVM ecosystem adds the following attribute:
Attribute name Description Values compatibility and disambiguation rulesIndicates the JVM version compatibility.
Integer using the version after the 1.
for Java 1.4 and before, the major version for Java 5 and beyond.
Defaults to the JVM version used by Gradle, lower is compatible with higher, prefers highest compatible.
Indicates that a variant is optimized for a certain JVM environment.
Common values are standard-jvm
and android
. Other values are allowed.
The attribute is used to prefer one variant over another if multiple are available, but in general all values are compatible. The default is standard-jvm
.
Indicates the name of the TestSuite that produced this output.
Value is the name of the Suite.
No default, no compatibility
The JVM ecosystem also contains a number of compatibility and disambiguation rules over the different attributes. The reader willing to know more can take a look at the code for org.gradle.api.internal.artifacts.JavaEcosystemSupport
.
For Gradle plugin development, the following attribute is supported since Gradle 7.0. A Gradle plugin variant can specify compatibility with a Gradle API version through this attribute.
Attribute name Description Values compatibility and disambiguation rulesIndicates the Gradle API version compatibility.
Valid Gradle version strings.
Defaults to the currently running Gradle, lower is compatible with higher, prefers highest compatible.
Using a standard attributeFor this example, letâs assume you are creating a library with different variants for different JVM versions.
lib/build.gradle.kts
plugins {
id("java-library")
}
configurations {
named("apiElements") {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
lib/build.gradle
plugins {
id 'java-library'
}
configurations {
apiElements {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
In the consumer project (that uses the library), you can specify the JVM version attribute when declaring dependencies.
consumer/build.gradle.kts
plugins {
id("application")
}
dependencies {
implementation(project(":lib")) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
consumer/build.gradle
plugins {
id 'application'
}
dependencies {
implementation(project(':lib')) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
By defining and using the JVM version attribute, you ensure that your library and its consumers are compatible with the specified JVM version. Essentially, this ensures that Gradle resolves to the variant that matches the desired JVM version.
Viewing and debugging attributesThe dependencyInsight
task is useful for inspecting specific dependencies and their attributes, including how they are resolved:
$ ./gradlew dependencyInsight --configuration compileClasspath --dependency com.example:your-library
> Task :dependencyInsight
com.example:your-library:1.0 (compileClasspath)
variant "apiElements" [
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-api]
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-runtime]
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.JavaLanguageVersion = [1.8]
]
variant "runtimeElements" [
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-runtime]
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.JavaLanguageVersion = [1.8]
]
Selection reasons:
- By constraint from configuration ':compileClasspath'
- Declared in build.gradle.kts
Resolved to:
com.example:your-library:1.0 (runtime)
Additional Information:
- Dependency declared in the 'implementation' configuration
- No matching variants found for the requested attributes in the 'compileClasspath' configuration
Declaring custom attributes
When extending Gradle with custom attributes, itâs important to consider their long-term impact, especially if you plan to publish libraries. Custom attributes allow you to integrate variant-aware dependency management in your plugin, but libraries using these attributes must also ensure consumers can interpret them correctly. This is typically done by applying the corresponding plugin, which defines compatibility and disambiguation rules.
If your plugin is publicly available and libraries are published to public repositories, introducing new attributes becomes a significant responsibility. Published attributes must remain supported or have a compatibility layer in future versions of the plugin to ensure backward compatibility.
Hereâs an example of declaring and using custom attributes in a Gradle plugin:
lib/build.gradle.kts
// Define a custom attribute
val myAttribute = Attribute.of("com.example.my-attribute", String::class.java)
configurations {
create("myConfig") {
// Set custom attribute
attributes {
attribute(myAttribute, "special-value")
}
}
}
dependencies {
// Apply the custom attribute to a dependency
add("myConfig","com.google.guava:guava:31.1-jre") {
attributes {
attribute(myAttribute, "special-value")
}
}
}
lib/build.gradle
// Define a custom attribute
def myAttribute = Attribute.of("com.example.my-attribute", String)
// Create a custom configuration
configurations {
create("myConfig") {
// Set custom attribute
attributes {
attribute(myAttribute, "special-value")
}
}
}
dependencies {
// Apply the custom attribute to a dependency
add("myConfig", "com.google.guava:guava:31.1-jre") {
attributes {
attribute(myAttribute, "special-value")
}
}
}
In this example: - A custom attribute my-attribute
is defined. - The attribute is set on a custom configuration (myConfig
). - When adding a dependency, the custom attribute is applied to match the configuration.
If publishing a library with this attribute, ensure that consumers apply the plugin that understands and handles my-attribute
.
Attributes are typed. An attribute can be created via the Attribute<T>.of
method:
build.gradle.kts
// An attribute of type `String`
val myAttribute = Attribute.of("my.attribute.name", String::class.java)
// An attribute of type `Usage`
val myUsage = Attribute.of("my.usage.attribute", Usage::class.java)
build.gradle
// An attribute of type `String`
def myAttribute = Attribute.of("my.attribute.name", String)
// An attribute of type `Usage`
def myUsage = Attribute.of("my.usage.attribute", Usage)
Attribute types support most Java primitive classes; such as String
and Integer
. Or anything extending org.gradle.api.Named
.
Attributes should always be declared in the attribute schema found on the dependencies
handler:
build.gradle.kts
dependencies.attributesSchema {
// registers this attribute to the attributes schema
attribute(myAttribute)
attribute(myUsage)
}
build.gradle
dependencies.attributesSchema {
// registers this attribute to the attributes schema
attribute(myAttribute)
attribute(myUsage)
}
Registering an attribute with the schema is required in order to use Compatibility and Disambiguation rules that can resolve ambiguity between multiple selectable variants during Attribute Matching.
Each configuration has a container of attributes. Attributes can be configured to set values:
build.gradle.kts
configurations {
create("myConfiguration") {
attributes {
attribute(myAttribute, "my-value")
}
}
}
build.gradle
configurations {
myConfiguration {
attributes {
attribute(myAttribute, 'my-value')
}
}
}
For attributes which type extends Named
, the value of the attribute must be created via the object factory:
build.gradle.kts
configurations {
"myConfiguration" {
attributes {
attribute(myUsage, project.objects.named(Usage::class.java, "my-value"))
}
}
}
build.gradle
configurations {
myConfiguration {
attributes {
attribute(myUsage, project.objects.named(Usage, 'my-value'))
}
}
}
Dealing with attribute matching
In Gradle, attribute matching and attribute disambiguation are key mechanisms for resolving dependencies with varying attributes.
Attribute matching allows Gradle to select compatible dependency variants based on predefined rules, even if an exact match isn’t available. Attribute disambiguation, on the other hand, helps Gradle choose the most suitable variant when multiple compatible options exist.
Attribute compatibility rulesAttributes let the engine select compatible variants. There are cases where a producer may not have exactly what the consumer requests but has a variant that can be used.
This example defines and registers a custom compatibility rule to ensure that dependencies are selected based on their compatibility with specific Java versions:
lib/build.gradle.kts
// Define the compatibility rule class
class TargetJvmVersionCompatibilityRule : AttributeCompatibilityRule<Int> {
// Implement the execute method which will check compatibility
override fun execute(details: CompatibilityCheckDetails<Int>) {
// Switch case to check the consumer value for supported Java versions
when (details.consumerValue) {
8, 11 -> details.compatible() // Compatible with Java 8 and 11
else -> details.incompatible()
}
}
}
// Register the compatibility rule within the dependencies block
dependencies {
attributesSchema {
// Add the compatibility rule for the TargetJvmVersion attribute
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE) {
// Add the defined compatibility rule to this attribute
compatibilityRules.add(TargetJvmVersionCompatibilityRule::class.java)
}
}
}
lib/build.gradle
// Define the compatibility rule
class TargetJvmVersionCompatibilityRule implements AttributeCompatibilityRule<Integer> {
@Override
void execute(CompatibilityCheckDetails<Integer> details) {
switch (details.consumerValue) {
case 8:
case 11:
details.compatible() // Compatible with Java 8 and 11
break
default:
details.incompatible("Unsupported Java version: ${details.consumerValue}")
}
}
}
// Register a compatibility rule
dependencies {
attributesSchema {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE) {
compatibilityRules.add(TargetJvmVersionCompatibilityRule)
}
}
}
Gradle provides attribute compatibility rules that can be defined for each attribute. The role of a compatibility rule is to explain which attribute values are compatible based on what the consumer asked for.
Attribute compatibility rules have to be registered via the attributes schema.
Attribute disambiguation rulesWhen multiple variants of a dependency are compatible with the consumer’s requested attributes, Gradle needs to decide which variant to select. This process of determining the "best" candidate among compatible options is called attribute disambiguation.
In Gradle, different variants might satisfy the consumer’s request, but not all are equal. For example, you might have several versions of a library that are compatible with a Java version requested by the consumer. Disambiguation helps Gradle choose the most appropriate one based on additional criteria.
You can define disambiguation rules to guide Gradle in selecting the most suitable variant when multiple candidates are found. This is done by implementing an AttributeDisambiguationRule and registering it via the attributes schema within the dependencies
block:
consumer/build.gradle.kts
dependencies {
attributesSchema {
attribute(Usage.USAGE_ATTRIBUTE) { (1)
disambiguationRules.add(CustomDisambiguationRule::class.java) (2)
}
}
}
abstract class CustomDisambiguationRule @Inject constructor(
private val objects: ObjectFactory
) : AttributeDisambiguationRule<Usage> {
override fun execute(details: MultipleCandidatesDetails<Usage>) {
// Prefer the JAVA_API usage over others (e.g., JAVA_RUNTIME) when multiple candidates exist
details.closestMatch(objects.named(Usage::class.java, Usage.JAVA_API)) (3)
}
}
consumer/build.gradle
dependencies {
attributesSchema {
attribute(Usage.USAGE_ATTRIBUTE) { (1)
disambiguationRules.add(CustomDisambiguationRule) (2)
}
}
}
abstract class CustomDisambiguationRule implements AttributeDisambiguationRule<Usage> {
private final ObjectFactory objects
@Inject
CustomDisambiguationRule(ObjectFactory objects) {
this.objects = objects
}
@Override
void execute(MultipleCandidatesDetails<Usage> details) {
// Prefer the JAVA_API usage over others (e.g., JAVA_RUNTIME) when multiple candidates exist
details.closestMatch(objects.named(Usage, Usage.JAVA_API)) (3)
}
}
Attribute Selection: Specify the attribute to disambiguate (e.g., Usage.USAGE_ATTRIBUTE
, a built-in Gradle attribute).
Rule Registration: Register the custom disambiguation rule using disambiguationRules.add()
within the attributesSchema
block of dependencies
.
Custom Logic: Implement the AttributeDisambiguationRule
interface in a separate class. The execute
method defines how Gradle selects among candidates. In this example, it prefers the JAVA_API
usage.
Attribute disambiguation rules must be registered via the attribute matching strategy, which is accessed through the attributes schema in the DependencyHandler. This ensures the rules apply globally to dependency resolution.
Mapping from Maven/Ivy to Gradle variantsNeither Maven nor Ivy have the concept of variants, which are only natively supported by Gradle Module Metadata. Gradle can still work with Maven and Ivy by using different variant derivation strategies.
Relationship with Gradle Module Metadata
Gradle Module Metadata is a metadata format for modules published on Maven, Ivy and other kinds of repositories. It is similar to the pom.xml
or ivy.xml
metadata file, but this format contains details about variants.
See the {metadata-file-spec}[Gradle Module Metadata specification] for more information.
Mapping of Maven POM metadata to variantsModules published to a Maven repository are automatically converted into variant-aware modules when resolved by Gradle.
There is no way for Gradle to know which kind of component was published:
a BOM that represents a Gradle platform
a BOM used as a super-POM
a POM that is both a platform and a library
The default strategy used by Java projects in Gradle is to derive 8 different variants:
two "library" variants (attribute org.gradle.category
= library
)
the compile
variant maps the <scope>compile</scope>
dependencies. This variant is equivalent to the apiElements
variant of the Java Library plugin. All dependencies of this scope are considered API dependencies.
the runtime
variant maps both the <scope>compile</scope>
and <scope>runtime</scope>
dependencies. This variant is equivalent to the runtimeElements
variant of the Java Library plugin. All dependencies of those scopes are considered runtime dependencies.
in both cases, the <dependencyManagement>
dependencies are not converted to constraints
a "sources" variant that represents the sources jar for the component
a "javadoc" variant that represents the javadoc jar for the component
four "platform" variants derived from the <dependencyManagement>
block (attribute org.gradle.category
= platform
):
the platform-compile
variant maps the <scope>compile</scope>
dependency management dependencies as dependency constraints.
the platform-runtime
variant maps both the <scope>compile</scope>
and <scope>runtime</scope>
dependency management dependencies as dependency constraints.
the enforced-platform-compile
is similar to platform-compile
but all the constraints are forced
the enforced-platform-runtime
is similar to platform-runtime
but all the constraints are forced
You can understand more about the use of platform and enforced platforms variants by looking at the importing BOMs section of the manual. By default, whenever you declare a dependency on a Maven module, Gradle is going to look for the library
variants. However, using the platform
or enforcedPlatform
keyword, Gradle is now looking for one of the "platform" variants, which allows you to import the constraints from the POM files, instead of the dependencies.
Gradle has no built-in derivation strategy implemented for Ivy files. Ivy is a flexible format that allows you to publish arbitrary files and can be heavily customized.
If you want to implement a derivation strategy for compile and runtime variants for Ivy, you can do so with component metadata rule. The component metadata rules API allows you to access Ivy configurations and create variants based on them. If you know that all the Ivy modules your are consuming have been published with Gradle without further customizations of the ivy.xml
file, you can add the following rule to your build:
The rule creates an apiElements
variant based on the compile
configuration and a runtimeElements
variant based on the default
configuration of each ivy module. For each variant, it sets the corresponding Java ecosystem attributes. Dependencies and artifacts of the variants are taken from the underlying configurations. If not all consumed Ivy modules follow this pattern, the rule can be adjusted or only applied to a selected set of modules.
For all Ivy modules without variants, Gradle has a fallback selection method. Gradle does not perform variant aware resolution and instead selects either the default
configuration or an explicitly named configuration.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.3