常見問題

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

本頁面提供 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_depsingle_version_overridemultiple_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 範圍嗎?

否。其他一些套件管理工具 (例如 npmCargo) 支援版本範圍 (隱含或明確),這通常需要限制條件求解器 (讓使用者更難預測輸出內容),且沒有鎖定檔就無法重現版本解析。

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 指令。舉例來說,gazellego_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

然後,根據內容存放區為何,您可以使用遷移指南中的「存放區可見度」表格,找出顯而易見的名稱實際解析為哪個存放區。

  • 如果內容存放區是主要存放區 (@@):
    1. 如果 bar 是根模組的 MODULE.bazel 檔案 (透過任何 bazel_depuse_repomoduleuse_repo_rule) 導入的顯式存放區名稱,則 @bar 會解析為該 MODULE.bazel 檔案聲明的事項。
    2. 否則,如果 bar 是在 WORKSPACE 中定義的存放區 (表示其標準名稱為 @@bar),則 @bar 會解析為 @@bar
    3. 否則,@bar 會解析為類似 @@[unknown repo 'bar' requested from @@] 的內容,最終導致錯誤。
  • 如果內容存放區是 Bzlmod 世界存放區 (也就是對應至非根 Bazel 模組,或是由模組擴充功能產生),則只會看到其他 Bzlmod 世界存放區,不會看到 WORKSPACE 世界存放區。
    • 值得注意的是,這包括根模組中以 non_module_deps 類似模組擴充功能導入的任何存放區,或根模組中的 use_repo_rule 例項。
  • 如果內容存放區是在 WORKSPACE 中定義:
    1. 首先,請檢查內容存放區定義是否具有 magical repo_mapping 屬性。如果是,請先完成對應 (因此,如果是以 repo_mapping = {"@bar": "@baz"} 定義的存放區,我們會查看下方的 @baz)。
    2. 如果 bar 是根模組的 MODULE.bazel 檔案導入的明顯存放區名稱,則 @bar 會解析為該 MODULE.bazel 檔案聲明的事項。(這與主要存放區案例中的項目 1 相同)。
    3. 否則,@bar 會解析為 @@bar。這很可能指向 WORKSPACE 中定義的存放區 bar;如果未定義這類存放區,Bazel 會擲回錯誤。

如要查看簡短版本,請參閱:

  • 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_proxyHTTPS_PROXY 環境變數。

在雙重堆疊 IPv4/IPv6 設定中,如何讓 Bazel 偏好使用 IPv6?

在僅支援 IPv6 的機器上,Bazel 可以下載依附元件,不必進行任何變更。不過,在雙重堆疊 IPv4/IPv6 電腦上,Bazel 遵循與 Java 相同的慣例,如果啟用 IPv4,則會優先使用 IPv4。在某些情況下 (例如 IPv4 網路無法解析/連線至外部位址),這可能會導致 Network unreachable 例外狀況和建構失敗。在這種情況下,您可以使用 java.net.preferIPv6Addresses=true 系統屬性,覆寫 Bazel 的行為,偏好使用 IPv6。詳細說明:

是否可透過遠端執行功能,從遠端執行存放區規則?

否,至少目前還沒有。使用遠端執行服務加快建構速度的使用者可能會發現,存放區規則仍在本機執行。舉例來說,http_archive 會先下載到本機 (如果適用,會使用任何本機下載快取),然後解壓縮,接著每個來源檔案都會上傳至遠端執行服務做為輸入檔案。您可能會想問,為什麼遠端執行服務不直接下載及解壓縮該封存檔,省下無用的往返行程。

部分原因是存放區規則 (和模組擴充功能) 類似於 Bazel 本身執行的「指令碼」。遠端執行器甚至不一定會安裝 Bazel。

另一個原因是,Bazel 通常需要下載及解壓縮封存檔中的 BUILD 檔案,才能執行載入和分析作業,而這些作業在本機執行。

目前有初步構想,可將存放區規則重新想像為建構規則,藉此解決這個問題,這樣一來,這些規則自然就能從遠端執行,但反過來說,這也會引發新的架構疑慮 (例如,query 指令可能需要執行動作,導致設計複雜化)。

如要進一步瞭解這個主題,請參閱「A way to support repositories that need Bazel for being fetched」。