從儲存庫提取的每個元件都包含 metadata,例如其群組、名稱、版本,以及它提供的各種變體及其產出物和相依性。

偶爾,此 metadata 可能不完整或不正確。

Gradle 提供 API 來解決此問題,讓您直接在建置腳本中撰寫元件 metadata 規則。這些規則會在模組的 metadata 下載後,但在用於相依性解析之前套用。

撰寫元件 metadata 規則

元件 metadata 規則會在建置腳本或設定腳本的 dependencies 區塊中的 components 區段內套用。

這些規則可以用兩種方式定義

  1. 內聯作為動作:直接在 components 區段內。

  2. 作為單獨的類別:實作 ComponentMetadataRule 介面。

雖然內聯動作對於快速實驗很方便,但通常建議將規則定義為單獨的類別。

以隔離類別撰寫的規則可以使用 @CacheableRule 註解,使其結果可以被快取,並避免每次解析相依性時重新執行。

規則應始終可快取,以避免對建置效能產生重大影響並確保更快的建置時間。
build.gradle.kts
@CacheableRule
abstract class TargetJvmVersionRule @Inject constructor(val jvmVersion: Int) : ComponentMetadataRule {
    @get:Inject abstract val objects: ObjectFactory

    override fun execute(context: ComponentMetadataContext) {
        context.details.withVariant("compile") {
            attributes {
                attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, jvmVersion)
                attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API))
            }
        }
    }
}
dependencies {
    components {
        withModule<TargetJvmVersionRule>("commons-io:commons-io") {
            params(7)
        }
        withModule<TargetJvmVersionRule>("commons-collections:commons-collections") {
            params(8)
        }
    }
    implementation("commons-io:commons-io:2.6")
    implementation("commons-collections:commons-collections:3.2.2")
}
build.gradle
@CacheableRule
abstract class TargetJvmVersionRule implements ComponentMetadataRule {
    final Integer jvmVersion
    @Inject TargetJvmVersionRule(Integer jvmVersion) {
        this.jvmVersion = jvmVersion
    }

    @Inject abstract ObjectFactory getObjects()

    void execute(ComponentMetadataContext context) {
        context.details.withVariant("compile") {
            attributes {
                attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, jvmVersion)
                attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API))
            }
        }
    }
}
dependencies {
    components {
        withModule("commons-io:commons-io", TargetJvmVersionRule) {
            params(7)
        }
        withModule("commons-collections:commons-collections", TargetJvmVersionRule) {
            params(8)
        }
    }
    implementation("commons-io:commons-io:2.6")
    implementation("commons-collections:commons-collections:3.2.2")
}

在此範例中,TargetJvmVersionRule 類別實作 ComponentMetadataRule,並使用 ActionConfiguration 進行進一步配置。

Gradle 強制隔離 ComponentMetadataRule 的實例,要求所有參數都必須是 Serializable 或可識別的 Gradle 類型。

此外,可以使用 @InjectObjectFactory 等服務注入到規則的建構子中。

元件 metadata 規則可以使用 all(rule) 套用於所有模組,或使用 withModule(groupAndName, rule) 套用於特定模組。通常,規則是為了豐富特定模組的 metadata 而量身定制的,因此首選 withModule API。

在中心位置宣告規則

在設定中宣告元件 metadata 規則是一項孵化中的功能

元件 metadata 規則可以在整個建置的 settings.gradle(.kts) 檔案中宣告,而不是在每個子專案中單獨宣告。在設定中宣告的規則預設會套用於所有專案,除非被專案特定的規則覆寫。

settings.gradle.kts
dependencyResolutionManagement {
    components {
        withModule<GuavaRule>("com.google.guava:guava")
    }
}
settings.gradle
dependencyResolutionManagement {
    components {
        withModule("com.google.guava:guava", GuavaRule)
    }
}

預設情況下,專案特定的規則優先於設定規則。但是,可以調整此行為

settings.gradle.kts
dependencyResolutionManagement {
    rulesMode = RulesMode.PREFER_SETTINGS
}
settings.gradle
dependencyResolutionManagement {
    rulesMode = RulesMode.PREFER_SETTINGS
}

如果調用此方法且專案或外掛宣告規則,則會發出警告。您可以使用此替代方案將其設為失敗

