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

處理不一致的模組版本

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

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.0 (jackson-databind:2.9.5 的依賴)

很容易最終得到一組無法良好協同工作的版本。為了修正這個問題,Gradle 支援依賴版本對齊,這由平台的概念支援。平台代表一組「良好協同工作」的模組。原因可能是它們實際上是作為整體發佈的(當平台的成員之一發佈時,所有其他模組也以相同的版本發佈),或者因為有人測試了這些模組並表明它們可以良好協同工作(通常是 Spring Platform)。

使用 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,它引入了平台 1.0,其中包含對 lib:1.0lib:1.0 的約束。然後我們添加 lib:1.1,它依賴於 platform:1.1。通過衝突解決,我們選擇 1.1 平台,它對 core:1.1 有約束。然後我們在 core:1.0core:1.1 之間進行衝突解決,這意味著 corelib 現在已正確對齊。

只有當您使用 Gradle Module Metadata 時,才會對已發佈的元件強制執行此行為。

對齊未使用 Gradle 發佈的模組版本

每當發佈者不使用 Gradle 時,例如在我們的 Jackson 範例中,我們可以向 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.0。這裡較低版本的 jackson-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')
}