变体 (Variants) 代表组件的不同版本或方面,例如 apiimplementation属性 (Attributes) 根据消费者的需求定义选择哪个变体。

例如,一个库可能有 apiimplementation 变体。此处,消费者需要一个外部的 implementation 变体

configurations {
    implementation {
        attributes {
            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
        }
    }
}

例如,一个构建可能有 debugrelease 变体。这会根据属性选择 debug 变体。

configurations {
    compileClasspath {
        attributes {
            attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))
        }
    }
}

属性 通过比较请求的属性与可用的属性,帮助 Gradle 匹配正确的变体

attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))

这会将 TargetConfiguration.TARGET_ATTRIBUTE 设置为 "debug",意味着 Gradle 将尝试解析具有 "debug" 变体的依赖项,而不是其他可用的变体(例如 "release")。

Gradle 定义的标准属性

作为 Gradle 用户,属性通常作为实现细节而被隐藏。但理解 Gradle 及其核心插件定义的标准属性可能会很有用。

作为插件作者,这些属性及其定义方式可以作为你在生态系统插件中构建自己的属性集的基础。

生态系统无关的标准属性

属性名称 描述 兼容性和消歧规则

org.gradle.usage

指示变体的主要用途

Usage 中定义的常量构建的 Usage

遵循生态系统语义(例如,java-runtime 可以替代 java-api 使用,反之则不行)

org.gradle.category

指示此软件组件的类别

Category 中定义的常量构建的 Category

遵循生态系统语义(例如,在 JVM 上 library 是默认值,否则不兼容)

org.gradle.libraryelements

指示 org.gradle.category=library 变体的内容

LibraryElements 中定义的常量构建的 LibraryElements

遵循生态系统语义(例如,在 JVM 世界中,jar 是默认值并与 classes 兼容)

org.gradle.docstype

指示 org.gradle.category=documentation 变体的内容

DocsType 中定义的常量构建的 DocsType

无默认值,不兼容

org.gradle.dependency.bundling

指示如何访问变体的依赖项。

Bundling 中定义的常量构建的 Bundling

遵循生态系统语义(例如,在 JVM 世界中,embeddedexternal 兼容)

org.gradle.verificationtype

指示生成此输出的是哪种验证任务。

VerificationType 中定义的常量构建的 VerificationType

无默认值,不兼容

当变体上存在具有孵化值 org.gradle.category=verificationCategory 属性时,该变体被视为仅用于验证时段的变体。

这些变体仅用于包含运行验证任务的结果,例如测试结果或代码覆盖率报告。它们不可发布,如果添加到已发布的组件中会产生错误。

属性名称 描述 兼容性和消歧规则

org.gradle.status

组件级别属性,派生

基于状态方案,默认方案基于源仓库。

基于正在使用的方案

JVM 生态系统特定属性

除了上面定义的生态系统无关属性外,JVM 生态系统还添加了以下属性

属性名称 描述 兼容性和消歧规则

org.gradle.jvm.version

指示 JVM 版本兼容性。

对于 Java 1.4 及以前的版本,使用 1. 后的版本号;对于 Java 5 及以后的版本,使用主版本号。

默认为 Gradle 使用的 JVM 版本,较低版本与较高版本兼容,优先选择最高兼容版本。

org.gradle.jvm.environment

指示变体已针对特定的 JVM 环境进行了优化。

常用值为 standard-jvmandroid。允许使用其他值。

如果有多个可用变体,此属性用于优先选择一个变体,但通常所有值都是兼容的。默认值为 standard-jvm

org.gradle.testsuite.name

指示生成此输出的 TestSuite 的名称。

值为 Suite 的名称。

无默认值,不兼容

JVM 生态系统还包含许多针对不同属性的兼容性和消歧规则。想要了解更多信息的读者可以查看 org.gradle.api.internal.artifacts.JavaEcosystemSupport 的代码。

原生生态系统特定属性