settings.gradle.kts
dependencyResolutionManagement {
    rulesMode = RulesMode.FAIL_ON_PROJECT_RULES
}
settings.gradle
dependencyResolutionManagement {
    rulesMode = RulesMode.FAIL_ON_PROJECT_RULES
}

預設行為等同於調用此方法

settings.gradle.kts
dependencyResolutionManagement {
    rulesMode = RulesMode.PREFER_PROJECT
}
settings.gradle
dependencyResolutionManagement {
    rulesMode = RulesMode.PREFER_PROJECT
}

metadata 的哪些部分可以修改?

Component Metadata Rules API 專注於 Gradle Module Metadata 和 dependencies API 支援的功能。使用 metadata 規則與在建置腳本中定義相依性/產出物之間的關鍵差異在於,元件 metadata 規則直接在變體上操作,而建置腳本通常一次影響多個變體(例如,api 相依性會套用於 Java 函式庫的 apiruntime 變體)。

變體可以透過以下方法修改

  • allVariants:修改元件的所有變體。

  • withVariant(name):修改由其名稱識別的特定變體。

  • addVariant(name)addVariant(name, base):從頭新增變體,或從現有變體 (base) 複製詳細資訊。

以下變體詳細資訊可以修改

  • 屬性:使用 attributes {} 區塊來調整識別變體的屬性。

  • 功能:使用 withCapabilities {} 區塊來定義變體提供的功能

  • 相依性:使用 withDependencies {} 區塊來管理變體的相依性,包括 豐富的版本約束。

  • 相依性約束:使用 withDependencyConstraints {} 區塊來定義變體的相依性約束,包括豐富的版本。

  • 發布的檔案:使用 withFiles {} 區塊來指定組成變體內容的檔案位置。

此外,還可以變更幾個元件層級的屬性

  • 元件屬性:此處唯一有意義的屬性org.gradle.status

  • 狀態方案:影響在版本選擇期間如何解釋 org.gradle.status 屬性。

  • BelongsTo 屬性:用於透過虛擬平台進行 <component_capabilities.adoc#sec:declaring-capabilities-external-modules,version alignment>>。

模組 metadata 的格式會影響其如何映射到以變體為中心的表示形式

  • Gradle 模組 Metadata:資料結構與模組的 .module 檔案類似。

  • POM Metadata:對於使用 .pom metadata 發布的模組,固定變體會如「將 POM 檔案映射到變體」章節中所述衍生。

  • Ivy Metadata:如果模組是使用 ivy.xml 檔案發布的,則可以存取 Ivy 配置來代替變體。它們的相依性、約束和檔案可以修改。您也可以使用 addVariant(name, baseVariantOrConfiguration) 從 Ivy 配置衍生變體,例如為 Java 函式庫外掛定義 compileruntime 變體

在使用元件 metadata 規則調整模組的 metadata 之前,請確定模組是使用 Gradle 模組 Metadata (.module 檔案) 還是傳統 metadata (.pomivy.xml) 發布的

  • 使用 Gradle 模組 Metadata 的模組:這些模組通常具有完整的 metadata,但仍可能發生問題。只有在您明確識別出 metadata 問題時,才套用元件 metadata 規則。對於相依性解析問題,請首先考慮使用具有豐富版本的相依性約束。如果您正在開發函式庫,請注意相依性約束會作為您自己的函式庫 metadata 的一部分發布,從而更容易與消費者共享解決方案。相比之下,元件 metadata 規則僅在您自己的建置中套用。

  • 使用傳統 Metadata 的模組 (.pomivy.xml):這些模組更可能具有不完整的 metadata,因為這些格式不支援變體和相依性約束等功能。此類模組可能具有被省略或錯誤定義為相依性的變體或約束。在以下章節中,我們將探討具有不完整 metadata 的 OSS 模組範例,以及新增遺失資訊的規則。

根據經驗法則,您應該考慮您正在撰寫的規則是否在您的建置上下文之外也有效。也就是說,如果應用於任何其他使用其影響的模組的建置中,該規則是否仍然產生正確且有用的結果?

修正不正確的相依性詳細資訊

考慮在 Maven Central 上發布的 Jaxen XPath Engine (版本 1.1.3)。它的 pom 檔案在 compile 範圍中宣告了幾個不必要的相依性,這些相依性稍後在版本 1.1.4 中被移除。如果您需要使用版本 1.1.3,您可以使用以下規則修正 metadata

