使用 動態依賴版本 (例如,1.+[1.0,2.0)) 可能會導致建置意外中斷,因為解析的確切依賴版本可能會隨時間而改變

build.gradle.kts
dependencies {
    // Depend on the latest 5.x release of Spring available in the searched repositories
    implementation("org.springframework:spring-web:5.+")
}
build.gradle
dependencies {
    // Depend on the latest 5.x release of Spring available in the searched repositories
    implementation 'org.springframework:spring-web:5.+'
}

為了確保 可重現的建置,有必要鎖定版本的依賴關係及其傳遞依賴關係。這保證了使用相同輸入的建置將始終解析為相同的模組版本,這個過程稱為依賴鎖定

依賴鎖定是一個過程,Gradle 將已解析的依賴版本保存到鎖定檔案中,確保後續建置使用相同的依賴版本。此鎖定狀態儲存在檔案中,有助於防止依賴關係圖中發生意外變更。

依賴鎖定提供以下幾個主要優點

  • 避免連鎖故障:管理多個儲存庫的團隊不再需要依賴 -SNAPSHOT 或變更依賴關係,如果依賴關係引入錯誤或不相容性,可能會導致意外故障。

  • 動態版本彈性與穩定性:使用最新版本依賴關係的團隊可以在開發和測試階段依賴動態版本,僅在發行時鎖定它們。

  • 發佈已解析版本:透過將依賴鎖定與 發佈已解析版本 的實務結合使用,動態版本會在發佈時被實際解析的版本取代。

  • 最佳化建置快取使用率:由於動態或變更的依賴關係違反了 穩定任務輸入 的原則,因此鎖定依賴關係可確保任務具有一致的輸入。

  • 增強開發工作流程:開發人員可以在本地鎖定依賴關係以提高穩定性,同時處理功能或偵錯問題,而 CI 環境可以測試最新的 SNAPSHOT 或 nightly 版本,以便及早提供有關整合問題的回饋。這使團隊能夠在開發期間平衡穩定性和早期回饋。

為特定配置啟用鎖定

鎖定是針對每個 依賴配置 啟用的。

啟用後,您必須建立初始鎖定狀態,使 Gradle 驗證解析結果是否未變更。這確保如果選定的依賴關係與鎖定的依賴關係不同(由於有較新的版本可用),建置將會失敗,防止意外的版本變更。

依賴鎖定對於 動態版本 有效,但不變更版本(例如,-SNAPSHOT)一起使用,在變更版本中,坐標保持不變,但內容可能會變更。

將依賴鎖定與變更版本一起使用表示對這些功能理解有誤,可能會導致不可預測的結果。

如果解析結果中存在變更依賴關係,Gradle 將在保存鎖定狀態時發出警告。

配置的鎖定是透過 ResolutionStrategy API 進行的

build.gradle.kts
configurations {
    compileClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
}
build.gradle
configurations {
    compileClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
}

只有可以解析的配置才會附加鎖定狀態。在不可解析的配置上應用鎖定是無效的。

為所有配置啟用鎖定

以下程式碼鎖定所有配置

build.gradle.kts
dependencyLocking {
    lockAllConfigurations()
}
build.gradle
dependencyLocking {
    lockAllConfigurations()
}

上述程式碼將鎖定所有專案配置,但不鎖定 buildscript 配置。

為特定配置停用鎖定

您也可以停用特定配置的鎖定。

如果外掛程式在所有配置上配置了鎖定,但您碰巧新增了一個不應鎖定的配置,這會很有用

build.gradle.kts
configurations.compileClasspath {
    resolutionStrategy.deactivateDependencyLocking()
}
build.gradle
configurations {
    compileClasspath {
        resolutionStrategy.deactivateDependencyLocking()
    }
}

為 buildscript classpath 配置啟用鎖定

如果您將外掛程式應用於您的建置,您可能也希望在那裡利用依賴鎖定。

若要鎖定用於腳本外掛程式的 classpath 配置

build.gradle.kts
buildscript {
    configurations.classpath {
        resolutionStrategy.activateDependencyLocking()
    }
}
build.gradle
buildscript {
    configurations.classpath {
        resolutionStrategy.activateDependencyLocking()
    }
}

產生和更新依賴鎖

若要產生或更新鎖定狀態,請在調用任何會觸發要解析的鎖定配置的任務時,新增 --write-locks 引數

$ ./gradlew dependencies --write-locks

這將在該建置執行期間為每個已解析的配置建立或更新鎖定狀態。如果鎖定狀態已存在,它將被覆寫。

gradle.lockfile
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.google.code.findbugs:jsr305:3.0.2=classpath
com.google.errorprone:error_prone_annotations:2.3.2=classpath
com.google.gradle:osdetector-gradle-plugin:1.7.1=classpath
com.google.guava:failureaccess:1.0.1=classpath
com.google.guava:guava:28.1-jre=classpath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath
com.google.j2objc:j2objc-annotations:1.3=classpath
empty=
如果建置失敗,Gradle 將不會將鎖定狀態寫入磁碟,從而防止持久化可能無效的狀態。