除了上面定义的生态系统无关属性外,原生生态系统还添加了以下属性

属性名称 描述 兼容性和消歧规则

org.gradle.native.debuggable

指示二进制文件是否包含调试符号构建

布尔值

不适用 (N/A)

org.gradle.native.optimized

指示二进制文件是否包含优化标志构建

布尔值

不适用 (N/A)

org.gradle.native.architecture

指示二进制文件的目标架构

MachineArchitecture 中定义的常量构建的 MachineArchitecture

org.gradle.native.operatingSystem

指示二进制文件的目标操作系统

OperatingSystemFamily 中定义的常量构建的 OperatingSystemFamily

Gradle 插件生态系统特定属性

对于 Gradle 插件开发,Gradle 7.0 开始支持以下属性。Gradle 插件变体可以通过此属性指定与 Gradle API 版本的兼容性。

属性名称 描述 兼容性和消歧规则

org.gradle.plugin.api‑version

指示 Gradle API 版本兼容性。

有效的 Gradle 版本字符串。

默认为当前运行的 Gradle,较低版本与较高版本兼容,优先选择最高兼容版本。

使用标准属性

对于此示例,假设你正在创建一个针对不同 JVM 版本具有不同变体的库。

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

在消费项目(使用该库的项目)中,你可以在声明依赖项时指定 JVM 版本属性。

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

通过定义和使用 JVM 版本属性,你可以确保你的库及其消费者与指定的 JVM 版本兼容。本质上,这确保了 Gradle 解析到与所需 JVM 版本匹配的变体。

查看和调试属性

dependencyInsight 任务对于检查特定依赖项及其属性(包括它们如何解析)非常有用

$ ./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

声明自定义属性

扩展 Gradle 时使用自定义属性,务必考虑它们的长期影响,特别是如果你计划发布库。自定义属性允许你在插件中集成变体感知的依赖管理,但使用这些属性的库也必须确保消费者能够正确解释它们。这通常通过应用相应的插件来完成,该插件定义了兼容性和消歧规则。

如果你的插件是公开可用的且库已发布到公共仓库,引入新属性将成为一项重大责任。发布的属性必须在插件的未来版本中保持支持或具有兼容层,以确保向后兼容性。

以下是在 Gradle 插件中声明和使用自定义属性的示例

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

在此示例中: - 定义了一个自定义属性 my-attribute。 - 该属性设置在自定义配置 (myConfig) 上。 - 添加依赖项时,应用自定义属性以匹配配置。

如果发布包含此属性的库,请确保消费者应用了理解和处理 my-attribute 的插件。

在构建脚本或插件中创建属性

属性是有类型的。可以通过 Attribute<T>.of 方法创建属性

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)

属性类型支持大多数 Java 原生类,例如 StringInteger。或任何扩展 org.gradle.api.Named 的类型。

属性应始终在 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)
}

必须向模式注册属性,才能使用兼容性和消歧规则,这些规则可以在属性匹配期间解决多个可选变体之间的歧义。

每个配置都有一个属性容器。可以配置属性来设置值

build.gradle.kts
configurations {
    create("myConfiguration") {
        attributes {
            attribute(myAttribute, "my-value")
        }
    }
}
build.gradle
configurations {
    myConfiguration {
        attributes {
            attribute(myAttribute, 'my-value')
        }
    }
}

对于类型扩展 Named 的属性,属性的值必须通过对象工厂创建

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

处理属性匹配

在 Gradle 中,属性匹配属性消歧是解析具有不同属性的依赖项的关键机制。

属性匹配允许 Gradle 根据预定义规则选择兼容的依赖项变体,即使没有完全匹配的变体。另一方面,属性消歧有助于 Gradle 在存在多个兼容选项时选择最合适的变体。

属性兼容性规则

属性让引擎能够选择兼容的变体。有些情况下,生产者可能没有消费者完全请求的内容,但有一个可用的变体。