build.gradle.kts
@CacheableRule
abstract class JaxenDependenciesRule: ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        context.details.allVariants {
            withDependencies {
                removeAll { it.group in listOf("dom4j", "jdom", "xerces",  "maven-plugins", "xml-apis", "xom") }
            }
        }
    }
}
build.gradle
@CacheableRule
abstract class JaxenDependenciesRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        context.details.allVariants {
            withDependencies {
                removeAll { it.group in ["dom4j", "jdom", "xerces",  "maven-plugins", "xml-apis", "xom"] }
            }
        }
    }
}

withDependencies 區塊中,您可以存取完整的相依性列表,並可以使用 Java 集合方法來檢查和修改該列表。您也可以使用 add(notation, configureAction) 方法新增相依性。同樣地,您可以檢查和修改 withDependencyConstraints 區塊內的相依性約束。

在 Jaxen 版本 1.1.4 中,dom4jjdomxerces 相依性仍然存在,但被標記為可選。可選相依性不會由 Gradle 或 Maven 自動處理,因為它們指示功能變體,這些變體需要額外的相依性。但是,pom 檔案缺少關於這些功能及其對應相依性的資訊。這可以使用 Gradle 模組 Metadata,透過變體和功能來表示,我們可以透過元件 metadata 規則新增這些。

build.gradle.kts
@CacheableRule
abstract class JaxenCapabilitiesRule: ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        context.details.addVariant("runtime-dom4j", "runtime") {
            withCapabilities {
                removeCapability("jaxen", "jaxen")
                addCapability("jaxen", "jaxen-dom4j", context.details.id.version)
            }
            withDependencies {
                add("dom4j:dom4j:1.6.1")
            }
        }
    }
}
build.gradle
@CacheableRule
abstract class JaxenCapabilitiesRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        context.details.addVariant("runtime-dom4j", "runtime") {
            withCapabilities {
                removeCapability("jaxen", "jaxen")
                addCapability("jaxen", "jaxen-dom4j", context.details.id.version)
            }
            withDependencies {
                add("dom4j:dom4j:1.6.1")
            }
        }
    }
}

在本範例中,我們使用 addVariant(name, baseVariant) 方法建立一個名為 runtime-dom4j 的新變體。此變體代表一個可選功能,由 jaxen-dom4j 功能定義。然後,我們將所需的相依性 dom4j:dom4j:1.6.1 新增到此功能。

build.gradle.kts
dependencies {
    components {
        withModule<JaxenDependenciesRule>("jaxen:jaxen")
        withModule<JaxenCapabilitiesRule>("jaxen:jaxen")
    }
    implementation("jaxen:jaxen:1.1.3")
    runtimeOnly("jaxen:jaxen:1.1.3") {
        capabilities { requireCapability("jaxen:jaxen-dom4j") }
    }
}
build.gradle
dependencies {
    components {
        withModule("jaxen:jaxen", JaxenDependenciesRule)
        withModule("jaxen:jaxen", JaxenCapabilitiesRule)
    }
    implementation("jaxen:jaxen:1.1.3")
    runtimeOnly("jaxen:jaxen:1.1.3") {
        capabilities { requireCapability("jaxen:jaxen-dom4j") }
    }
}

透過套用這些規則,當需要 jaxen-dom4j 功能時,Gradle 會使用豐富的 metadata 來正確解析可選相依性。

將發布為分類 jar 的變體明確化

在現代建置中,變體通常作為單獨的產出物發布,每個產出物都由自己的 jar 檔案表示。例如,函式庫可以為不同的 Java 版本提供不同的 jar,以確保在執行階段或編譯階段根據環境使用正確的版本。

例如,在 Maven Central 上發布的非同步程式設計函式庫 Quasar 的版本 0.7.9,同時包含 quasar-core-0.7.9.jarquasar-core-0.7.9-jdk8.jar。在 Maven 儲存庫中,發布帶有分類器 (例如 jdk8) 的 jar 是一種常見做法。但是,Maven 和 Gradle metadata 都未提供關於這些分類 jar 的資訊。因此,沒有明確的方法可以確定它們的存在或變體之間的任何差異,例如相依性。

