Bazel Lockfile

回報問題 查看來源

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

產生鎖定檔案

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

當影響其依附元件的專案發生變更時, Lockfile 會自動更新以反映新的狀態。如此可確保鎖定檔案能專注於目前建構所需的特定依附元件組合,準確呈現專案解析的依附元件。

鎖定檔案使用方式

這個鎖定檔案可由標記 --lockfile_mode 控管,以便在專案狀態與鎖定檔案不同時自訂 Bazel 的行為。可用的模式如下:

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

鎖定檔案的優點

Lockfile 提供多項優點,且可多種運用方式:

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

  • 快速漸進式解析度。而 Lockfile 可讓 Bazel 避免下載先前版本中使用的登錄檔案。這可大幅改善建構效率,特別是在解析度可能費時。

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

Lockfile 內容

Lockfile 包含判斷專案狀態是否變更的所有必要資訊。同時也包括在目前狀態下建構專案的結果。Lockfile 包含兩個主要部分:

  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 區段包含模組解析期間存取遠端註冊資料庫的所有檔案的雜湊。由於當輸入相同且所有遠端輸入經過雜湊處理時,解析度演算法會完全確定,因此能確保完全可以重現的解析結果,同時避免在 Lockfile 中過度重複遠端資訊。請注意,如果特定註冊資料庫不含特定模組,但優先順序較低的註冊資料庫確實有該模組,您還需要錄製 (請參閱範例的「找不到」項目)。您可以透過 bazel mod deps --lockfile_mode=refresh 更新原本可變動的資訊。

Bazel 會在下載之前使用鎖定檔案中的雜湊查詢存放區快取中的註冊資料庫檔案,進而加快後續的解析速度。

已選取的 Yanked 版本

selectedYankedVersions 區段包含模組解析所選取的模組分裂版本。由於這通常會在嘗試建構時產生錯誤,因此只有在透過 --allow_yanked_versionsBZLMOD_ALLOW_YANKED_VERSIONS 明確允許的變形版本時,這個區段才不會空白。

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

模組擴充功能

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

如果擴充功能與作業系統或架構類型無關,本節只會顯示單一「一般」項目。否則的話,會包含多個以 OS 和/或架構命名的項目,每個項目分別對應於這些具體上評估擴充結果。

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

  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 可確保所有開發人員都使用與 Lockfile 相符的 Bazel 版本。

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