Bazel 模組

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

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

模組的存放區根目錄中必須有 MODULE.bazel 檔案 (位於 WORKSPACE 檔案旁邊)。這個檔案是模組的資訊清單,會宣告模組的名稱、版本、直接依附元件清單和其他資訊。基本範例如下:

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 中,「發布」部分的每個區隔都只能是數字。 在 Bazel 中,這項限制會放寬,允許使用字母,且比較語意會與「prerelease」部分的「identifiers」相符。
  • 此外,系統不會強制執行主要、次要和修補程式版本遞增的語意。不過,如要瞭解如何標示回溯相容性,請參閱相容性等級

任何有效的 SemVer 版本都是有效的 Bazel 模組版本。此外,只有在以 Bazel 模組版本進行比較時,兩個 SemVer 版本 ab 才會比較 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 會擲回錯誤。如要修正這個錯誤,請升級至較新的非 yanked 版本,或使用 --allow_yanked_versions 旗標明確允許 yanked 版本。

相容性等級

在 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 會擲回錯誤。

舉例來說,如果解析前依附元件圖中存在 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 模組的存放區

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

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

在幕後,這項功能是透過與模組擴充功能相同的機制實作,可讓您更彈性地定義存放區。

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

除非 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。 * 查詢 Runfile 時,請使用 $(rlocationpath ...)@bazel_tools//tools/{bash,cpp,java}/runfiles 中的其中一個 Runfile 程式庫,或規則集 rules_foo 中的 @rules_foo//foo/runfiles。* 從 IDE 或語言伺服器等外部工具與 Bazel 互動時,請使用 bazel mod dump_repo_mapping 指令,取得特定存放區組合的顯式名稱到標準名稱的對應。

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