相依性版本對齊允許屬於相同邏輯群組(平台)的不同模組在相依性圖中具有相同的版本。

處理不一致的模組版本

Gradle 支援對齊屬於相同「平台」的模組版本。例如,通常較佳的做法是元件的 API 和實作模組使用相同版本。然而,由於傳遞相依性解析的遊戲,屬於相同平台的不同模組最終可能使用不同的版本。例如,您的專案可能相依於 jackson-databindvert.x 函式庫,如下所示

範例 1. 宣告相依性
build.gradle.kts
dependencies {
    // a dependency on Jackson Databind
    implementation("com.fasterxml.jackson.core:jackson-databind:2.8.9")

    // and a dependency on vert.x
    implementation("io.vertx:vertx-core:3.5.3")
}
build.gradle
dependencies {
    // a dependency on Jackson Databind
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.9'

    // and a dependency on vert.x
    implementation 'io.vertx:vertx-core:3.5.3'
}

由於 vert.x 相依於 jackson-core,我們實際上會解析下列相依性版本

  • jackson-core 版本 2.9.5(由 vertx-core 帶入)

  • jackson-databind 版本 2.9.5(由衝突解析)

  • jackson-annotation 版本 2.9.0jackson-databind:2.9.5 的相依性)

很容易就會得到一組無法順利搭配運作的版本。為了修正此問題,Gradle 支援相依性版本對齊,而這由平台的概念所支援。平台代表一組「能順利搭配運作」的模組。原因可能是它們實際上以整體形式發布(當平台的其中一個成員發布時,所有其他模組也會以相同版本發布),或因為有人測試過這些模組並指出它們能順利搭配運作(通常是 Spring 平台)。

使用 Gradle 原生對齊版本

Gradle 原生支援對齊由 Gradle 製作的模組。這是 相依性約束 傳遞性的直接結果。因此,如果您有一個多專案組建,而且您希望使用者取得所有模組的相同版本,Gradle 提供了一個簡單的方法來使用 Java Platform 外掛程式 執行此操作。

例如,如果您有一個由 3 個模組組成的專案

  • lib

  • utils

  • core,相依於 libutils

以及宣告下列相依項目的使用者

  • core 版本 1.0

  • lib 版本 1.1

則預設解析會選取 core:1.0lib:1.1,因為 lib 沒有相依於 core。我們可以透過在專案中新增一個模組,一個會對專案所有模組新增限制的平台,來修正這個問題

範例 2. 平台模組
build.gradle.kts
plugins {
    `java-platform`
}

dependencies {
    // The platform declares constraints on all components that
    // require alignment
    constraints {
        api(project(":core"))
        api(project(":lib"))
        api(project(":utils"))
    }
}
build.gradle
plugins {
    id 'java-platform'
}

dependencies {
    // The platform declares constraints on all components that
    // require alignment
    constraints {
        api(project(":core"))
        api(project(":lib"))
        api(project(":utils"))
    }
}

完成後,我們需要確認所有模組現在相依於平台,像這樣

build.gradle.kts
dependencies {
    // Each project has a dependency on the platform
    api(platform(project(":platform")))

    // And any additional dependency required
    implementation(project(":lib"))
    implementation(project(":utils"))
}
build.gradle
dependencies {
    // Each project has a dependency on the platform
    api(platform(project(":platform")))

    // And any additional dependency required
    implementation(project(":lib"))
    implementation(project(":utils"))
}

平台包含所有元件的限制非常重要,但每個元件相依於平台也很重要。這樣一來,每當 Gradle 在圖表中新增相依項至平台的模組時,它也會包含對平台其他模組的限制。這表示如果我們看到屬於同一平台的其他模組,我們會自動升級至相同版本。

在我們的範例中,表示我們首先看到 core:1.0,它會帶來一個包含對 lib:1.0lib:1.0 的限制的平台 1.0。然後我們新增相依於 platform:1.1lib:1.1。透過衝突解析,我們選取包含對 core:1.1 的限制的平台 1.1。然後我們在 core:1.0core:1.1 之間進行衝突解析,表示 corelib 現在已正確比對。

只有在您使用 Gradle 模組元資料時,才會對已發佈的元件強制執行此行為。

比對未與 Gradle 發佈的模組版本

