Bazel 模組

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

Bazel 模組是可擁有多個版本的 Bazel 專案,每個版本都會發布關於所依附其他模組的中繼資料。這與其他依附元件管理系統中的常見概念相似,例如 Maven 構件、npm 套件、Go 模組或 Cargo crate

模組的存放區根目錄中必須有 MODULE.bazel 檔案。此檔案是模組的資訊清單,會宣告模組的名稱、版本、直接依附元件清單和其他資訊。基本範例:

module(name = "my-module", version = "1.0")

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

請參閱 MODULE.bazel 檔案中可用的指令完整清單

為了執行模組解析,Bazel 會先讀取根模組的 MODULE.bazel 檔案,然後從 Bazel 登錄處重複要求任何依附元件的 MODULE.bazel 檔案,直到發現整個相依關係圖為止。

根據預設,Bazel 會選取每個模組的一個版本來使用。Bazel 會使用一個存放區代表每個模組,並再次參考註冊中心,瞭解如何定義每個存放區。

版本格式

Bazel 擁有多元的生態系統,專案會使用各種版本管理方案。目前為止最受歡迎的是 SemVer,但也有使用不同配置的知名專案,例如 Abseil,其中的版本都是以日期為基礎的版本,例如 20210324.2

因此,Bzlmod 採用了較為寬鬆的 SemVer 規格版本。差異包括:

  • SemVer 規定版本的「發布」部分必須包含 3 個部分:MAJOR.MINOR.PATCH。在 Bazel 中,這項規定已放寬,因此可允許任意數量的區段。
  • 在 SemVer 中,「release」部分的每個區段都必須只包含數字。在 Bazel 中,這項限制已放寬,因此也允許使用字母,且比較語意會與「預發布」部分的「ID」相符。
  • 此外,系統不會強制執行主要、次要和修補版本的語意。不過,請參閱「相容性等級」一文,進一步瞭解我們如何標示回溯相容性。

任何有效的 SemVer 版本都是有效的 Bazel 模組版本。此外,如果兩個 SemVer 版本 ab 以 Bazel 模組版本比較,則只有在兩者相同時,才能比較 a < b

選擇版本

請考慮菱形依附元件問題,這是版本依附元件管理空間的常見問題。假設您有依附元件圖:

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

應使用哪個版本的 D?為解決這個問題,Bzlmod 使用 Go 模組系統中導入的最小版本選取 (MVS) 演算法。MVS 會假設模組的所有新版本皆可向下相容,因此會挑選任何依附元件 (在本例中為 D 1.1) 指定的最高版本。之所以稱為「最小」版本,是因為 D 1.1 是可滿足我們需求的早期版本,即使 D 1.2 或更新版本也存在,我們也不會選取這些版本。使用 MVS 可建立高保真度可重現的版本選擇程序。

彎曲版本

如果應避免使用特定版本 (例如存在安全漏洞),登錄可以將其宣告為已撤銷。選取模組的撤銷版本時,Bazel 會擲回錯誤。如要修正這個錯誤,請升級至較新的非撤銷版本,或是使用 --allow_yanked_versions 標記明確允許撤銷版本。

相容性等級

在 Go 中,MVS 對向下相容性的假設是有效的,因為它會將模組的向下相容版本視為獨立模組。就 SemVer 而言,這表示 A 1.xA 2.x 被視為不同的模組,可以並存在於已解析的依附元件圖表中。反過來說,這項功能是透過在 Go 的套件路徑中編碼主要版本而實現,因此不會發生任何編譯或連結時間衝突。

不過,Bazel 無法提供這類保證,因此需要「主要版本」編號才能偵測回溯不相容的版本。這個數字稱為相容性層級,由各模組版本在其 module() 指令中指定。有了這項資訊,Bazel 就能在偵測到已解析依附元件圖形中存在不同相容性等級的同一個模組版本時,擲回錯誤。

覆寫

MODULE.bazel 檔案中指定覆寫值,即可變更 Bazel 模組解析的行為。只有根模組的覆寫設定會生效,如果模組做為依附元件使用,系統就會忽略其覆寫。

