在某些情況下,您可能希望完全控制相依性圖表。特別是,您可能希望確保

  • 建置指令碼中宣告的版本實際上與解析的版本相符

  • 或確保相依性解析隨著時間推移而可重製

Gradle 提供透過設定解析策略來執行此操作的方法。

版本衝突時失敗

當 Gradle 在相依性圖表中找到兩個不同版本的相同模組時,就會發生版本衝突。預設情況下,Gradle 會執行樂觀升級,表示如果圖表中找到版本 1.11.3,我們會解析為最高版本 1.3。但是,很容易遺漏某些相依性因傳遞相依性而升級。在上面的範例中,如果 1.1 是建置指令碼中使用的版本,而 1.3 是傳遞引進的版本,您可以在沒有實際注意到的情況下使用 1.3

為了確保您知道此類升級,Gradle 提供了一種模式,可以在設定中啟用。想像以下相依性宣告

build.gradle.kts
dependencies {
    implementation("org.apache.commons:commons-lang3:3.0")
    // the following dependency brings lang3 3.8.1 transitively
    implementation("com.opencsv:opencsv:4.6")
}
build.gradle
dependencies {
    implementation 'org.apache.commons:commons-lang3:3.0'
    // the following dependency brings lang3 3.8.1 transitively
    implementation 'com.opencsv:opencsv:4.6'
}

然後,預設情況下,Gradle 會升級 commons-lang3,但有可能失敗建置

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnVersionConflict()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnVersionConflict()
    }
}

確保解析結果可重現

在某些情況下,相依性解析會隨著時間推移而不穩定。也就是說,如果你在 D 日建置,在 D+x 日建置可能會得到不同的解析結果。

在下列情況下可能會發生這種情況

  • 使用動態相依性版本(版本範圍、latest.release1.+ 等)

  • 或使用變動中的版本(SNAPSHOT、內容變動的固定版本等)

處理動態版本的建議方法是使用 相依性鎖定。不過,也可以完全禁止使用動態版本,這是一種替代策略

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnDynamicVersions()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnDynamicVersions()
    }
}

同樣地,也可以透過啟用這個標記來禁止使用變動中的版本

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnChangingVersions()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnChangingVersions()
    }
}

在發布時讓變動版本失敗是一個好習慣。

最後,可以使用單一呼叫來同時讓動態版本和變動版本失敗

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnNonReproducibleResolution()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnNonReproducibleResolution()
    }
}

取得一致的相依性解析結果

相依性解析一致性是一個正在孵化的功能

一個常見的誤解是,應用程式只有一個相依性圖。事實上,Gradle 會在建置期間解析許多不同的相依性圖,即使是在單一專案中。例如,編譯時使用的相依性圖與執行時使用的相依性圖不同。一般來說,執行時的相依性圖是編譯相依性的超集(有例外,例如有些相依性會在執行時二進位檔中重新封裝)。

Gradle 獨立解析這些相依性圖。這表示,例如在 Java 生態系中,「編譯類別路徑」的解析不會影響「執行時類別路徑」的解析。同樣地,測試相依性可能會提升生產相依性的版本,導致執行測試時出現一些令人驚訝的結果。

可以透過啟用相依性解析一致性來減輕這些令人驚訝的行為。

啟用專案本機相依性解析一致性

例如,想像您的 Java 函式庫仰賴下列函式庫

build.gradle.kts
dependencies {
    implementation("org.codehaus.groovy:groovy:3.0.1")
    runtimeOnly("io.vertx:vertx-lang-groovy:3.9.4")
}
build.gradle
dependencies {
    implementation 'org.codehaus.groovy:groovy:3.0.1'
    runtimeOnly 'io.vertx:vertx-lang-groovy:3.9.4'
}

接著解析 compileClasspath 組態會將 groovy 函式庫解析為預期的版本 3.0.1。然而,解析 runtimeClasspath 組態會傳回 groovy 3.0.2

原因是 vertx 的傳遞相依性(為 runtimeOnly 相依性)帶來較新版本的 groovy。一般來說,這不是問題,但也表示您在執行階段要使用的 Groovy 函式庫版本會與編譯時使用的版本不同。

為了避免這種情況,Gradle 提供一個 API 來說明組態應該一致地解析。

宣告組態之間的解析一致性

在上述範例中,我們可以宣告我們想要在執行階段使用與編譯階段相同的常見相依性版本,方法是宣告「執行階段類別路徑」應與「編譯階段類別路徑」一致

build.gradle.kts
configurations {
    runtimeClasspath.get().shouldResolveConsistentlyWith(compileClasspath.get())
}
build.gradle
configurations {
    runtimeClasspath.shouldResolveConsistentlyWith(compileClasspath)
}

結果是 runtimeClasspathcompileClasspath 都會解析 Groovy 3.0.1。

關係是導向的,表示如果必須解析 runtimeClasspath 組態,Gradle 會解析 compileClasspath,然後將解析結果「注入」為 嚴格約束runtimeClasspath

如果因為某些原因而無法「對齊」兩個圖表的版本,解析就會失敗並呼叫動作。

在 Java 生態系中宣告一致的解析

上述的 runtimeClasspathcompileClasspath 範例在 Java 生態系中很常見。不過,只宣告這兩個組態之間的一致性通常不夠。例如,您很可能會希望測試執行階段類別路徑執行階段類別路徑一致。

為了讓這更容易,Gradle 提供一種方式來使用 java 擴充功能為 Java 生態系組態一致的解析

build.gradle.kts
java {
    consistentResolution {
        useCompileClasspathVersions()
    }
}
build.gradle
java {
    consistentResolution {
        useCompileClasspathVersions()
    }
}

請參閱 Java 外掛擴充文件 以取得更多組態選項。