在 Gradle 模組 Metadata 中,會存在變體資訊。對於已發布的 Quasar 函式庫,我們可以使用以下規則新增此資訊

build.gradle.kts
@CacheableRule
abstract class QuasarRule: ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        listOf("compile", "runtime").forEach { base ->
            context.details.addVariant("jdk8${base.capitalize()}", base) {
                attributes {
                    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
                }
                withFiles {
                    removeAllFiles()
                    addFile("${context.details.id.name}-${context.details.id.version}-jdk8.jar")
                }
            }
            context.details.withVariant(base) {
                attributes {
                    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 7)
                }
            }
        }
    }
}
build.gradle
@CacheableRule
abstract class QuasarRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        ["compile", "runtime"].each { base ->
            context.details.addVariant("jdk8${base.capitalize()}", base) {
                attributes {
                    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
                }
                withFiles {
                    removeAllFiles()
                    addFile("${context.details.id.name}-${context.details.id.version}-jdk8.jar")
                }
            }
            context.details.withVariant(base) {
                attributes {
                    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 7)
                }
            }
        }
    }
}

在這種情況下,jdk8 分類器清楚地指示目標 Java 版本,這對應於 Java 生態系統中的已知屬性。由於我們需要 Java 8 的 compile 和 runtime 變體,因此我們使用現有的 compile 和 runtime 變體作為基礎建立兩個新變體。這確保了所有其他 Java 生態系統屬性都已正確設定,並且相依性已轉移。

我們為兩個新變體將 TARGET_JVM_VERSION_ATTRIBUTE 指派為 8,使用 removeAllFiles() 移除任何現有檔案,然後使用 addFile() 新增 jdk8 jar。移除檔案是必要的,因為對主要 jar quasar-core-0.7.9.jar 的參考是從基礎變體複製的。

最後,我們使用 attribute(TARGET_JVM_VERSION_ATTRIBUTE, 7) 豐富現有的 compile 和 runtime 變體,使其包含它們以 Java 7 為目標的資訊。

透過這些變更,您現在可以為編譯類別路徑上的所有相依性請求 Java 8 版本,Gradle 將自動選擇最合適的變體。在 Quasar 的情況下,這將是 jdk8Compile 變體,它公開了 quasar-core-0.7.9-jdk8.jar

build.gradle.kts
configurations["compileClasspath"].attributes {
    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
}
dependencies {
    components {
        withModule<QuasarRule>("co.paralleluniverse:quasar-core")
    }
    implementation("co.paralleluniverse:quasar-core:0.7.9")
}
build.gradle
configurations.compileClasspath.attributes {
    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
}
dependencies {
    components {
        withModule("co.paralleluniverse:quasar-core", QuasarRule)
    }
    implementation("co.paralleluniverse:quasar-core:0.7.9")
}

透過此配置,Gradle 將為編譯類別路徑選擇 Quasar 的 Java 8 變體。

將編碼在版本中的變體明確化

發布同一函式庫的多個替代方案的另一種解決方案是使用版本控制模式,如流行的 Guava 函式庫所做的那樣。在這裡,每個新版本都會發布兩次,方法是將分類器附加到版本而不是 jar 產出物。例如,在 Guava 28 的情況下,我們可以在 Maven central 上找到 28.0-jre (Java 8) 和 28.0-android (Java 6) 版本。僅使用 pom metadata 時,使用此模式的優點是兩種變體都可以透過版本發現。缺點是沒有資訊說明不同版本後綴在語義上的含義。因此,在發生衝突時,Gradle 只會在比較版本字串時選擇最高的版本。

將其轉變為適當的變體有點棘手,因為 Gradle 首先選擇模組的版本,然後選擇最合適的變體。因此,不直接支援將變體編碼為版本的概念。但是,由於兩個變體始終一起發布,我們可以假設檔案在物理上位於同一個儲存庫中。而且由於它們是使用 Maven 儲存庫慣例發布的,因此如果我們知道模組名稱和版本,我們就知道每個檔案的位置。我們可以撰寫以下規則