在單次建置執行中鎖定所有配置

使用多個配置時,您可能希望在單次建置執行中一次鎖定所有配置。您有兩個選項可以執行此操作

  1. 執行 gradle dependencies --write-locks

    • 此命令將鎖定所有已啟用鎖定的可解析配置。

    • 在多專案設定中,請注意 dependencies 僅在一個專案上執行,通常是根專案。

  2. 宣告自訂任務以解析所有配置

    • 如果您需要更精確地控制鎖定哪些配置,此方法特別有用。

此自訂任務解析所有配置,並在此過程中鎖定它們

build.gradle.kts
tasks.register("resolveAndLockAll") {
    notCompatibleWithConfigurationCache("Filters configurations at execution time")
    doFirst {
        require(gradle.startParameter.isWriteDependencyLocks) { "$path must be run from the command line with the `--write-locks` flag" }
    }
    doLast {
        configurations.filter {
            // Add any custom filtering on the configurations to be resolved
            it.isCanBeResolved
        }.forEach { it.resolve() }
    }
}
build.gradle
tasks.register('resolveAndLockAll') {
    notCompatibleWithConfigurationCache("Filters configurations at execution time")
    doFirst {
        assert gradle.startParameter.writeDependencyLocks : "$path must be run from the command line with the `--write-locks` flag"
    }
    doLast {
        configurations.findAll {
            // Add any custom filtering on the configurations to be resolved
            it.canBeResolved
        }.each { it.resolve() }
    }
}

透過篩選和解析特定配置,您可以確保僅鎖定相關的配置,從而根據專案的需求量身定制鎖定過程。這在原生建置等環境中特別有用,在這些環境中,並非所有配置都可以在單一平台上解析。

了解鎖定狀態位置和格式

鎖定檔案是一個關鍵組件,它記錄專案中使用的依賴關係的確切版本,允許在建置期間進行驗證,以確保不同環境和不同時間的一致結果。當專案在不同機器上或在不同時間建置時,它有助於識別依賴關係中的差異。

鎖定檔案應簽入原始碼控制。

鎖定檔案的位置

  • 鎖定狀態保存在名為 gradle.lockfile 的檔案中,該檔案位於每個專案或子專案目錄的根目錄中。

  • 例外情況是 buildscript 本身的鎖定檔案,它被命名為 buildscript-gradle.lockfile

鎖定檔案的結構

考慮以下依賴關係宣告

build.gradle.kts
configurations {
    compileClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
    runtimeClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
    annotationProcessor {
        resolutionStrategy.activateDependencyLocking()
    }
}

dependencies {
    implementation("org.springframework:spring-beans:[5.0,6.0)")
}
build.gradle
configurations {
    compileClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
    runtimeClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
    annotationProcessor {
        resolutionStrategy.activateDependencyLocking()
    }
}

dependencies {
    implementation 'org.springframework:spring-beans:[5.0,6.0)'
}

使用上述配置,產生的 gradle.lockfile 將如下所示

gradle.lockfile
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
org.springframework:spring-beans:5.0.5.RELEASE=compileClasspath, runtimeClasspath
org.springframework:spring-core:5.0.5.RELEASE=compileClasspath, runtimeClasspath
org.springframework:spring-jcl:5.0.5.RELEASE=compileClasspath, runtimeClasspath
empty=annotationProcessor

其中

  • 每一行代表 group:artifact:version 格式的單個依賴關係。

  • 配置: 在版本之後,列出了包含依賴關係的配置。

  • 排序: 依賴關係和配置按字母順序排列,以便更輕鬆地管理版本控制差異。

  • 空配置: 最後一行列出了空配置,表示它們不包含任何依賴關係。

鎖定檔案應包含在原始碼控制中,以確保所有團隊成員和環境都使用完全相同的依賴版本。

遷移您的舊版鎖定檔案

如果您的專案使用每個鎖定配置一個檔案的舊版鎖定檔案格式,請按照以下指示遷移到新格式

  1. 請遵循有關 寫入更新 依賴鎖定狀態的文件。

  2. 在寫入每個專案的單個鎖定檔案後,Gradle 也會刪除已傳輸狀態的所有每個配置的鎖定檔案。

遷移可以一次完成一個配置。只要單個鎖定檔案中沒有該配置的資訊,Gradle 就會繼續從每個配置的檔案中獲取鎖定狀態。

配置每個專案的鎖定檔案名稱和位置

當使用每個專案的單個鎖定檔案時,您可以配置其名稱和位置。

此功能允許您根據專案屬性指定檔案名稱,使單個專案能夠為不同的執行上下文儲存不同的鎖定狀態。

例如,在 JVM 生態系統中,Scala 版本通常包含在成品坐標中

build.gradle.kts
val scalaVersion = "2.12"
dependencyLocking {
    lockFile = file("$projectDir/locking/gradle-${scalaVersion}.lockfile")
}
build.gradle
def scalaVersion = "2.12"
dependencyLocking {
    lockFile = file("$projectDir/locking/gradle-${scalaVersion}.lockfile")
}

