Bazel Lockfile

回報問題 查看原始碼 Nightly · 8.0 . 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

產生鎖定檔案

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

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

鎖定檔案用途

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

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

鎖定檔案的優點

鎖定檔案有許多優點,且可用於各種用途:

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

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

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

鎖定檔案內容

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

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

以下範例說明瞭鎖定檔案的結構,以及各個部分的說明:

{
  "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 部分包含模組解析期間存取的遠端登錄資料庫中所有檔案的雜湊值。由於解析演算法在提供相同輸入內容時會完全決定性,且所有遠端輸入內容都會經過雜湊處理,因此可確保完全可重現的解析結果,同時避免在鎖定檔案中重複使用過多遠端資訊。請注意,當特定登錄不含特定模組,但優先順序較低的登錄含有該模組時,也需要記錄這項資訊 (請參閱範例中的「not found」項目)。這項資訊本身可變更,可透過 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

  • 在版本控制中加入鎖定檔案,以利協同合作,並確保所有團隊成員都能存取相同的鎖定檔案,促進專案中一致的開發環境。

  • 請使用 bazelisk 執行 Bazel,並在版本控制中加入 .bazelversion 檔案,指定與鎖定檔案相對應的 Bazel 版本。由於 Bazel 本身是建構作業的依附元件,因此鎖定檔案會依 Bazel 版本而異,即使在向下相容的 Bazel 版本之間,也會有所變動。使用 bazelisk 可確保所有開發人員都使用與鎖定檔案相符的 Bazel 版本。

只要遵循這些最佳做法,您就能有效運用 Bazel 的鎖定檔案功能,打造更有效率、可靠且可協作的軟體開發工作流程。

合併衝突

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

自動解析

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 更新鎖定檔案。