build.gradle.kts
@CacheableRule
abstract class GuavaRule: ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        val variantVersion = context.details.id.version
        val version = variantVersion.substring(0, variantVersion.indexOf("-"))
        listOf("compile", "runtime").forEach { base ->
            mapOf(6 to "android", 8 to "jre").forEach { (targetJvmVersion, jarName) ->
                context.details.addVariant("jdk$targetJvmVersion${base.capitalize()}", base) {
                    attributes {
                        attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, targetJvmVersion)
                    }
                    withFiles {
                        removeAllFiles()
                        addFile("guava-$version-$jarName.jar", "../$version-$jarName/guava-$version-$jarName.jar")
                    }
                }
            }
        }
    }
}
build.gradle
@CacheableRule
abstract class GuavaRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        def variantVersion = context.details.id.version
        def version = variantVersion.substring(0, variantVersion.indexOf("-"))
        ["compile", "runtime"].each { base ->
            [6: "android", 8: "jre"].each { targetJvmVersion, jarName ->
                context.details.addVariant("jdk$targetJvmVersion${base.capitalize()}", base) {
                    attributes {
                        attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, targetJvmVersion)
                    }
                    withFiles {
                        removeAllFiles()
                        addFile("guava-$version-${jarName}.jar", "../$version-$jarName/guava-$version-${jarName}.jar")
                    }
                }
            }
        }
    }
}

與先前的範例類似,我們為兩個 Java 版本新增了 runtime 和 compile 變體。但是,在 withFiles 區塊中,我們現在還為對應的 jar 檔案指定了相對路徑,這讓 Gradle 可以找到檔案,無論它選擇的是 -jre 還是 -android 版本。路徑始終相對於所選模組版本的 metadata(在本例中為 pom)檔案的位置。因此,透過這些規則,Guava 28 的兩個「版本」都帶有 jdk6 和 jdk8 變體。因此,Gradle 解析到哪個版本並不重要。變體以及正確的 jar 檔案是根據請求的 TARGET_JVM_VERSION_ATTRIBUTE 值確定的。

build.gradle.kts
configurations["compileClasspath"].attributes {
    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 6)
}
dependencies {
    components {
        withModule<GuavaRule>("com.google.guava:guava")
    }
    // '23.3-android' and '23.3-jre' are now the same as both offer both variants
    implementation("com.google.guava:guava:23.3+")
}
build.gradle
configurations.compileClasspath.attributes {
    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 6)
}
dependencies {
    components {
        withModule("com.google.guava:guava", GuavaRule)
    }
    // '23.3-android' and '23.3-jre' are now the same as both offer both variants
    implementation("com.google.guava:guava:23.3+")
}

為原生 jar 新增變體

帶有分類器的 jar 也用於將函式庫的某些部分(例如原生程式碼)與主要產出物分開,對於這些部分存在多個替代方案。例如,Lightweight Java Game Library (LWGJ) 就是這樣做的,它將幾個平台特定的 jar 發布到 Maven central,在執行階段,除了主要 jar 之外,始終需要其中一個。不可能在 pom metadata 中傳達此資訊,因為沒有透過 metadata 將多個產出物關聯起來的概念。在 Gradle 模組 Metadata 中,每個變體可以有任意多個檔案,我們可以透過撰寫以下規則來利用這一點

build.gradle.kts
@CacheableRule
abstract class LwjglRule: ComponentMetadataRule {
    data class NativeVariant(val os: String, val arch: String, val classifier: String)

    private val nativeVariants = listOf(
        NativeVariant(OperatingSystemFamily.LINUX,   "arm32",  "natives-linux-arm32"),
        NativeVariant(OperatingSystemFamily.LINUX,   "arm64",  "natives-linux-arm64"),
        NativeVariant(OperatingSystemFamily.WINDOWS, "x86",    "natives-windows-x86"),
        NativeVariant(OperatingSystemFamily.WINDOWS, "x86-64", "natives-windows"),
        NativeVariant(OperatingSystemFamily.MACOS,   "x86-64", "natives-macos")
    )

    @get:Inject abstract val objects: ObjectFactory

