Bazel Lockfile

7.3 · 7.2 · 7.1 · 7.0 · 6.5

Bazel 的 Lockfile 功能可記錄專案所需的特定版本或依附元件。透過儲存模組解析度和擴充功能評估的結果,達成了這個目標。鎖定檔案可促進可重現的建構作業,確保開發環境一致。此外,它還可讓 Bazel 略過解析程序中不受專案依附元件變更影響的部分,進而提升建構效率。此外,鎖定檔案可防止外部程式庫發生意外更新或破壞性變更,進而提升穩定性,降低引入錯誤的風險。

產生鎖定檔案

這個 Lockfile 會在工作區根目錄下產生,名為 MODULE.bazel.lock。這項資訊會在建構程序期間建立或更新,特別是在模組解析和擴充功能評估之後。重要的是,其中只包含目前版本叫用中包含的依附元件。

當專案發生變更,並影響其依附元件時,系統會自動更新鎖定檔案,以反映新的狀態。這麼做可確保鎖定檔案持續專注於目前版本所需的特定依附元件組合,並準確呈現專案已解析的依附元件。

鎖定檔案用途

鎖定檔案可透過標記 --lockfile_mode 進行控制,在專案狀態與鎖定檔案不同時,可自訂 Bazel 的行為。可用的模式如下:

  • update (預設):使用 Lockfile 中的資訊,略過已知登錄檔案的下載作業,避免重新評估其結果仍為最新狀態的擴充功能。如果缺少資訊,系統會將資訊新增至鎖定檔案。在這個模式下,Bazel 也會避免針對未變更的依附元件重新整理可變動資訊 (例如變形版本)。
  • refresh:與 update 類似,但在切換至此模式時,以及在這個模式下大約每小時,變更資訊一律會重新整理。
  • error:類似 update,但如果缺少任何資訊或資訊過時,Bazel 就會失敗並顯示錯誤。這個模式絕不會變更鎖定檔案,也不會在解析期間執行網路要求。標示為 reproducible 的模組擴充功能仍可執行網路要求,但預期一律會產生相同結果。
  • off:未檢查或更新 Lockfile。

鎖定檔案的優點

鎖定檔案有許多優點,而且可以透過各種方式使用:

  • 可重現的建構作業:透過擷取軟體程式庫的特定版本或依附元件,鎖定檔案可確保建構作業可在不同環境和一段時間後重現。開發人員在建構專案時,可以依賴一致且可預測的結果。

  • 快速增加解析度。鎖定檔可讓 Bazel 避免下載先前建構中已使用的登錄檔案。這可大幅改善建構效率,特別是在解析度可能費時。

  • 穩定性與風險降低:鎖定檔案可防止外部程式庫發生意外更新或破壞性變更,進而維持穩定性。將依附元件鎖定於特定版本後,就能降低因不相容或未經測試的更新而引入錯誤的風險。

鎖定檔案內容

鎖定檔案包含判斷專案狀態是否已變更所需的所有必要資訊。並包含在目前狀態下建構專案的結果。鎖定檔案包含兩個主要部分:

  1. 所有屬於模組解析度的遠端檔案,其雜湊值。
  2. 對於每個模組擴充功能,鎖定檔案都會納入影響該擴充功能的輸入內容,以 bzlTransitiveDigestusagesDigest 和其他欄位表示,以及執行該擴充功能的輸出內容,稱為 generatedRepoSpecs

以下範例呈現了 Lockfile 的結構,以及各區段的說明:

{
  "lockFileVersion": 10,
  "registryFileHashes": {
    "https://bcr.bazel.build/bazel_registry.json": "8a28e4af...5d5b3497",
    "https://bcr.bazel.build/modules/foo/1.0/MODULE.bazel": "7cd0312e...5c96ace2",
    "https://bcr.bazel.build/modules/foo/2.0/MODULE.bazel": "70390338... 9fc57589",
    "https://bcr.bazel.build/modules/foo/2.0/source.json": "7e3a9adf...170d94ad",
    "https://registry.mycorp.com/modules/foo/1.0/MODULE.bazel": "not found",
    ...
  },
  "selectedYankedVersions": {
    "foo@2.0": "Yanked for demo purposes"
  },
  "moduleExtensions": {
    "//:extension.bzl%lockfile_ext": {
      "general": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    },
    "//:extension.bzl%lockfile_ext2": {
      "os:macos": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      },
      "os:linux": {
        "bzlTransitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    }
  }
}

登錄檔案雜湊

registryFileHashes 部分包含模組解析期間存取的遠端登錄資料庫中所有檔案的雜湊值。由於解析演算法在提供相同輸入內容並對所有遠端輸入內容進行雜湊時,可完全確定結果,因此可確保完全可重現的解析結果,同時避免在鎖定檔案中重複使用過多遠端資訊。請注意,如果特定註冊資料庫不含特定模組,但優先順序較低的註冊資料庫確實有該模組,您還需要錄製 (請參閱範例的「找不到」項目)。這項本質可變動的資訊可透過 bazel mod deps --lockfile_mode=refresh 更新。

Bazel 會使用鎖定檔案中的雜湊值,在下載之前查詢存放區快取中的登錄檔案,藉此加快後續的解析作業。

已選取的已撤銷版本

selectedYankedVersions 區段包含模組解析所選取的模組版本。由於這通常會導致建構時發生錯誤,因此只有在透過 --allow_yanked_versionsBZLMOD_ALLOW_YANKED_VERSIONS 明確允許撤銷版本時,這個部分才會非空白。

與模組檔案相比,客層檔案本身可變動的版本資訊,因此需要使用這個欄位,因此無法由雜湊參照。您可以透過 bazel mod deps --lockfile_mode=refresh 更新這項資訊。

模組擴充功能

moduleExtensions 區段是一個地圖,只包含目前叫用或先前叫用所用的擴充功能,並排除不再使用的擴充功能。換句話說,如果擴充功能不再用於依附元件圖表,就會從 moduleExtensions 對應圖中移除。

如果擴充功能不受作業系統或架構類型影響,這個部分只會顯示單一「一般」項目。否則,系統會納入多個項目,並以作業系統、架構或兩者命名,每個項目都會對應到針對這些特定項目評估擴充功能的結果。

擴充功能對應表中的每個項目都對應至已使用的擴充功能,並以包含的檔案和名稱識別。每個項目的對應值都包含與該擴充功能相關聯的相關資訊:

  1. bzlTransitiveDigest 是擴充功能實作項目的摘要,以及由擴充功能間接載入的 .bzl 檔案。
  2. usagesDigest 是依附元件圖表中擴充功能「用法」的摘要,其中包含所有標記。
  3. 未指定的其他欄位,會追蹤擴充功能的其他輸入內容,例如它讀取的檔案或目錄,或使用了的環境變數。
  4. generatedRepoSpecs 會使用目前的輸入內容將擴充功能建立的存放區進行編碼。
  5. 選用的 moduleExtensionMetadata 欄位包含擴充功能提供的中繼資料,例如根模組是否應透過 use_repo 匯入所建立的特定存放區。這項資訊可供 bazel mod tidy 指令使用。

模組擴充功能可以使用 reproducible = True 設定傳回中繼資料,選擇不納入鎖定檔案。如此一來,就能保證在輸入相同資料的情況下,一律都會建立相同的存放區。

最佳做法

如要充分發揮鎖定檔案功能的優點,請考慮採用下列最佳做法:

  • 請定期更新鎖定檔案,反映專案依附元件或設定的變更。這可確保後續建構是以最新且準確的依附元件組合為基礎。如要一次鎖定所有擴充功能,請執行 bazel mod deps --lockfile_mode=update

  • 將 Lockfile 納入版本管控,以利協同合作,並確保所有團隊成員都能存取相同的 Lockfile,促進專案中一致的開發環境。

  • 請使用 bazelisk 執行 Bazel,並在版本管控中加入 .bazelversion 檔案,指定與鎖定檔案對應的 Bazel 版本。由於 Bazel 本身是建構的依附元件,因此鎖定檔案僅適用於 Bazel 版本,而且即使在回溯相容 Bazel 版本之間變更亦然。使用 bazelisk 可確保所有開發人員都使用與鎖定檔案相符的 Bazel 版本。

只要按照這些最佳做法,您就可以有效利用 Bazel 中的 Lockfile 功能,創造更有效率、更穩定、協同運作的軟體開發工作流程。

合併衝突

鎖定檔案格式旨在盡量減少合併衝突,但仍可能發生。

自動解析度

Bazel 會提供自訂 git 合併驅動程式,協助自動解決這些衝突。

將這行程式碼新增至 Git 存放區根目錄中的 .gitattributes 檔案,即可設定驅動程式:

# A custom merge driver for the Bazel lockfile.
# https://bazel.build/external/lockfile#automatic-resolution
MODULE.bazel.lock merge=bazel-lockfile-merge

接著,每位想要使用驅動程式的開發人員都必須按照下列步驟註冊一次:

  1. 安裝 jq (1.5 以上版本)。
  2. 執行下列指令:
jq_script=$(curl https://raw.githubusercontent.com/bazelbuild/bazel/master/scripts/bazel-lockfile-merge.jq)
printf '%s\n' "${jq_script}" | less # to optionally inspect the jq script
git config --global merge.bazel-lockfile-merge.name   "Merge driver for the Bazel lockfile (MODULE.bazel.lock)"
git config --global merge.bazel-lockfile-merge.driver "jq -s '${jq_script}' -- %O %A %B > %A.jq_tmp && mv %A.jq_tmp %A"

手動解決

只要保留衝突兩邊的所有項目,即可安全解決 registryFileHashesselectedYankedVersions 欄位中的簡單合併衝突。

其他類型的合併衝突則不應手動解決。請改採以下做法:

  1. 透過 git reset MODULE.bazel.lock && git checkout MODULE.bazel.lock 還原鎖定檔案先前的狀態。
  2. 解決 MODULE.bazel 檔案中的任何衝突。
  3. 執行 bazel mod deps 以更新鎖定檔案。