此示例定义并注册了一个自定义兼容性规则,以确保根据依赖项与特定 Java 版本的兼容性来选择依赖项

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 提供了可以为每个属性定义的属性兼容性规则。兼容性规则的作用是解释哪些属性值基于消费者请求的内容是兼容的

属性兼容性规则必须通过属性模式注册。

属性消歧规则

当依赖项的多个变体与消费者请求的属性兼容时,Gradle 需要决定选择哪个变体。在兼容选项中确定“最佳”候选者的过程称为属性消歧

在 Gradle 中,不同的变体可能满足消费者的请求,但并非所有变体都相同。例如,你可能有多个与消费者请求的 Java 版本兼容的库版本。消歧有助于 Gradle 根据附加条件选择最合适的版本。

你可以定义消歧规则来指导 Gradle 在找到多个候选变体时选择最合适的变体。这可以通过实现 AttributeDisambiguationRule 并在 dependencies 块内的 attributes schema 中注册它来完成

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)
    }
}
  1. 属性选择:指定要消歧的属性(例如,Usage.USAGE_ATTRIBUTE,一个内置的 Gradle 属性)。

  2. 规则注册:在 dependenciesattributesSchema 块中使用 disambiguationRules.add() 注册自定义消歧规则。

  3. 自定义逻辑:在单独的类中实现 AttributeDisambiguationRule 接口。execute 方法定义了 Gradle 如何在候选者中进行选择。在此示例中,它优先选择 JAVA_API 用途。

属性消歧规则必须通过属性匹配策略注册,该策略通过DependencyHandler 中的属性模式访问。这确保了规则全局应用于依赖解析。

从 Maven/Ivy 映射到 Gradle 变体

Maven 和 Ivy 都没有变体的概念,变体仅受 Gradle 模块元数据原生支持。Gradle 仍然可以通过使用不同的变体派生策略来处理 Maven 和 Ivy。

与 Gradle 模块元数据的关系

Gradle 模块元数据是发布在 Maven、Ivy 和其他类型仓库上的模块的元数据格式。它类似于 pom.xmlivy.xml 元数据文件,但这种格式包含有关变体的详细信息。

有关更多信息,请参阅 {metadata-file-spec}[Gradle 模块元数据规范]。

Maven POM 元数据到变体的映射

发布到 Maven 仓库的模块在被 Gradle 解析时会自动转换为变体感知的模块。

Gradle 无法知道发布了哪种组件

  • 代表 Gradle 平台的 BOM

  • 用作超级 POM 的 BOM

  • 既是平台又是库的 POM

