本頁面提供 Bazel 外部依附元件的常見問題解答。
MODULE.bazel
如何為 Bazel 模組設定版本?
如果未妥善管理,在來源封存檔中透過 module
指令設定 version
MODULE.bazel
可能會造成多種負面影響和非預期的副作用:
重複作業:發布新版模組通常需要遞增
MODULE.bazel
中的版本,並標記發布內容,這兩個步驟是分開進行,因此可能會失去同步。雖然自動化可降低這類風險,但最簡單安全的方法是完全避免。不一致:使用者透過非登錄檔覆寫,以特定提交內容覆寫模組時,會看到錯誤版本。舉例來說,如果來源封存檔中的
MODULE.bazel
設定version = "0.3.0"
,但該版本發布後又進行了其他提交,使用者以其中一個提交覆寫時,仍會看到0.3.0
。實際上,版本應反映出該版本領先於發布版本,例如0.3.1-rc1
。非登錄覆寫問題:如果使用者以非登錄覆寫覆寫模組,使用預留位置值可能會導致問題。舉例來說,
0.0.0
不會排序為最高版本,這通常是使用者在執行非登錄覆寫時預期的行為。
因此,建議您避免在來源封存檔 MODULE.bazel
中設定版本。請改為在登錄檔 (例如 Bazel Central Registry) 中儲存的 MODULE.bazel
內設定,這是 Bazel 外部依附元件解析期間的模組版本實際來源 (請參閱 Bazel 登錄檔)。
這通常是自動化作業,例如 rules-template
範例規則存放區會使用 bazel-contrib/publish-to-bcr publish.yaml GitHub Action,將版本發布至 BCR。這項動作會為來源封存檔MODULE.bazel
產生修補程式,並提供發布版本。這個修補程式會儲存在登錄檔中,並在 Bazel 解決外部依附元件時擷取模組時套用。
這樣一來,登錄檔中發布的版本就會正確設為發布的版本,因此 bazel_dep
、single_version_override
和 multiple_version_override
會如預期運作,同時避免在執行非登錄檔覆寫時發生潛在問題,因為來源封存檔中的版本會是預設值 (''
),系統一律會正確處理 (畢竟這是預設版本值),且排序時會如預期運作 (空字串會視為最高版本)。
何時應增加相容性層級?
如果導入不具回溯相容性 (「重大」) 的變更,應在同一次提交中遞增 Bazel 模組的 compatibility_level
。
不過,如果 Bazel 偵測到已解析的依附元件圖形中,存在相同模組但相容性等級不同的版本,可能會擲回錯誤。舉例來說,如果兩個模組依附的第三個模組版本相容性不同,就可能發生這種情況。
因此,如果 compatibility_level
遞增過於頻繁,可能會造成干擾,建議不要這麼做。為避免這種情況,只有在重大變更影響大多數用途,且不容易遷移及/或規避時,才應遞增 compatibility_level
。
為什麼 MODULE.bazel 不支援 load
?
在依附元件解析期間,系統會從登錄檔擷取所有參照外部依附元件的 MODULE.bazel 檔案。在這個階段,系統尚未擷取依附元件的來源封存檔;因此,如果 MODULE.bazel 檔案 load
是另一個檔案,Bazel 就無法擷取該檔案,除非擷取整個來源封存檔。請注意,MODULE.bazel 檔案本身很特別,因為該檔案直接託管在登錄檔上。
一般而言,要求 MODULE.bazel 中 load
的使用者對以下幾個用途感興趣,而這些用途不需要 load
即可解決:
- 確保 MODULE.bazel 中列出的版本與儲存在其他位置 (例如 .bzl 檔案) 的建構中繼資料一致:如要達成這個目標,請在從 BUILD 檔案載入的 .bzl 檔案中使用
native.module_version
方法。 - 將非常大的 MODULE.bazel 檔案分割為可管理的區段,特別是針對單一存放區:根模組可以使用
include
指令,將 MODULE.bazel 檔案分割為多個區段。基於同樣的理由,我們不允許在 MODULE.bazel 檔案中使用load
,因此include
無法用於非根模組。 - 舊版 WORKSPACE 系統的使用者可能記得要宣告存放區,然後立即從該存放區執行複雜的邏輯。
load
這項功能已由模組擴充功能取代。
我可以為 bazel_dep
指定 SemVer 範圍嗎?
否。其他一些套件管理工具 (例如 npm 和 Cargo) 支援版本範圍 (隱含或明確),這通常需要限制條件求解器 (讓使用者更難預測輸出內容),且沒有鎖定檔就無法重現版本解析。
Bazel 則會像 Go 一樣使用最低版本選取,因此輸出內容容易預測,且保證可重現。這項取捨符合 Bazel 的設計目標。
此外,Bazel 模組版本是語意版本管理的超集,因此在嚴格的語意版本管理環境中合理的做法,不一定適用於 Bazel 模組版本。
我可以讓 bazel_dep
自動更新至最新版本嗎?
部分使用者偶爾會要求指定 bazel_dep(name = "foo",
version = "latest")
,以便自動取得依附元件的最新版本。這與有關 SemVer 範圍的問題類似,答案也是否定的。
建議您使用自動化功能處理這項作業。舉例來說,Renovate 支援 Bazel 模組。
有時,提出這個問題的使用者其實是想在本地開發期間快速疊代。您可以使用 local_path_override
達成此目的。
為什麼會有這麼多 use_repo
?
MODULE.bazel 檔案中的模組擴充功能用法有時會附帶大型 use_repo
指令。舉例來說,gazelle
的 go_deps
擴充功能一般用法可能如下所示:
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
"com_github_gogo_protobuf",
"com_github_golang_mock",
"com_github_golang_protobuf",
"org_golang_x_net",
... # potentially dozens of lines...
)
由於資訊可能已在參照的 go.mod
檔案中,因此冗長的 use_repo
指令可能顯得多餘。
Bazel 需要這個 use_repo
指令的原因是,它會延遲執行模組擴充功能。也就是說,只有在觀察模組擴充功能的結果時,系統才會執行該擴充功能。由於模組擴充功能的「輸出」是存放區定義,這表示只有在要求模組定義的存放區時,我們才會執行模組擴充功能 (例如,如果建構目標 @org_golang_x_net//:foo
,如上例所示)。不過,在執行模組擴充功能之前,我們不知道該模組擴充功能會定義哪些存放區。這時就可使用 use_repo
指令;使用者可以告訴 Bazel 擴充功能預期會產生哪些存放區,而 Bazel 只會在這些特定存放區使用時執行擴充功能。
為協助維護這項 use_repo
指令,模組擴充功能可以從實作函式傳回 extension_metadata
物件。使用者可以執行 bazel mod tidy
指令,更新這些模組擴充功能的 use_repo
指令。
Bzlmod 遷移
系統會先評估 MODULE.bazel 還是 WORKSPACE?
如果同時設定 --enable_bzlmod
和 --enable_workspace
,您可能會想知道系統會先查詢哪個系統。簡而言之,系統會先評估 MODULE.bazel (Bzlmod)。
長篇答案是,「哪個先評估」並非正確的問題;正確的問題是:在具有正式名稱 @@foo
的存放區環境中,顯而易見的存放區名稱 @bar
會解析為哪個名稱?或者,@@base
的存放區對應為何?
具有明顯存放區名稱的標籤 (單一開頭 @
) 可以根據解析的環境參照不同項目。看到標籤 @bar//:baz
時,如果想知道標籤實際指向的內容,首先必須找出內容存放區。舉例來說,如果標籤位於 @@foo
存放區的 BUILD 檔案中,內容存放區就是 @@foo
。
然後,根據內容存放區為何,您可以使用遷移指南中的「存放區可見度」表格,找出顯而易見的名稱實際解析為哪個存放區。
- 如果內容存放區是主要存放區 (
@@
):- 如果
bar
是根模組的 MODULE.bazel 檔案 (透過任何bazel_dep
、use_repo
、module
、use_repo_rule
) 導入的顯式存放區名稱,則@bar
會解析為該 MODULE.bazel 檔案聲明的事項。 - 否則,如果
bar
是在 WORKSPACE 中定義的存放區 (表示其標準名稱為@@bar
),則@bar
會解析為@@bar
。 - 否則,
@bar
會解析為類似@@[unknown repo 'bar' requested from @@]
的內容,最終導致錯誤。
- 如果
- 如果內容存放區是 Bzlmod 世界存放區 (也就是對應至非根 Bazel 模組,或是由模組擴充功能產生),則只會看到其他 Bzlmod 世界存放區,不會看到 WORKSPACE 世界存放區。
- 值得注意的是,這包括根模組中以
non_module_deps
類似模組擴充功能導入的任何存放區,或根模組中的use_repo_rule
例項。
- 值得注意的是,這包括根模組中以
- 如果內容存放區是在 WORKSPACE 中定義:
- 首先,請檢查內容存放區定義是否具有 magical
repo_mapping
屬性。如果是,請先完成對應 (因此,如果是以repo_mapping = {"@bar": "@baz"}
定義的存放區,我們會查看下方的@baz
)。 - 如果
bar
是根模組的 MODULE.bazel 檔案導入的明顯存放區名稱,則@bar
會解析為該 MODULE.bazel 檔案聲明的事項。(這與主要存放區案例中的項目 1 相同)。 - 否則,
@bar
會解析為@@bar
。這很可能指向 WORKSPACE 中定義的存放區bar
;如果未定義這類存放區,Bazel 會擲回錯誤。
- 首先,請檢查內容存放區定義是否具有 magical
如要查看簡短版本,請參閱:
- Bzlmod-world 存放區 (主要存放區除外) 只會看到 Bzlmod-world 存放區。
- WORKSPACE 世界的存放區 (包括主要存放區) 會先查看 Bzlmod 世界中根模組定義的內容,然後再回頭查看 WORKSPACE 世界的存放區。
請注意,Bazel 指令列中的標籤 (包括 Starlark 旗標、標籤型別的旗標值,以及建構/測試目標模式) 會視為以主要存放區做為內容存放區。
其他
如何準備及執行離線建構作業?
使用 bazel fetch
指令預先擷取存放區。您可以使用 --repo
標記 (例如 bazel fetch --repo @foo
) 僅擷取存放區 @foo
(在主要存放區的環境中解析,請參閱上述問題),或使用目標模式 (例如 bazel fetch @foo//:bar
) 擷取 @foo//:bar
的所有遞移依附元件 (這等同於 bazel build --nobuild @foo//:bar
)。
如要確保建構期間不會發生擷取作業,請使用 --nofetch
。更精確地說,這會導致任何嘗試執行非本機存放區規則的作業失敗。
如要擷取存放區並修改,以便在本機測試,請考慮使用 bazel vendor
指令。
如何使用 HTTP Proxy?
Bazel 會遵守其他程式 (例如 curl) 普遍接受的 http_proxy
和 HTTPS_PROXY
環境變數。
在雙重堆疊 IPv4/IPv6 設定中,如何讓 Bazel 偏好使用 IPv6?
在僅支援 IPv6 的機器上,Bazel 可以下載依附元件,不必進行任何變更。不過,在雙重堆疊 IPv4/IPv6 電腦上,Bazel 遵循與 Java 相同的慣例,如果啟用 IPv4,則會優先使用 IPv4。在某些情況下 (例如 IPv4 網路無法解析/連線至外部位址),這可能會導致 Network
unreachable
例外狀況和建構失敗。在這種情況下,您可以使用 java.net.preferIPv6Addresses=true
系統屬性,覆寫 Bazel 的行為,偏好使用 IPv6。詳細說明:
使用
--host_jvm_args=-Djava.net.preferIPv6Addresses=true
startup 選項,例如在.bazelrc
檔案中新增下列程式碼:startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true
執行需要連上網際網路的 Java 建構目標 (例如整合測試) 時,請使用
--jvmopt=-Djava.net.preferIPv6Addresses=true
tool flag。舉例來說,在.bazelrc
檔案中加入以下內容:build --jvmopt=-Djava.net.preferIPv6Addresses
如果您使用
rules_jvm_external
解決依附元件版本問題,請將-Djava.net.preferIPv6Addresses=true
新增至COURSIER_OPTS
環境變數,為 Coursier 提供 JVM 選項。
是否可透過遠端執行功能,從遠端執行存放區規則?
否,至少目前還沒有。使用遠端執行服務加快建構速度的使用者可能會發現,存放區規則仍在本機執行。舉例來說,http_archive
會先下載到本機 (如果適用,會使用任何本機下載快取),然後解壓縮,接著每個來源檔案都會上傳至遠端執行服務做為輸入檔案。您可能會想問,為什麼遠端執行服務不直接下載及解壓縮該封存檔,省下無用的往返行程。
部分原因是存放區規則 (和模組擴充功能) 類似於 Bazel 本身執行的「指令碼」。遠端執行器甚至不一定會安裝 Bazel。
另一個原因是,Bazel 通常需要下載及解壓縮封存檔中的 BUILD 檔案,才能執行載入和分析作業,而這些作業是在本機執行。
目前有初步構想,可將存放區規則重新想像為建構規則,藉此解決這個問題,這樣一來,這些規則自然就能從遠端執行,但反過來說,這也會引發新的架構疑慮 (例如,query
指令可能需要執行動作,導致設計複雜化)。
如要進一步瞭解這個主題,請參閱「A way to support repositories that need Bazel for being fetched」。