變體代表組件的不同版本或方面,例如 apiimplementation屬性定義根據消費者需求選擇哪個變體。

例如,一個函式庫可能具有 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

遵循生態系統語義(例如,library 是 JVM 上的預設值,否則不相容)

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

無預設值,不相容

Category 屬性在變體上以孵化值 org.gradle.category=verification 存在時,該變體被視為僅限於驗證時期的變體。

這些變體旨在僅包含執行驗證任務的結果,例如測試結果或程式碼覆蓋率報告。它們是不可發布的,如果添加到已發布的組件中,將產生錯誤。

屬性名稱 描述 相容性和消除歧義規則

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

指示二進制檔案是否使用偵錯符號建置

布林值

不適用

org.gradle.native.optimized

指示二進制檔案是否使用最佳化標誌建置

布林值

不適用

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 處理器上的屬性架構中宣告

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 在找到多個候選者時選擇最合適的變體。這可以透過實作 屬性消除歧義規則 來完成

import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeMatchingStrategy

// Define custom attribute
val javaLanguageVersion = Attribute.of("org.gradle.jvm.version", String::class.java)

// Register disambiguation rules
configurations.all {
    attributes {
        // Define the attribute matching strategy
        attribute(javaLanguageVersion, "1.8") {
            // Set up disambiguation logic
            disambiguationStrategy {
                // Example disambiguation: Prefer newer versions
                preferNewer()
            }
        }
    }
}
  1. 屬性定義:建立或參考您要應用消除歧義規則的屬性。在這裡,使用 javaLanguageVersion

  2. 註冊消除歧義規則:使用 attributes 區塊中的 disambiguationStrategy 應用消除歧義策略。此範例設定了一個簡單的規則來優先選擇較新版本。

  3. 消除歧義邏輯preferNewer() 方法是您的自訂邏輯的預留位置。您可以根據您的需求實作更複雜的規則。

屬性消除歧義規則必須透過您可以從 屬性架構 獲得的 屬性匹配策略 註冊,屬性架構是 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-compile 類似於 platform-compile,但所有約束都是強制的

    • enforced-platform-runtime 類似於 platform-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) }
}

該規則基於 compile 配置建立 apiElements 變體,並基於每個 ivy 模組的 default 配置建立 runtimeElements 變體。對於每個變體,它都設定了相應的 Java 生態系統屬性。變體的相依性和成品取自底層配置。如果並非所有使用的 Ivy 模組都遵循此模式,則可以調整規則或僅將其應用於選定的模組集。

對於所有沒有變體的 Ivy 模組,Gradle 都有後備選擇方法。Gradle 執行變體感知解析,而是選擇 default 配置或顯式命名的配置。