    override fun execute(context: ComponentMetadataContext) {
        context.details.withVariant("runtime") {
            attributes {
                attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named("none"))
                attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named("none"))
            }
        }
        nativeVariants.forEach { variantDefinition ->
            context.details.addVariant("${variantDefinition.classifier}-runtime", "runtime") {
                attributes {
                    attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(variantDefinition.os))
                    attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(variantDefinition.arch))
                }
                withFiles {
                    addFile("${context.details.id.name}-${context.details.id.version}-${variantDefinition.classifier}.jar")
                }
            }
        }
    }
}
build.gradle
@CacheableRule
abstract class LwjglRule implements ComponentMetadataRule { //val os: String, val arch: String, val classifier: String)
    private def nativeVariants = [
        [os: OperatingSystemFamily.LINUX,   arch: "arm32",  classifier: "natives-linux-arm32"],
        [os: OperatingSystemFamily.LINUX,   arch: "arm64",  classifier: "natives-linux-arm64"],
        [os: OperatingSystemFamily.WINDOWS, arch: "x86",    classifier: "natives-windows-x86"],
        [os: OperatingSystemFamily.WINDOWS, arch: "x86-64", classifier: "natives-windows"],
        [os: OperatingSystemFamily.MACOS,   arch: "x86-64", classifier: "natives-macos"]
    ]

    @Inject abstract ObjectFactory getObjects()

    void execute(ComponentMetadataContext context) {
        context.details.withVariant("runtime") {
            attributes {
                attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, "none"))
                attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, "none"))
            }
        }
        nativeVariants.each { variantDefinition ->
            context.details.addVariant("${variantDefinition.classifier}-runtime", "runtime") {
                attributes {
                    attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, variantDefinition.os))
                    attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, variantDefinition.arch))
                }
                withFiles {
                    addFile("${context.details.id.name}-${context.details.id.version}-${variantDefinition.classifier}.jar")
                }
            }
        }
    }
}

此規則與上述 Quasar 函式庫範例非常相似。只有這次我們新增了五個不同的 runtime 變體,而 compile 變體不需要變更任何內容。runtime 變體都基於現有的 runtime 變體,我們不會變更任何現有資訊。所有 Java 生態系統屬性、相依性和主要 jar 檔案都保留為每個 runtime 變體的一部分。我們僅設定了額外的屬性 OPERATING_SYSTEM_ATTRIBUTEARCHITECTURE_ATTRIBUTE,這些屬性定義為 Gradle 的 原生支援的一部分。而且我們新增了對應的原生 jar 檔案,以便每個 runtime 變體現在都帶有兩個檔案:主要 jar 和原生 jar。

在建置腳本中,我們現在可以請求特定的變體,如果需要更多資訊才能做出決定,Gradle 將會失敗並顯示選擇錯誤。

Gradle 能夠理解常見情況,即缺少單個屬性會消除歧義。在這種情況下,Gradle 不會列出關於所有可用變體上所有屬性的資訊,而是有用地僅列出該屬性的可能值以及每個值將選擇的變體。

build.gradle.kts
configurations["runtimeClasspath"].attributes {
    attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named("windows"))
}
dependencies {
    components {
        withModule<LwjglRule>("org.lwjgl:lwjgl")
    }
    implementation("org.lwjgl:lwjgl:3.2.3")
}
build.gradle
configurations["runtimeClasspath"].attributes {
    attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, "windows"))
}
dependencies {
    components {
        withModule("org.lwjgl:lwjgl", LwjglRule)
    }
    implementation("org.lwjgl:lwjgl:3.2.3")
}

Gradle 無法選擇變體,因為需要選擇機器架構

> Could not resolve all files for configuration ':runtimeClasspath'.
   > Could not resolve org.lwjgl:lwjgl:3.2.3.
     Required by:
         project :
      > The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, preferably optimized for standard JVMs, and its dependencies declared externally, as well as attribute 'org.gradle.native.operatingSystem' with value 'windows'. There are several available matching variants of org.lwjgl:lwjgl:3.2.3
       The only attribute distinguishing these variants is 'org.gradle.native.architecture'. Add this attribute to the consumer's configuration to resolve the ambiguity:
          - Value: 'x86-64' selects variant: 'natives-windows-runtime'
          - Value: 'x86' selects variant: 'natives-windows-x86-runtime'

透過功能讓函式庫的不同風味可用