Gradle 中 Java 项目使用的默认策略是派生 8 种不同的变体

  • 两个“库”变体(属性 org.gradle.category = library

    • compile 变体映射 <scope>compile</scope> 依赖项。此变体等同于Java 库插件apiElements 变体。此范围内的所有依赖项都被视为API 依赖项

    • runtime 变体映射 <scope>compile</scope><scope>runtime</scope> 依赖项。此变体等同于Java 库插件runtimeElements 变体。这些范围内的所有依赖项都被视为运行时依赖项

      • 在这两种情况下,<dependencyManagement> 依赖项不会转换为约束

  • 代表组件源 jar 的“sources”变体

  • 代表组件 javadoc jar 的“javadoc”变体

  • <dependencyManagement> 块派生的四个“平台”变体(属性 org.gradle.category = platform

    • platform-compile 变体将 <scope>compile</scope> 依赖管理依赖项映射为依赖约束

    • platform-runtime 变体将 <scope>compile</scope><scope>runtime</scope> 依赖管理依赖项都映射为依赖约束

    • enforced-platform-compileplatform-compile 类似,但所有约束都是强制的

    • enforced-platform-runtimeplatform-runtime 类似,但所有约束都是强制的

通过查阅手册的导入 BOM 部分,你可以更深入地了解平台和强制平台变体的使用。默认情况下,每当你声明对 Maven 模块的依赖项时,Gradle 都会查找 library 变体。然而,使用 platformenforcedPlatform 关键字,Gradle 现在会查找“平台”变体之一,这允许你从 POM 文件导入约束,而不是依赖项。

Ivy 文件到变体的映射

Gradle 没有为 Ivy 文件实现内置的派生策略。Ivy 是一种灵活的格式,允许你发布任意文件,并且可以进行高度定制。

如果你想为 Ivy 实现 compileruntime 变体的派生策略,可以使用组件元数据规则来实现。组件元数据规则 API 允许你访问 Ivy 配置并基于它们创建变体。如果你知道所有你正在消费的 Ivy 模块都是使用 Gradle 发布且未对 ivy.xml 文件进行进一步定制,你可以将以下规则添加到你的构建中

build.gradle.kts
abstract class IvyVariantDerivationRule @Inject internal constructor(objectFactory: ObjectFactory) : ComponentMetadataRule {
    private val jarLibraryElements: LibraryElements
    private val libraryCategory: Category
    private val javaRuntimeUsage: Usage
    private val javaApiUsage: Usage

    init {
        jarLibraryElements = objectFactory.named(LibraryElements.JAR)
        libraryCategory = objectFactory.named(Category.LIBRARY)
        javaRuntimeUsage = objectFactory.named(Usage.JAVA_RUNTIME)
        javaApiUsage = objectFactory.named(Usage.JAVA_API)
    }

    override fun execute(context: ComponentMetadataContext) {
        // This filters out any non Ivy module
        if(context.getDescriptor(IvyModuleDescriptor::class) == null) {
            return
        }

        context.details.addVariant("runtimeElements", "default") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
                attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
                attribute(Usage.USAGE_ATTRIBUTE, javaRuntimeUsage)
            }
        }
        context.details.addVariant("apiElements", "compile") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
                attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
                attribute(Usage.USAGE_ATTRIBUTE, javaApiUsage)
            }
        }
    }
}

dependencies {
    components { all<IvyVariantDerivationRule>() }
}
build.gradle
abstract class IvyVariantDerivationRule implements ComponentMetadataRule {
    final LibraryElements jarLibraryElements
    final Category libraryCategory
    final Usage javaRuntimeUsage
    final Usage javaApiUsage

    @Inject
    IvyVariantDerivationRule(ObjectFactory objectFactory) {
        jarLibraryElements = objectFactory.named(LibraryElements, LibraryElements.JAR)
        libraryCategory = objectFactory.named(Category, Category.LIBRARY)
        javaRuntimeUsage = objectFactory.named(Usage, Usage.JAVA_RUNTIME)
        javaApiUsage = objectFactory.named(Usage, Usage.JAVA_API)
    }

    void execute(ComponentMetadataContext context) {
        // This filters out any non Ivy module
        if(context.getDescriptor(IvyModuleDescriptor) == null) {
            return
        }

        context.details.addVariant("runtimeElements", "default") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
                attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
                attribute(Usage.USAGE_ATTRIBUTE, javaRuntimeUsage)
            }
        }
        context.details.addVariant("apiElements", "compile") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
                attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
                attribute(Usage.USAGE_ATTRIBUTE, javaApiUsage)
            }
        }
    }
}

dependencies {
    components { all(IvyVariantDerivationRule) }
}

该规则基于每个 Ivy 模块的 compile 配置创建一个 apiElements 变体,并基于 default 配置创建一个 runtimeElements 变体。对于每个变体,它设置了相应的Java 生态系统属性。变体的依赖项和 Artifact 来自底层配置。如果并非所有消费的 Ivy 模块都遵循此模式,则可以调整规则或仅应用于选定的一组模块。

对于所有没有变体的 Ivy 模块,Gradle 有一个回退选择方法。Gradle 执行变体感知解析,而是选择 default 配置或显式命名的配置。