每當發佈者未如我們的 Jackson 範例中使用 Gradle,我們可以向 Gradle 說明所有 Jackson 模組「屬於」同一個平台,並受益於與原生比對相同的行為。有兩種方式可以表達一組模組屬於一個平台

  1. 平台會作為 BOM 發佈,並可以使用:例如,com.fasterxml.jackson:jackson-bom 可以用作平台。在這種情況下,Gradle 缺少的資訊是,如果使用其中一個成員,就應該將平台新增至相依項。

  2. 無法使用現有的平台。相反地,應該由 Gradle 建立一個虛擬平台:在這種情況下,Gradle 會根據所有使用的成員建立平台本身。

若要提供 Gradle 缺少的資訊,您可以定義 元件元資料規則,如下所述。

使用已發佈的 BOM 對齊模組版本

build.gradle.kts
abstract class JacksonBomAlignmentRule: ComponentMetadataRule {
    override fun execute(ctx: ComponentMetadataContext) {
        ctx.details.run {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules belong to the platform defined by the Jackson BOM
                belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
            }
        }
    }
}
build.gradle
abstract class JacksonBomAlignmentRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext ctx) {
        ctx.details.with {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules belong to the platform defined by the Jackson BOM
                belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
            }
        }
    }
}

透過將 belongsTofalse (虛擬) 搭配使用,我們宣告所有模組都屬於同一個已發佈平台。在本例中,平台為 com.fasterxml.jackson:jackson-bom,而 Gradle 會在宣告的儲存庫中尋找它,就像任何其他模組一樣。

build.gradle.kts
dependencies {
    components.all<JacksonBomAlignmentRule>()
}
build.gradle
dependencies {
    components.all(JacksonBomAlignmentRule)
}

使用此規則時,上述範例中的版本會對齊至 com.fasterxml.jackson:jackson-bom 所定義的任何選取版本。在本例中,com.fasterxml.jackson:jackson-bom:2.9.5 會被選取,因為 2.9.5 是選取的模組中最高版本。在該 BOM 中,定義並會使用下列版本:jackson-core:2.9.5jackson-databind:2.9.5jackson-annotation:2.9.0jackson-annotation 較低版本可能是預期結果,因為這是 BOM 建議的版本。

此行為自 Gradle 6.1 起可靠運作。實際上,它類似於 元件資料規則,它會使用 withDependencies 將平台相依性新增至平台的所有成員。

在沒有已發佈平台的情況下對齊模組版本

build.gradle.kts
abstract class JacksonAlignmentRule: ComponentMetadataRule {
    override fun execute(ctx: ComponentMetadataContext) {
        ctx.details.run {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules all belong to the Jackson virtual platform
                belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
            }
        }
    }
}
build.gradle
abstract class JacksonAlignmentRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext ctx) {
        ctx.details.with {
            if (id.group.startsWith("com.fasterxml.jackson")) {
                // declare that Jackson modules all belong to the Jackson virtual platform
                belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
            }
        }
    }
}

透過使用 belongsTo 關鍵字而沒有其他參數(平台虛擬的),我們宣告所有模組都屬於同一個虛擬平台,而引擎會特別處理此平台。虛擬平台不會從儲存庫中擷取。識別碼(在本例中為 com.fasterxml.jackson:jackson-virtual-platform)是由您作為建置作者自行定義。然後,Gradle 會透過收集指向同一個虛擬平台的所有 belongsTo 陳述,動態建立平台的「內容」。

build.gradle.kts
dependencies {
    components.all<JacksonAlignmentRule>()
}
build.gradle
dependencies {
    components.all(JacksonAlignmentRule)
}

使用規則,上述範例中所有版本都會與 2.9.5 對齊。在此情況下,也會採用 jackson-annotation:2.9.5,因為這是我們定義本機虛擬平台的方式。

對於已發布和虛擬平台,Gradle 允許您透過指定平台上的強制相依性,來覆寫平台本身的版本選項

build.gradle.kts
dependencies {
    // Forcefully downgrade the virtual Jackson platform to 2.8.9
    implementation(enforcedPlatform("com.fasterxml.jackson:jackson-virtual-platform:2.8.9"))
}
build.gradle
dependencies {
    // Forcefully downgrade the virtual Jackson platform to 2.8.9
    implementation enforcedPlatform('com.fasterxml.jackson:jackson-virtual-platform:2.8.9')
}