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 模組版本。此外,只有在 a
和 b
這兩個 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 可建立高保真度且可重現的版本選擇程序。
彎曲版本
如果需要避免特定版本 (例如安全漏洞),註冊資料庫可以宣告這些版本為「yanked」。Bazel 會在選取模組的分支版本時擲回錯誤。如要修正這個錯誤,請升級至較新的非 Ayked 版本,或使用 --allow_yanked_versions
標記明確允許 yanked 版本。
相容性等級
在 Go 中,MVS 對回溯相容性的假設是有效的,因為它會將不相容的模組版本視為獨立模組。就 SemVer 而言,這表示 A 1.x
和 A 2.x
視為不同的模組,可在已解析的依附元件圖表中並存。反過來,在 Go 套件路徑中對主要版本進行編碼後,即可達成這個目標,因此不會發生編譯時間或連結時間衝突的情形。
不過,Bazel 無法提供這類保證,因此需要「主要版本」編號才能偵測回溯不相容的版本。該數字稱為「相容性等級」,由每個模組版本在其 module()
指令中指定。有了這項資訊,Bazel 就能在偵測到已解析依附元件圖形中存在不同相容性等級的同一個模組版本時,擲回錯誤。
覆寫
在 MODULE.bazel
檔案中指定覆寫值,以變更 Bazel 模組解析的行為。只有根模組的覆寫值會生效。如果模組用於依附元件,系統會忽略其覆寫值。
每個覆寫值都是由特定模組名稱指定,進而影響依附元件圖中的所有版本。雖然只有根模組的覆寫值會生效,但這些值可用於根模組未直接依附的間接依附元件。
單一版本覆寫
single_version_override
有多項用途:
- 使用
version
屬性時,無論依附元件圖表要求哪個依附元件版本,您都可以將依附元件釘選至特定版本。 - 您可以使用
registry
屬性,強制讓這個依附元件來自特定登錄,而非依照一般登錄選取程序。 - 您可以使用
patch*
屬性,指定要套用至下載模組的一系列修補程式。
這些屬性皆為選用,且可相互搭配使用。
多版本覆寫
您可以指定 multiple_version_override
,讓同一個模組的多個版本在已解析的依附元件圖表中共存。
您可以明確指定模組的允許版本清單,這些版本必須在解析前出現在依附元件圖表中,且必須存在某些傳遞依附元件,取決於每個允許版本。解決後,系統只會保留允許的模組版本,而 Bazel 會將模組的其他版本升級至相同相容性層級中最近的較高允許版本。如果該相容性等級沒有更高層級的允許版本,Bazel 會擲回錯誤。
舉例來說,如果在解析前,依附元件圖表中存在 1.1
、1.3
、1.5
、1.7
和 2.0
版本,且主要版本為相容性層級:
- 允許
1.3
、1.7
和2.0
的多重版本覆寫會導致1.1
升級至1.3
,1.5
升級為1.7
,其他版本則維持不變。 - 允許
1.5
和2.0
的多版本覆寫值會導致錯誤,因為1.7
沒有可升級至相同相容性等級的較高版本。 - 允許
1.9
和2.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
指令,針對特定的存放區集合取得從明顯名稱到標準名稱的對應項目。
模組擴充功能也可以將其他存放區引入模組的可見範圍。