在存在鎖定狀態的情況下執行建置

當建置需要解析已啟用鎖定的配置,並且找到相符的鎖定狀態時,它將使用它來驗證給定的配置是否仍解析相同的版本。

成功的建置表示您的建置使用的依賴關係與鎖定狀態中儲存的依賴關係相同,無論您的建置使用的任何倉庫中是否有與動態選擇器相符的新版本。

完整的驗證如下

  • 鎖定狀態中的現有條目必須在建置中相符

    • 版本不符或缺少已解析的模組會導致建置失敗

  • 與鎖定狀態相比,解析結果不得包含額外的依賴關係

使用鎖定模式微調依賴鎖定行為

雖然預設鎖定模式的行為如上所述,但還有兩種其他模式可用

嚴格模式

在此模式下,除了上述驗證之外,如果標記為已鎖定的配置沒有與其關聯的鎖定狀態,依賴鎖定將會失敗。

寬鬆模式

在此模式下,依賴鎖定仍會釘選動態版本,但對依賴解析的其他變更不再是錯誤。其他變更包括

  • 新增或移除依賴關係,即使它們是嚴格版本控制的,也不會導致建置失敗。

  • 允許傳遞依賴關係發生變動,只要動態版本仍然被釘選即可。

此模式為您可能想要探索或測試新依賴關係或版本變更而不會中斷建置的情況提供彈性,使其適用於測試 nightly 或 snapshot 建置。

鎖定模式可以從 dependencyLocking 區塊控制,如下所示

build.gradle.kts
dependencyLocking {
    lockMode = LockMode.STRICT
}
build.gradle
dependencyLocking {
    lockMode = LockMode.STRICT
}

選擇性地更新鎖定狀態條目

為了僅更新配置的特定模組,您可以使用 --update-locks 命令列標誌。它採用逗號 (,) 分隔的模組表示法列表。在此模式下,現有的鎖定狀態仍然用作解析的輸入,過濾掉更新目標的模組

$ ./gradlew dependencies --update-locks org.apache.commons:commons-lang3,org.slf4j:slf4j-api

萬用字元(以 * 表示)可以用於群組或模組名稱中。它們可以是唯一的字元,也可以分別出現在群組或模組的末尾。以下萬用字元表示法範例是有效的

  • org.apache.commons:*:將允許屬於群組 org.apache.commons 的所有模組更新

  • *:guava:將允許所有名為 guava 的模組(無論其群組為何)更新

  • org.springframework.spring*:spring*:將允許所有群組以 org.springframework.spring 開頭且名稱以 spring 開頭的模組更新

解析可能會導致其他模組版本更新,這由 Gradle 解析規則決定。

停用依賴鎖定

若要停用配置的依賴鎖定

  1. 移除鎖定配置: 確保您不再想要鎖定的配置未配置依賴鎖定。這表示移除或註解掉該配置的任何 activateDependencyLocking() 呼叫。

  2. 更新鎖定狀態: 下次您更新並保存鎖定狀態(使用 --write-locks 選項)時,Gradle 將自動清理與不再鎖定的配置關聯的任何過時鎖定狀態。

Gradle 必須解析不再標記為鎖定的配置,才能偵測並移除關聯的鎖定狀態。如果不解析配置,Gradle 無法識別應清理哪個鎖定狀態。

從鎖定狀態中忽略特定依賴關係

在某些情況下,您可能希望將依賴鎖定用於建置可重現性以外的其他原因。

作為建置作者,您可能希望某些依賴關係比其他依賴關係更頻繁地更新。例如,組織內部的內部依賴關係可能始終使用最新版本,而第三方依賴關係則遵循不同的更新週期。

這種方法可能會損害可重現性。請考慮針對特定情況使用不同的鎖定模式單獨的鎖定檔案

您可以在 dependencyLocking 專案擴充功能中配置要忽略的依賴關係

build.gradle.kts
dependencyLocking {
    ignoredDependencies.add("com.example:*")
}
build.gradle
dependencyLocking {
    ignoredDependencies.add('com.example:*')
}

表示法 <group>:<name> 用於指定依賴關係,其中 * 充當尾隨萬用字元。請注意,不接受 *:*,因為它實際上停用了鎖定。請參閱有關更新鎖定檔案的說明以取得更多詳細資訊。

忽略依賴關係將具有以下影響

  • 忽略的依賴關係適用於所有鎖定的配置,並且設定是專案範圍的。

  • 忽略依賴關係不會將其傳遞依賴關係從鎖定狀態中排除。

  • 沒有驗證可以確保忽略的依賴關係存在於任何配置解析中。

  • 如果依賴關係存在於鎖定狀態中,則載入它將過濾掉該依賴關係。

  • 如果依賴關係存在於解析結果中,則在根據鎖定狀態驗證解析時,將忽略該依賴關係。

  • 當鎖定狀態更新並持久化時,任何忽略的依賴關係都將從寫入的鎖定狀態中省略。

了解鎖定限制

  • 依賴鎖定目前不適用於原始碼依賴關係。