由於很難使用 pom metadata 將可選功能變體建模為單獨的 jar,因此函式庫有時包含具有不同功能集的不同 jar。也就是說,您不是從不同的功能變體組合函式庫的風味,而是選擇預先組合的變體之一(在一個 jar 中提供所有內容)。一個這樣的函式庫是著名的相依性注入框架 Guice,它在 Maven central 上發布,提供完整的風味(主要 jar)和一個沒有面向切面程式設計支援的簡化變體 (guice-4.2.2-no_aop.jar)。pom metadata 中未提及帶有分類器的第二個變體。透過以下規則,我們基於該檔案建立 compile 和 runtime 變體,並使其可透過名為 com.google.inject:guice-no_aop 的功能選擇。

build.gradle.kts
@CacheableRule
abstract class GuiceRule: ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        listOf("compile", "runtime").forEach { base ->
            context.details.addVariant("noAop${base.capitalize()}", base) {
                withCapabilities {
                    addCapability("com.google.inject", "guice-no_aop", context.details.id.version)
                }
                withFiles {
                    removeAllFiles()
                    addFile("guice-${context.details.id.version}-no_aop.jar")
                }
                withDependencies {
                    removeAll { it.group == "aopalliance" }
                }
            }
        }
    }
}
build.gradle
@CacheableRule
abstract class GuiceRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        ["compile", "runtime"].each { base ->
            context.details.addVariant("noAop${base.capitalize()}", base) {
                withCapabilities {
                    addCapability("com.google.inject", "guice-no_aop", context.details.id.version)
                }
                withFiles {
                    removeAllFiles()
                    addFile("guice-${context.details.id.version}-no_aop.jar")
                }
                withDependencies {
                    removeAll { it.group == "aopalliance" }
                }
            }
        }
    }
}

新的變體也移除了對標準化的 aop 介面函式庫 aopalliance:aopalliance 的相依性,因為這些變體顯然不需要它。同樣,這是無法在 pom metadata 中表達的資訊。我們現在可以選擇 guice-no_aop 變體,並將獲得正確的 jar 檔案正確的相依性。

build.gradle.kts
dependencies {
    components {
        withModule<GuiceRule>("com.google.inject:guice")
    }
    implementation("com.google.inject:guice:4.2.2") {
        capabilities { requireCapability("com.google.inject:guice-no_aop") }
    }
}
build.gradle
dependencies {
    components {
        withModule("com.google.inject:guice", GuiceRule)
    }
    implementation("com.google.inject:guice:4.2.2") {
        capabilities { requireCapability("com.google.inject:guice-no_aop") }
    }
}

新增遺失的功能以偵測衝突

功能的另一個用途是表達兩個不同的模組(例如 log4jlog4j-over-slf4j)提供相同事物的替代實作。透過宣告兩者都提供相同的功能,Gradle 在相依性圖中只接受其中一個。此範例以及如何使用元件 metadata 規則解決它,在功能建模章節中詳細說明。

讓 Ivy 模組具備變體感知能力

使用 Ivy 發布的模組預設沒有可用的變體。

但是,Ivy 配置可以映射到變體,因為 addVariant(name, baseVariantOrConfiguration) 接受任何作為基礎發布的 Ivy 配置。例如,這可以用於定義 runtime 和 compile 變體。可以在此處找到相應規則的範例。Ivy 配置的 Ivy 詳細資訊(例如相依性和檔案)也可以使用 withVariant(configurationName) API 修改。但是,修改 Ivy 配置上的屬性或功能沒有效果。

對於非常特定於 Ivy 的用例,元件 metadata 規則 API 還提供對僅在 Ivy metadata 中找到的其他詳細資訊的存取權。這些詳細資訊可透過 IvyModuleDescriptor 介面獲得,並且可以使用 ComponentMetadataContext 上的 getDescriptor(IvyModuleDescriptor) 存取。

build.gradle.kts
@CacheableRule
abstract class IvyComponentRule : ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        val descriptor = context.getDescriptor(IvyModuleDescriptor::class)
        if (descriptor != null && descriptor.branch == "testing") {
            context.details.status = "rc"
        }
    }
}
build.gradle
@CacheableRule
abstract class IvyComponentRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        def descriptor = context.getDescriptor(IvyModuleDescriptor)
        if (descriptor != null && descriptor.branch == "testing") {
            context.details.status = "rc"
        }
    }
}

使用 Maven metadata 篩選

對於特定於 Maven 的用例,元件 metadata 規則 API 還提供對僅在 POM metadata 中找到的其他詳細資訊的存取權。這些詳細資訊可透過 PomModuleDescriptor 介面獲得,並且可以使用 ComponentMetadataContext 上的 getDescriptor(PomModuleDescriptor) 存取。

