Bazel Lockfile

回報問題 查看來源 Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

產生鎖定檔

鎖定檔會在工作區根目錄下產生,名稱為 MODULE.bazel.lock。這個檔案會在建構程序中建立或更新,具體來說,是在模組解析和擴充功能評估之後。重要事項:這項屬性只會納入目前建構呼叫中包含的依附元件。

如果專案發生變更,導致依附元件受到影響,鎖定檔會自動更新,以反映新狀態。這麼做可確保鎖定檔只會記錄目前建構作業所需的特定依附元件集,準確呈現專案已解決的依附元件。

Lockfile 用量

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

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

鎖定檔優點

鎖定檔有許多優點,而且用途廣泛:

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

  • 快速解決問題。鎖定檔可讓 Bazel 避免下載先前建構作業已使用的登錄檔。這項功能可大幅提升建構效率,尤其是在解析作業可能耗時的情況下。

  • 穩定性與風險降低。鎖定檔可防止外部程式庫發生意外更新或重大變更,有助於維持穩定性。將依附元件鎖定為特定版本,可降低因不相容或未經測試的更新而導入錯誤的風險。

隱藏鎖定檔

Bazel 也會在 "$(bazel info output_base)"/MODULE.bazel.lock 維護另一個鎖定檔案。這個鎖定檔的格式和內容未明確指定。這項功能只會用於最佳化效能。雖然可以透過 bazel clean --expunge 一併刪除輸出基礎,但如有此需求,表示 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 更新鎖定檔。