每個覆寫值都會指定特定模組名稱,並影響依附元件圖表中的所有版本。雖然只有根模組的覆寫會生效,但可能適用於根模組不會直接依附的遞移依附元件。

單一版本覆寫

single_version_override 可用於多種用途:

  • 使用 version 屬性,即可將依附元件固定在特定版本,無論依附元件圖中要求的依附元件版本為何。
  • 您可以使用 registry 屬性,強制讓這個依附元件來自特定登錄,而非依照一般登錄選取程序。
  • 您可以使用 patch* 屬性,指定要套用至下載模組的一系列修補程式。

這些屬性為選用屬性,可以混合搭配。

多版本覆寫

您可以指定 multiple_version_override,讓同一個模組的多個版本在已解析的依附元件圖表中共存。

您可以明確指定模組的允許版本清單,這些版本必須在解析前出現在依附元件圖表中,且必須存在某些傳遞依附元件,取決於每個允許版本。解決後,系統只會保留允許的模組版本,而 Bazel 會將模組的其他版本升級至相同相容性層級中最近的較高允許版本。如果沒有相同相容性等級的較高版本,Bazel 會擲回錯誤。

舉例來說,如果在解析前,依附元件圖表中存在 1.11.31.51.72.0 版本,且主要版本為相容性層級:

  • 允許 1.31.72.0 的多版本覆寫值會導致 1.1 升級至 1.31.5 升級至 1.7,其他版本則保持不變。
  • 允許 1.52.0 的多版本覆寫值會導致錯誤,因為 1.7 沒有可升級至相同相容性等級的較高版本。
  • 允許 1.92.0 的多版本覆寫會導致錯誤,因為在解析前,依附元件圖中並未出現 1.9

此外,使用者也可以使用 registry 屬性覆寫登錄,類似於單一版本覆寫。

非登錄覆寫值

非登錄檔覆寫值會從版本解析中完全移除模組。Bazel 不會從註冊表要求這些 MODULE.bazel 檔案,而是從儲存庫本身要求。

Bazel 支援下列非註冊覆寫值:

定義不代表 Bazel 模組的 repos

您可以使用 bazel_dep 定義代表其他 Bazel 模組的存放區。有時需要定義「不」代表 Bazel 模組的存放區,例如包含純 JSON 檔案的存放區,以便讀取資料。

在這種情況下,您可以使用 use_repo_rule 指示詞,透過叫用 repo 規則直接定義 repo。這個存放區只會顯示其定義的模組。

實際上,這項功能是使用與模組擴充功能相同的機制實作,可讓您以更靈活的方式定義存放區。

存放區名稱和嚴格依附元件

為模組提供直接依附元件的 repo 的顯示名稱預設為模組名稱,除非 bazel_dep 指令的 repo_name 屬性另有規定。請注意,這表示模組只能找出其直接依附元件。這有助於避免因傳遞式依附元件變更而導致意外中斷。

支援模組的存放區標準名稱module_name+version (例如 bazel_skylib+1.0.3) 或 module_name+ (例如 bazel_features+),取決於整個依附元件圖表中是否有模組的多個版本 (請參閱 multiple_version_override)。請注意,標準名稱格式並非您應依附的 API,且隨時可能變更。請勿硬式編碼標準名稱,而是使用支援的方式直接從 Bazel 取得:

  • 在 BUILD 和 .bzl 檔案中,在 Label 執行個體中使用 Label.repo_name 對由存放區的表稱名稱提供的標籤字串所建構而成,例如Label("@bazel_skylib").repo_name
  • 查詢執行檔時,請使用 $(rlocationpath ...)@bazel_tools//tools/{bash,cpp,java}/runfiles 中的其中一個執行檔程式庫,或是 @rules_foo//foo/runfiles 中的規則集 rules_foo
  • 透過 IDE 或語言伺服器等外部工具與 Bazel 互動時,請使用 bazel mod dump_repo_mapping 指令,針對特定的存放區組合取得從顯式名稱到標準名稱的對應項目。

模組擴充功能也可以將其他存放區引入模組的可見範圍。