使用動態相依性版本(例如 1.+[1.0,2.0))會讓建置無法確定。這會導致建置在沒有明顯變更的情況下中斷,更糟的是,這可能是由建置作者無法控制的傳遞相依性所造成的。

要達成 可重製建置,必須鎖定相依性和傳遞相依性的版本,以便輸入相同的建置會永遠解析相同的模組版本。這稱為相依性鎖定

它特別能啟用下列場景

  • 處理多個儲存庫的公司不再需要依賴 -SNAPSHOT 或變更相依性,這有時會在相依性引進錯誤或不相容性時導致連鎖失敗。現在,相依性可以針對主要或次要版本範圍宣告,讓使用者能夠在 CI 上使用最新版本,同時在穩定的開發人員建置中利用鎖定。

  • 永遠想要使用最新相依性的團隊可以使用動態版本,只在發行時鎖定其相依性。發行標籤會包含鎖定狀態,讓該建置在需要開發錯誤修正時可以完全重製。

搭配 發佈已解析版本,您也可以在發佈時取代宣告的動態版本部分。消費者會看到您的發行版所解析的版本。

鎖定會針對每個 相依性設定 啟用。啟用後,您必須建立一個初始鎖定狀態。這會讓 Gradle 驗證解析結果不會變更,即使產生較新的版本,也會選取相同的相依性。會影響已解析相依性組的組建修改,會導致組建失敗。這可以確保已發布相依性或組建定義中的變更,不會在未調整鎖定狀態的情況下變更解析。

相依性鎖定只對 動態版本 有意義。它不會對 變更版本 (例如 -SNAPSHOT) 產生影響,其座標保持相同,但內容可能會變更。當持續鎖定狀態且解析結果中存在變更相依性時,Gradle 甚至會發出警告。

在設定中啟用鎖定

設定的鎖定透過 ResolutionStrategy 進行

build.gradle.kts
configurations {
    compileClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
}
build.gradle
configurations {
    compileClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
}
只有可以解析的設定才會附加鎖定狀態。在不可解析的設定中套用鎖定只是一個空操作。

或以下,作為鎖定所有設定的方式

build.gradle.kts
dependencyLocking {
    lockAllConfigurations()
}
build.gradle
dependencyLocking {
    lockAllConfigurations()
}
上述範例會鎖定所有專案設定,但不會鎖定組建指令碼設定。

您也可以在特定設定中停用鎖定。如果外掛設定在所有設定中鎖定,但您恰好新增一個不應鎖定的設定,這會很有用。

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

鎖定組建指令碼類別路徑設定

如果您將外掛套用到您的組建,您可能也想要在那裡利用相依性鎖定。若要鎖定用於指令碼外掛的 classpath 設定,請執行以下操作

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

產生和更新相依性鎖定

若要產生或更新鎖定狀態,您需要指定 --write-locks 命令列引數,以及會觸發設定解析的正常工作。這會在該組建執行中為每個已解析的設定建立鎖定狀態。請注意,如果先前存在鎖定狀態,它會被覆寫。

如果組建失敗,Gradle 就不會將鎖定狀態寫入磁碟。這可以防止持續可能無效的狀態。

在一次組建執行中鎖定所有設定

在鎖定多個設定時,您可能想要在一次組建執行期間一次鎖定所有設定。

針對此情況,您有兩個選項

  • 執行 `gradle dependencies --write-locks`。這將有效鎖定已啟用鎖定的所有可解析設定檔。請注意,在多個專案設定中,`dependencies` 僅在 *一個* 專案執行,在本例中為根專案。

  • 宣告自訂任務來解析所有設定檔。這不適用於 Android 專案。

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`。

鎖定檔案將具有以下內容

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` 符號中的單一相依性

  • 然後列出包含給定相依性的所有設定檔

  • 模組和設定檔按字母順序排列,以簡化差異

  • 檔案的最後一行列出所有空的設定檔,即已知沒有相依性的設定檔

這與以下相依性宣告相符

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 也會刪除已傳輸狀態的每個設定檔的鎖定檔案。

移轉可以一次針對一個設定檔進行。只要單一鎖定檔案中沒有該設定檔的資訊,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")
}

執行建置時存在鎖定狀態

當建置需要解析已啟用鎖定的組態,並且找到符合的鎖定狀態時,它會使用該狀態驗證指定的組態是否仍解析相同的版本。

建置成功表示使用與鎖定狀態中儲存的相同依賴項,不論是否已產生符合動態選擇器的版本。

完整的驗證如下

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

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

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

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

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

嚴格模式

在此模式中,除了上述驗證外,如果標記為已鎖定的組態沒有關聯的鎖定狀態,依賴項鎖定會失敗。

寬鬆模式

在此模式中,依賴項鎖定仍會固定動態版本,但依賴項解析的其他變更不再是錯誤。

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

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

選擇性更新鎖定狀態項目

若要只更新組態中的特定模組,您可以使用 --update-locks 命令列旗標。它會使用逗號 (,) 分隔的模組表示法清單。在此模式中,現有的鎖定狀態仍用作解析的輸入,並篩選出更新目標的模組。

❯ gradle classes --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. 請確定您不再想要鎖定的組態未設定為鎖定。

  2. 下次您更新儲存鎖定狀態時,Gradle 會自動清除所有過期的鎖定狀態。

Gradle 需要解析一個組態(不再標記為已鎖定)才能偵測可以捨棄關聯的鎖定狀態。

從鎖定狀態忽略特定相依性

相依性鎖定可用於可重複性並非主要目標的情況。作為建置作者,您可能希望根據相依性來源(例如)來設定不同的相依性版本更新頻率。在這種情況下,忽略某些相依性可能會很方便,因為您總是希望對這些相依性使用最新版本。一個範例是組織中的內部相依性,它應該總是使用最新版本,而不是具有不同升級週期的第三方相依性。

此功能可能會破壞可重複性,應謹慎使用。有些場景更適合利用 不同的鎖定模式使用不同的鎖定檔名稱

您可以在 dependencyLocking 專案延伸模組中設定忽略的相依性

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

符號是一個 <group>:<name> 相依性符號,其中 * 可用作尾端萬用字元。有關更多詳細資訊,請參閱 更新鎖定檔的說明。請注意,值 *:* 不被接受,因為它等於停用鎖定。

忽略相依性將產生以下影響

  • 忽略的相依性適用於所有已鎖定的組態。設定為專案範圍。

  • 忽略相依性並不表示鎖定狀態會忽略其傳遞相依性。

  • 沒有驗證忽略的相依性是否存在於任何組態解析中。

  • 如果相依性存在於鎖定狀態中,載入它會過濾掉相依性。

  • 如果依賴項存在於解析結果中,則在驗證解析是否與鎖定狀態相符時,將會忽略該依賴項。

  • 最後,如果依賴項存在於解析結果中且鎖定狀態已持續存在,則該依賴項將不存在於寫入的鎖定狀態中。

鎖定限制

  • 鎖定尚無法套用於來源依賴項。