build.gradle.kts
@CacheableRule
abstract class MavenComponentRule : ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        val descriptor = context.getDescriptor(PomModuleDescriptor::class)
        if (descriptor != null && descriptor.packaging == "war") {
            // ...
        }
    }
}
build.gradle
@CacheableRule
abstract class MavenComponentRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        def descriptor = context.getDescriptor(PomModuleDescriptor)
        if (descriptor != null && descriptor.packaging == "war") {
            // ...
        }
    }
}

在元件層級修改 metadata 以進行對齊

雖然以上所有範例都對元件的變體進行了修改,但也可以對元件本身的 metadata 進行一組有限的修改。此資訊可以影響模組在相依性解析期間的版本選擇過程,該過程在選擇元件的一個或多個變體之前執行。

元件上可用的第一個 API 是 belongsTo(),用於建立虛擬平台,以對齊沒有 Gradle 模組 Metadata 的多個模組的版本。在關於對齊未使用 Gradle 發布的模組版本的章節中對此進行了詳細說明。

在元件層級修改 metadata 以根據狀態選擇版本

Gradle 和 Gradle 模組 Metadata 也允許在整個元件而不是單個變體上設定屬性。這些屬性中的每一個都帶有特殊的語義,因為它們會影響在變體選擇之前完成的版本選擇。雖然變體選擇可以處理任何自訂屬性,但版本選擇僅考慮實作了特定語義的屬性。目前,此處唯一有意義的屬性是 org.gradle.status

org.gradle.status 模組屬性指示模組或函式庫的生命週期狀態或成熟度級別

  1. integration:這表示模組正在積極開發中,可能不穩定。

  2. milestone:具有此狀態的模組比標記為 integration 的模組更成熟。

  3. release:此狀態表示模組穩定且已正式發布。

因此,建議僅在元件層級修改此屬性(如果有的話)。專用的 API setStatus(value) 可用於此目的。若要修改元件所有變體的另一個屬性,則應改為使用 withAllVariants { attributes {} }

當解析latest 版本選取器時,會考慮模組的狀態。具體而言,latest.someStatus 將解析為具有狀態 someStatus 或更成熟狀態的最高模組版本。例如,latest.integration 將選擇最高模組版本,而不管其狀態如何(因為 integration 是成熟度最低的狀態,如下所述),而 latest.release 將選擇具有狀態 release 的最高模組版本。

狀態的解釋可以透過 setStatusScheme(valueList) API 變更模組的狀態方案來影響。此概念模擬模組隨著時間推移透過不同的發布版本過渡的不同成熟度級別。預設狀態方案(從成熟度最低到最高排序)為 integrationmilestonereleaseorg.gradle.status 屬性必須設定為元件狀態方案中的值之一。因此,每個元件始終都有一個從 metadata 確定的狀態,如下所示

  • Gradle 模組 Metadata:在元件上為 org.gradle.status 屬性發布的值

  • Ivy metadata:在 ivy.xml 中定義的 status,如果遺失則預設為 integration

  • Pom metadata:對於具有 SNAPSHOT 版本的模組為 integration,對於所有其他模組為 release

以下範例示範了基於在適用於所有模組的元件 metadata 規則中宣告的自訂狀態方案的 latest 選取器

build.gradle.kts
@CacheableRule
abstract class CustomStatusRule : ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        context.details.statusScheme = listOf("nightly", "milestone", "rc", "release")
        if (context.details.status == "integration") {
            context.details.status = "nightly"
        }
    }
}

dependencies {
    components {
        all<CustomStatusRule>()
    }
    implementation("org.apache.commons:commons-lang3:latest.rc")
}
build.gradle
@CacheableRule
abstract class CustomStatusRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        context.details.statusScheme = ["nightly", "milestone", "rc", "release"]
        if (context.details.status == "integration") {
            context.details.status = "nightly"
        }
    }
}

dependencies {
    components {
        all(CustomStatusRule)
    }
    implementation("org.apache.commons:commons-lang3:latest.rc")
}

與預設方案相比,該規則插入了新狀態 rc 並將 integration 替換為 nightly。具有狀態 integration 的現有模組映射到 nightly