使用元件元資料規則修正元資料
從儲存庫拉取的每個模組都有與其關聯的元資料,例如其群組、名稱、版本,以及其提供的不同變體及其成品和相依性。有時,這些元資料不完整或不正確。為了在建置指令碼內處理此類不完整的元資料,Gradle 提供了一個 API 來撰寫元件元資料規則。這些規則會在模組的元資料下載後,但在相依性解析中使用之前生效。
撰寫元件元資料規則的基礎
元件元資料規則會套用在建置指令碼或設定指令碼的相依性區塊 (DependencyHandler) 的元件 (ComponentMetadataHandler) 區段。規則可以兩種不同的方式定義
-
作為動作,直接套用於 組件 區段
-
作為實作 ComponentMetadataRule 介面的獨立類別
雖然將規則定義為內嵌動作很適合用於實驗,但通常建議將規則定義為獨立類別。以獨立類別撰寫的規則可以使用 @CacheableRule
加上註解,以快取套用結果,如此一來,就不需要在每次解析相依項時重新執行。
@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")
}
@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")
}
如上方的範例所示,組件元資料規則的定義方式是實作 ComponentMetadataRule,它有一個單一的 execute
方法,接收 ComponentMetadataContext 的執行個體作為參數。在此範例中,規則也透過 ActionConfiguration 進一步設定。支援的方式是讓 ComponentMetadataRule
實作中的建構函式接受已設定的參數和需要注入的服務。
Gradle 強制隔離 ComponentMetadataRule
的執行個體。這表示所有參數都必須是 Serializable
或已知的 Gradle 類型,才能隔離。
此外,可以將 Gradle 服務注入到 ComponentMetadataRule
中。因此,只要您有建構函式,就必須加上 @javax.inject.Inject
註解。一個常見的必要服務是 ObjectFactory,用來建立強類型值物件的執行個體,例如設定 Attribute 的值。對於進階使用元資料規則搭配自訂元資料,RepositoryResourceAccessor 服務會很有幫助。
組件元資料規則可以套用到所有模組 — all(rule)
— 或選定的模組 — withModule(groupAndName, rule)
。通常,規則會特別撰寫來豐富特定模組的元資料,因此建議優先使用 withModule
API。
在集中位置宣告規則
在設定中宣告組件元資料規則是一項孵化中的功能 |
不必個別為每個子專案宣告規則,也可以在 settings.gradle(.kts)
檔案中為整個建置宣告規則。在設定中宣告的規則是套用至每個專案的慣例規則:如果專案未宣告任何規則,就會使用設定指令碼中的規則。
dependencyResolutionManagement {
components {
withModule<GuavaRule>("com.google.guava:guava")
}
}
dependencyResolutionManagement {
components {
withModule("com.google.guava:guava", GuavaRule)
}
}
預設情況下,專案中宣告的規則會覆寫在設定中宣告的任何內容。可以變更此預設值,例如永遠偏好設定規則
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_SETTINGS
}
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_SETTINGS
}
如果呼叫此方法,而且專案或外掛宣告規則,系統會發出警告。您可以改用此替代方法,讓系統改為傳回失敗
dependencyResolutionManagement {
rulesMode = RulesMode.FAIL_ON_PROJECT_RULES
}
dependencyResolutionManagement {
rulesMode = RulesMode.FAIL_ON_PROJECT_RULES
}
預設行為等同於呼叫此方法
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_PROJECT
}
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_PROJECT
}
哪些部分的元資料可以修改?
元件元資料規則 API 針對 Gradle 模組元資料 和建置指令碼中的相依性 API 所支援的功能而設計。撰寫規則與在建置指令碼中定義相依性和人工製品之間的主要差異在於,元件元資料規則遵循 Gradle 模組元資料的結構,直接操作變異。相反地,在建置指令碼中,您通常會一次影響多個變異的形狀(例如,api 相依性會新增到 Java 函式庫的api 和runtime 變異,jar 任務產生的人工製品也會新增到這兩個變異)。
可以透過下列方法來修改變異
-
allVariants
:修改元件的所有變異 -
withVariant(name)
:修改由名稱識別的單一變異 -
addVariant(name)
或addVariant(name, base)
:新增新的變異到元件,可以從頭開始,也可以複製現有變異 (base) 的詳細資料
可以調整每個變異的下列詳細資料
還有幾個整個元件的屬性可以變更
-
元件層級屬性,目前唯一有意義的屬性是
org.gradle.status
-
狀態配置以影響版本選取期間
org.gradle.status
屬性的詮釋 -
belongsTo屬性用於透過虛擬平台進行版本比對
根據模組的元資料格式,它會以不同的方式對應到元資料的變異中心表示法
-
如果模組有 Gradle 模組元資料,規則運作的資料結構非常類似於您在模組的
.module
檔案中找到的資料結構。 -
如果模組僅使用
.pom
元資料發佈,則會衍生出許多固定變異,如POM 檔案對應到變異區段中所說明的。 -
如果模組僅使用
ivy.xml
檔案發佈,則可以存取檔案中定義的Ivy 組態,而不是變異。它們的相依性、相依性約束和檔案可以修改。此外,addVariant(name, baseVariantOrConfiguration) { }
API 可用於根據需要從Ivy 組態衍生變異(例如,Java 函式庫外掛程式的編譯和執行時期變異可以使用此功能定義)。
何時使用元件元資料規則?
一般來說,如果您考慮使用元件元資料規則來調整特定模組的元資料,您應該先檢查該模組是否使用 Gradle 模組元資料(.module
檔案)或僅使用傳統元資料(.pom
或ivy.xml
)發佈。
如果模組是用 Gradle 模組元資料發佈,元資料很可能是完整的,儘管仍然可能有些情況只是純粹出錯。對於這些模組,只有在您明確找出元資料本身的問題時,才應使用元件元資料規則。如果您對相依性解析結果有問題,您應先檢查是否能透過宣告具有豐富版本的相依性約束來解決問題。特別是,如果您正在開發您要發佈的函式庫,您應記住相依性約束與元件元資料規則不同,會作為您自己的函式庫元資料的一部分發佈。因此,透過相依性約束,您可以自動與您的使用者分享相依性解析問題的解決方案,而元件元資料規則只會套用於您自己的建置。
如果模組是用傳統元資料發佈(只有 .pom
或 ivy.xml
,沒有 .module
檔案),元資料不完整的可能性較高,因為這些格式不支援變異或相依性約束等功能。不過,在概念上,此類模組可以包含不同的變異,或可能省略相依性約束(或錯誤定義為相依性)。在下一節,我們將探討許多具有此類不完整元資料的現有 oss 模組,以及加入遺失元資料資訊的規則。
根據經驗法則,您應考慮您撰寫的規則是否也能在建置的內容外運作。也就是說,如果在使用受影響模組的任何其他建置中套用該規則,該規則是否仍然會產生正確且有用的結果?
修正錯誤的相依性詳細資料
讓我們以在Maven central上發佈 Jaxen XPath Engine 為例。1.1.3 版本的 pom 在編譯範圍中宣告許多實際上不需要編譯的相依性。這些相依性已在 1.1.4 pom 中移除。假設我們因為某些原因需要使用 1.1.3,我們可以使用下列規則修正元資料
@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") }
}
}
}
}
@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 pom,我們會發現 dom4j、jdom 和 xerces 相依項仍然存在,但標記為 optional。pom 中的選用相依項不會由 Gradle 或 Maven 自動處理。原因是它們表示 Jaxen 函式庫提供的 選用功能變體,需要其中一個或多個相依項,但這些功能是什麼以及哪個相依項屬於哪個功能的資訊遺失了。此類資訊無法在 pom 檔案中表示,但可以在 Gradle 模組中透過變體和 功能 來表示。因此,我們也可以在規則中新增此資訊。
@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")
}
}
}
}
@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)
方法來建立一個額外的變體,我們透過定義一個新的功能 jaxen-dom4j 來識別為 功能變體,以表示 Jaxen 的選用 dom4j 整合功能。這類似於在建置指令碼中 定義選用功能變體 的方式。然後,我們使用 add
方法之一來新增相依項,以定義此選用功能需要的相依項。
在建置指令碼中,我們可以新增 選用功能的相依項,而 Gradle 將使用豐富的中繼資料來找出正確的傳遞相依項。
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") }
}
}
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") }
}
}
使發佈為分類 jar 的變體明確
在先前的範例中,所有變體(「主要變體」和選用功能)都封裝在一個 jar 檔案中,但通常會將某些變體發佈為個別檔案。特別是當變體是互斥時,也就是說它們 不是 功能變體,而是提供其他選項的不同變體。所有基於 pom 的函式庫都有一個範例,即 runtime 和 compile 變體,其中 Gradle 可以根據手邊的任務選擇其中一個。在 Java 生態系統中經常發現的另一種此類選項是針對不同 Java 版本的 jar。
舉例來說,我們來看非同步程式設計函式庫 Quasar 的 0.7.9 版本,它發佈在 Maven central 上。如果我們檢查目錄清單,我們會發現除了 quasar-core-0.7.9.jar
之外,還發佈了 quasar-core-0.7.9-jdk8.jar
。在 maven 儲存庫中,使用 分類器(這裡是 jdk8)發佈額外的 jar 是常見做法。雖然 Maven 和 Gradle 都允許您透過分類器來參考此類 jar,但它們在中繼資料中完全沒有提到。因此,沒有資訊指出這些 jar 存在,以及這些 jar 所代表的變體之間是否有任何其他差異,例如不同的相依項。
在 Gradle 模組中繼資料中,此變體資訊會存在,而對於已發佈的 Quasar 函式庫,我們可以使用下列規則來新增它
@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)
}
}
}
}
}
@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)
}
}
}
}
}
在這個案例中,很明顯地分類器代表目標 Java 版本,這是 已知的 Java 生態系統屬性。由於我們也需要 Java 8 的 編譯 和 執行時間,因此我們建立兩個新變體,但使用現有的 編譯 和 執行時間 變體作為 基礎。這樣,所有其他 Java 生態系統屬性都已正確設定,且所有依賴關係都已傳遞。接著,我們將 TARGET_JVM_VERSION_ATTRIBUTE
設定為 8
以適用於這兩個變體,使用 removeAllFiles()
移除新變體中的任何現有檔案,並使用 addFile()
新增 jdk8 jar 檔案。需要使用 removeAllFiles()
,因為對應基礎變體中會複製主 jar quasar-core-0.7.5.jar
的參照。
我們也會使用它們針對 Java 7 的資訊來豐富現有的 編譯 和 執行時間 變體 — attribute(TARGET_JVM_VERSION_ATTRIBUTE, 7)
。
現在,我們可以在建置指令碼中針對編譯類別路徑中的所有依賴關係要求 Java 8 版本,而 Gradle 會自動為每個函式庫選取最合適的變體。在 Quasar 的案例中,這現在會是公開 quasar-core-0.7.9-jdk8.jar
的 jdk8Compile 變體。
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")
}
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")
}
讓編碼在版本中的變體明確化
發布同一個函式庫的許多替代方案的另一種解決方案是使用版本模式,就像廣受歡迎的 Guava 函式庫所做的那樣。在此,每個新版本會發布兩次,方法是將分類器附加到版本,而不是 jar 產出。例如,在 Guava 28 的案例中,我們可以在 Maven central 找到 28.0-jre (Java 8) 和 28.0-android (Java 6) 版本。當只使用 pom 元資料時,使用此模式的優點是兩個變體都可以透過版本找到。缺點是沒有資訊說明不同的版本字尾在語意上代表什麼意思。因此,在發生衝突時,Gradle 在比較版本字串時只會選取最高版本。
將其轉換為適當的變體有點棘手,因為 Gradle 首先會選擇模組版本,然後再選擇最合適的變體。因此,變體編碼為版本的概念並未得到直接支援。但是,由於兩個變體總是同時發布,因此我們可以假設這些檔案實體上位於同一個儲存庫中。而且由於它們是使用 Maven 儲存庫慣例發布的,因此如果我們知道模組名稱和版本,我們就知道每個檔案的位置。我們可以撰寫以下規則
@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")
}
}
}
}
}
}
@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 版本新增執行時期和編譯變體。不過,在 withFiles
區塊中,我們現在還為對應的 jar 檔案指定相對路徑,讓 Gradle 無論選擇的是 -jre 還是 -android 版本,都能找到檔案。此路徑總是相對於選取模組版本元資料(在本例中為 pom
)檔案的位置。因此,使用此規則,兩個 Guava 28「版本」都包含 jdk6 和 jdk8 變體。因此,Gradle 解析到哪一個並不重要。此變體以及正確的 jar 檔案,是根據要求的 TARGET_JVM_VERSION_ATTRIBUTE
值來決定的。
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+")
}
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 中央,除了主 jar 之外,執行時期總是需要其中一個。由於無法透過元資料傳達此資訊,因此無法透過元資料將多個製成品關聯起來。在 Gradle 模組元資料中,每個變體都可以有任意多個檔案,我們可以透過撰寫以下規則來利用這一點
@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")
}
}
}
}
}
@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 變體,我們不會變更任何現有資訊。所有 Java 生態系統屬性、相依性和主 jar 檔案都仍然是每個執行時期變體的一部分。我們只設定額外的屬性 OPERATING_SYSTEM_ATTRIBUTE
和 ARCHITECTURE_ATTRIBUTE
,這些屬性定義為 Gradle 的 原生支援 的一部分。我們還新增對應的原生 jar 檔案,以便每個執行時期變體現在包含兩個檔案:主 jar 和原生 jar。
在建置腳本中,我們現在可以要求特定變異,如果需要更多資訊來做出決定,Gradle 會因選擇錯誤而失敗。
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")
}
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")
}
> Could not resolve all files for configuration ':runtimeClasspath'. > Could not resolve org.lwjgl:lwjgl:3.2.3. Required by: project : > Cannot choose between the following variants of org.lwjgl:lwjgl:3.2.3: - natives-windows-runtime - natives-windows-x86-runtime
透過功能提供不同風味的函式庫
由於難以將 選用功能變異建模為具有 pom 元資料的獨立 jar,因此函式庫有時會組成具有不同功能集的不同 jar。也就是說,您不是從不同的功能變異組成函式庫的風味,而是從預先組成的變異中選取一個(在一個 jar 中提供所有功能)。其中一個此類函式庫是著名的依賴注入架構 Guice,已發布在 Maven 中央,它提供一個完整風味(主 jar)和一個不支援面向切面程式設計的精簡變異(guice-4.2.2-no_aop.jar
)。pom 元資料中未提及具有分類器的第二個變異。使用以下規則,我們根據該檔案建立編譯和執行時期變異,並透過名為 com.google.inject:guice-no_aop
的功能讓它可供選擇。
@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" }
}
}
}
}
}
@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 元資料中表達的資訊。我們現在可以選擇 guice-no_aop
變異,並將取得正確的 jar 檔案和正確的依賴項。
dependencies {
components {
withModule<GuiceRule>("com.google.inject:guice")
}
implementation("com.google.inject:guice:4.2.2") {
capabilities { requireCapability("com.google.inject:guice-no_aop") }
}
}
dependencies {
components {
withModule("com.google.inject:guice", GuiceRule)
}
implementation("com.google.inject:guice:4.2.2") {
capabilities { requireCapability("com.google.inject:guice-no_aop") }
}
}
新增遺失的功能以偵測衝突
功能的另一種用法是表示兩個不同的模組,例如 log4j
和 log4j-over-slf4j
,提供同一事物的替代實作。透過宣告兩者提供相同的功能,Gradle 僅在依賴圖中接受其中一個。此範例以及如何使用元件元資料規則來處理它,已在 功能建模 區段中詳細說明。
讓 Ivy 模組具備變異感知能力
具有 Ivy 元資料的模組預設沒有變異。但是,Ivy 組態可以對應到變異,因為 addVariant(name, baseVariantOrConfiguration)
接受任何已發布為基礎的 Ivy 組態。例如,這可以用於定義執行時期和編譯變異。可以在 這裡 找到對應規則的範例。也可以使用 withVariant(configurationName)
API 修改 Ivy 組態的 Ivy 詳細資料(例如依賴項和檔案)。但是,修改 Ivy 組態上的屬性或功能不會產生任何效果。
對於非常特定的 Ivy 使用案例,元件資料規則 API 也提供存取其他僅在 Ivy 資料中找到的詳細資料。這些資料可透過 IvyModuleDescriptor 介面取得,並可使用 ComponentMetadataContext 上的 getDescriptor(IvyModuleDescriptor)
存取。
@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"
}
}
}
@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 資料過濾
對於特定的 Maven 使用案例,元件資料規則 API 也提供存取其他僅在 POM 資料中找到的詳細資料。這些資料可透過 PomModuleDescriptor 介面取得,並可使用 ComponentMetadataContext 上的 getDescriptor(PomModuleDescriptor)
存取。
@CacheableRule
abstract class MavenComponentRule : ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
val descriptor = context.getDescriptor(PomModuleDescriptor::class)
if (descriptor != null && descriptor.packaging == "war") {
// ...
}
}
}
@CacheableRule
abstract class MavenComponentRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
def descriptor = context.getDescriptor(PomModuleDescriptor)
if (descriptor != null && descriptor.packaging == "war") {
// ...
}
}
}
修改元件層級的資料以進行比對
雖然上述所有範例都修改了元件的變異,但也可以對元件本身的資料進行有限度的修改。此資訊會影響相依性解析期間模組的 版本選取 程序,此程序會在選取元件的一個或多個變異之前執行。
元件上可用的第一個 API 是 belongsTo()
,用於建立虛擬平台以比對多個模組的版本,而無需使用 Gradle 模組資料。它在 比對未發佈到 Gradle 的模組版本 的章節中詳細說明。
修改元件層級的資料以根據狀態進行版本選取
Gradle 和 Gradle 模組元資料也允許在整個元件上設定屬性,而不是單一變體。這些屬性中的每個屬性都具有特殊語意,因為它們會影響版本選擇,而版本選擇是在變體選擇之前進行的。雖然變體選擇可以處理任何自訂屬性,但版本選擇只會考慮已實作特定語意的屬性。目前,唯一有意義的屬性是org.gradle.status
。因此,建議僅在元件層級修改此屬性(如果有)。已提供專屬的 API setStatus(value)
來執行此動作。若要修改元件所有變體的另一個屬性,應改用 withAllVariants { attributes {} }
。
當解析最新版本選擇器時,會考慮模組的狀態。特別是,latest.someStatus
將解析為具有狀態 someStatus
或更成熟狀態的最高模組版本。例如,latest.integration
將選擇最高模組版本,而不考慮其狀態(因為如以下所述,integration
是最不成熟的狀態),而 latest.release
將選擇具有狀態 release
的最高模組版本。
可以透過 setStatusScheme(valueList)
API 變更模組的狀態配置,進而影響狀態的詮釋。此概念會建模模組隨著時間推移和不同出版品而轉換的不同成熟度層級。預設狀態配置(從最不成熟的狀態到最成熟的狀態排序)為 integration
、milestone
、release
。org.gradle.status
屬性必須設定為元件狀態配置中的其中一個值。因此,每個元件始終都有狀態,其狀態會根據元資料以下列方式決定
-
Gradle 模組元資料:在元件上針對
org.gradle.status
屬性發布的值 -
Ivy 元資料:ivy.xml 中定義的
status
,如果遺失,預設為integration
-
Pom 元資料:對於具有 SNAPSHOT 版本的模組為
integration
,對於所有其他模組為release
下列範例示範基於元件元資料規則中宣告的自訂狀態配置的 latest
選擇器,此規則套用至所有模組
@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")
}
@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
。