Bzlmod 遷移指南

7.3 · 7.2 · 7.1 · 7.0 · 6.5

由於 WORKSPACE 的缺點,Bzlmod 會在未來的 Bazel 版本中取代舊版 WORKSPACE 系統。本指南可協助您將專案遷移至 Bzlmod,並捨棄 WORKSPACE 以擷取外部依附元件。

WORKSPACE 與 Bzlmod

Bazel 的 WORKSPACE 和 Bzlmod 提供類似的功能,但語法不同。本節說明如何從特定 WORKSPACE 功能遷移至 Bzlmod。

定義 Bazel 工作區的根目錄

WORKSPACE 檔案會標記 Bazel 專案的來源根目錄。在 Bazel 6.3 以上版本中,這項責任會由 MODULE.bazel 取代。在 6.3 之前的 Bazel 版本中,工作區根目錄應仍會有 WORKSPACEWORKSPACE.bazel 檔案,可能會附帶以下註解:

  • WORKSPACE

    # This file marks the root of the Bazel workspace.
    # See MODULE.bazel for external dependencies setup.
    

在 bazelrc 中啟用 Bzlmod

.bazelrc 可讓您設定每次執行 Bazel 時都會套用的標記。如要啟用 Bzlmod,請使用 --enable_bzlmod 標記並將其套用至 common 指令,以便套用至每個指令:

  • .bazelrc

    # Enable Bzlmod for every Bazel command
    common --enable_bzlmod
    

指定工作區的存放區名稱

  • 工作區

    workspace 函式可用來指定工作區的存放區名稱。這樣就能將工作區中的目標 //foo:bar 當做 @<workspace name>//foo:bar 參照。如果未指定,工作區的預設存放區名稱為 __main__

    ## WORKSPACE
    workspace(name = "com_foo_bar")
    
  • Bzlmod

    建議您使用 //foo:bar 語法 (不含 @<repo name>) 參照同一個工作區中的目標。不過,如果您需要舊語法,可以使用 module 函式指定的模組名稱做為存放區名稱。如果模組名稱與所需存放區名稱不同,您可以使用 module 函式的 repo_name 屬性覆寫存放區名稱。

    ## MODULE.bazel
    module(
        name = "bar",
        repo_name = "com_foo_bar",
    )
    

擷取外部依附元件做為 Bazel 模組

如果依附元件是 Bazel 專案,您應該可以在採用 Bzlmod 時,將其視為 Bazel 模組來依附。

  • WORKSPACE

    使用 WORKSPACE 時,通常會使用 http_archivegit_repository 存放區規則來下載 Bazel 專案的來源。

    ## WORKSPACE
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    
    http_archive(
        name = "bazel_skylib",
        urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz"],
        sha256 = "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa",
    )
    load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
    bazel_skylib_workspace()
    
    http_archive(
        name = "rules_java",
        urls = ["https://github.com/bazelbuild/rules_java/releases/download/6.1.1/rules_java-6.1.1.tar.gz"],
        sha256 = "76402a50ae6859d50bd7aed8c1b8ef09dae5c1035bb3ca7d276f7f3ce659818a",
    )
    load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains")
    rules_java_dependencies()
    rules_java_toolchains()
    

    如您所見,使用者需要從依附元件巨集載入遞移依附元件。假設 bazel_skylibrules_java 都依附 platformplatform 依附元件的確切版本會由巨集的順序決定。

  • Bzlmod

    使用 Bzlmod 時,只要您的依附元件可在 Bazel 中央註冊中心或自訂的 Bazel 註冊中心中使用,您就可以透過 bazel_dep 指令依附該元件。

    ## MODULE.bazel
    bazel_dep(name = "bazel_skylib", version = "1.4.2")
    bazel_dep(name = "rules_java", version = "6.1.1")
    

    Bzlmod 會使用 MVS 演算法,以轉接方式解析 Bazel 模組依附元件。因此,系統會自動選取所需的 platform 最高版本。

將依附元件覆寫為 Bazel 模組

您可以透過不同的方式,在根模組中覆寫 Bazel 模組依附元件。

詳情請參閱「覆寫值」一節。

您可以在範例存放區中找到一些使用範例。

使用模組擴充功能擷取外部依附元件

如果您的依附元件並非 Bazel 專案,或是尚未在任何 Bazel 註冊資料庫中使用,您可以使用模組擴充功能導入。

  • WORKSPACE

    使用 http_file 存放區規則下載檔案。

    ## WORKSPACE
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
    
    http_file(
        name = "data_file",
        url = "http://example.com/file",
        sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    )
    
  • Bzlmod

    使用 Bzlmod 時,您必須將定義移至 .bzl 檔案,這樣您就能在遷移期間在 WORKSPACE 和 Bzlmod 之間共用定義。

    ## repositories.bzl
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
    def my_data_dependency():
        http_file(
            name = "data_file",
            url = "http://example.com/file",
            sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        )
    

    實作模組擴充功能,載入依附元件巨集。您可以在巨集的相同 .bzl 檔案中定義,但為了與舊版 Bazel 保持相容性,建議您在個別的 .bzl 檔案中定義。

    ## extensions.bzl
    load("//:repositories.bzl", "my_data_dependency")
    def _non_module_dependencies_impl(_ctx):
        my_data_dependency()
    
    non_module_dependencies = module_extension(
        implementation = _non_module_dependencies_impl,
    )
    

    如要讓根目錄專案能看到存放區,您必須在 MODULE.bazel 檔案中宣告模組擴充功能和存放區的用途。

    ## MODULE.bazel
    non_module_dependencies = use_extension("//:extensions.bzl", "non_module_dependencies")
    use_repo(non_module_dependencies, "data_file")
    

使用模組擴充功能解決外部依附元件的衝突

專案可提供巨集,根據呼叫端的輸入內容推薦外部存放區。但如果依附元件圖表中有多個呼叫端,且這些呼叫端會造成衝突,該怎麼辦呢?

假設專案 foo 提供下列巨集,並將 version 做為引數。

## repositories.bzl in foo {:#repositories.bzl-foo}
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
def data_deps(version = "1.0"):
    http_file(
        name = "data_file",
        url = "http://example.com/file-%s" % version,
        # Omitting the "sha256" attribute for simplicity
    )
  • 工作區

    透過 WORKSPACE,您可以從 @foo 載入巨集,並指定所需的資料依附元件版本。假設您還有另一個依附元件 @bar,這個依附元件也依附於 @foo,但需要不同版本的資料依附元件。

    ## WORKSPACE
    
    # Introduce @foo and @bar.
    ...
    
    load("@foo//:repositories.bzl", "data_deps")
    data_deps(version = "2.0")
    
    load("@bar//:repositories.bzl", "bar_deps")
    bar_deps() # -> which calls data_deps(version = "3.0")
    

    在這種情況下,使用者必須仔細調整 WORKSPACE 中的巨集順序,才能取得所需的版本。這是 WORKSPACE 最棘手的問題之一,因為它並未提供合理的方式來解決依附元件。

  • Bzlmod

    有了 Bzlmod,foo 專案的作者就能使用模組擴充功能來解決衝突問題。舉例來說,假設一律在所有 Bazel 模組中選取資料依附元件的最低版本。

    ## extensions.bzl in foo
    load("//:repositories.bzl", "data_deps")
    
    data = tag_class(attrs={"version": attr.string()})
    
    def _data_deps_extension_impl(module_ctx):
        # Select the maximal required version in the dependency graph.
        version = "1.0"
        for mod in module_ctx.modules:
            for data in mod.tags.data:
                version = max(version, data.version)
        data_deps(version)
    
    data_deps_extension = module_extension(
        implementation = _data_deps_extension_impl,
        tag_classes = {"data": data},
    )
    
    ## MODULE.bazel in bar
    bazel_dep(name = "foo", version = "1.0")
    
    foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension")
    foo_data_deps.data(version = "3.0")
    use_repo(foo_data_deps, "data_file")
    
    ## MODULE.bazel in root module
    bazel_dep(name = "foo", version = "1.0")
    bazel_dep(name = "bar", version = "1.0")
    
    foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension")
    foo_data_deps.data(version = "2.0")
    use_repo(foo_data_deps, "data_file")
    

    在這種情況下,根模組需要資料版本 2.0,而其依附元件 bar 需要 3.0foo 中的模組擴充功能可正確解決這項衝突,並自動選取資料依附元件的 3.0 版本。

整合第三方套件管理工具

在上一節之後,由於模組擴充功能可讓您從依附元件圖表收集資訊,執行自訂邏輯來解析依附元件,並呼叫存放區規則來導入外部存放區,因此可讓規則作者強化特定語言套件管理員的規則集。

請參閱「模組擴充功能」頁面,進一步瞭解如何使用模組擴充功能。

以下是已採用 Bzlmod 從不同套件管理工具擷取依附元件的規則集清單:

您可在範例存放區中找到整合虛擬套件管理員的最簡單範例。

偵測主機電腦上的工具鏈

當 Bazel 建構規則需要偵測主機機器上可用的工具鍊時,就會使用存放區規則來檢查主機機器,並產生工具鍊資訊做為外部存放區。

  • 工作區

    請參考下列存放區規則,瞭解如何偵測殼層工具鏈。

    ## local_config_sh.bzl
    def _sh_config_rule_impl(repository_ctx):
        sh_path = get_sh_path_from_env("SH_BIN_PATH")
    
        if not sh_path:
            sh_path = detect_sh_from_path()
    
        if not sh_path:
            sh_path = "/shell/binary/not/found"
    
        repository_ctx.file("BUILD", """
    load("@bazel_tools//tools/sh:sh_toolchain.bzl", "sh_toolchain")
    sh_toolchain(
        name = "local_sh",
        path = "{sh_path}",
        visibility = ["//visibility:public"],
    )
    toolchain(
        name = "local_sh_toolchain",
        toolchain = ":local_sh",
        toolchain_type = "@bazel_tools//tools/sh:toolchain_type",
    )
    """.format(sh_path = sh_path))
    
    sh_config_rule = repository_rule(
        environ = ["SH_BIN_PATH"],
        local = True,
        implementation = _sh_config_rule_impl,
    )
    

    您可以在 WORKSPACE 中載入存放區規則。

    ## WORKSPACE
    load("//:local_config_sh.bzl", "sh_config_rule")
    sh_config_rule(name = "local_config_sh")
    
  • Bzlmod

    使用 Bzlmod 時,您可以使用模組擴充功能引入相同的存放區,這類似於在上一節中引入 @data_file 存放區。

    ## local_config_sh_extension.bzl
    load("//:local_config_sh.bzl", "sh_config_rule")
    
    sh_config_extension = module_extension(
        implementation = lambda ctx: sh_config_rule(name = "local_config_sh"),
    )
    

    然後在 MODULE.bazel 檔案中使用擴充功能。

    ## MODULE.bazel
    sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension")
    use_repo(sh_config_ext, "local_config_sh")
    

註冊工具鏈和執行平台

在上一節之後,介紹存放區託管工具鍊資訊 (例如 local_config_sh) 之後,您可能會想註冊工具鍊。

  • 工作區

    您可以透過以下方式,使用 WORKSPACE 註冊工具鏈。

    1. 您可以將工具鏈註冊至 .bzl 檔案,並在 WORKSPACE 檔案中載入巨集。

      ## local_config_sh.bzl
      def sh_configure():
          sh_config_rule(name = "local_config_sh")
          native.register_toolchains("@local_config_sh//:local_sh_toolchain")
      
      ## WORKSPACE
      load("//:local_config_sh.bzl", "sh_configure")
      sh_configure()
      
    2. 或者,您也可以直接在 WORKSPACE 檔案中註冊工具鏈。

      ## WORKSPACE
      load("//:local_config_sh.bzl", "sh_config_rule")
      sh_config_rule(name = "local_config_sh")
      register_toolchains("@local_config_sh//:local_sh_toolchain")
      
  • Bzlmod

    在 Bzlmod 中,register_toolchainsregister_execution_platforms API 僅會出現在 MODULE.bazel 檔案中。您無法在模組擴充功能中呼叫 native.register_toolchains

    ## MODULE.bazel
    sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension")
    use_repo(sh_config_ext, "local_config_sh")
    register_toolchains("@local_config_sh//:local_sh_toolchain")
    

WORKSPACEWORKSPACE.bzlmod 中註冊的工具鏈和執行平台,以及每個 Bazel 模組的 MODULE.bazel 檔案,在工具鏈選取時會依照以下優先順序 (由高至低):

  1. 已在根模組 MODULE.bazel 檔案中註冊的工具鍊和執行平台。
  2. WORKSPACEWORKSPACE.bzlmod 檔案中註冊的工具鏈和執行平台。
  3. 模組註冊的工具鏈和執行平台,這些模組是根模組的 (間接) 依附元件。
  4. 不使用 WORKSPACE.bzlmod 時:在 WORKSPACE 後置字元中註冊的工具鏈。

導入本機存放區

如果需要以本機版本進行偵錯,或想在工作區中加入目錄做為外部存放區,您可能需要將依附元件做為本機存放區導入。

  • 工作區

    在 WORKSPACE 中,這項功能是透過兩個原生存放區規則 local_repositorynew_local_repository 實現。

    ## WORKSPACE
    local_repository(
        name = "rules_java",
        path = "/Users/bazel_user/workspace/rules_java",
    )
    
  • Bzlmod

    透過 Bzlmod,您可以使用 local_path_override 以本機路徑覆寫模組。

    ## MODULE.bazel
    bazel_dep(name = "rules_java")
    local_path_override(
        module_name = "rules_java",
        path = "/Users/bazel_user/workspace/rules_java",
    )
    

    您也可以透過模組擴充功能引入本機存放區。不過,您無法在模組擴充功能中呼叫 native.local_repository,我們目前正致力於將所有原生存放區規則轉換為 Starlark (請查看 #18285 瞭解進度)。接著,您可以在模組擴充功能中呼叫相對應的 Starlark local_repository。如果這會造成阻礙,則實作 local_repository 存放區規則的自訂版本也很容易。

繫結目標

WORKSPACE 中的 bind 規則已淘汰,且 Bzlmod 不支援該規則。這項功能的推出目的,是為了在特殊 //external 套件中為目標指定別名。系統會遷移所有相關使用者。

舉例來說

## WORKSPACE
bind(
    name = "openssl",
    actual = "@my-ssl//src:openssl-lib",
)

這樣一來,其他目標就能依附 //external:openssl。您可以透過下列方式遷移:

  • 將所有 //external:openssl 用法替換為 @my-ssl//src:openssl-lib

  • 或使用 alias 建構規則

    • 在套件中定義下列目標 (例如 //third_party)

      ## third_party/BUILD
      alias(
          name = "openssl,
          actual = "@my-ssl//src:openssl-lib",
      )
      
    • 將所有的 //external:openssl 使用方式替換為 //third_party:openssl-lib

遷移

本節提供 Bzlmod 遷移程序的實用資訊和指南。

瞭解 WORKSPACE 中的依附元件

遷移的第一步是瞭解您有哪些依附元件。由於遞移依附元件通常會透過 *_deps 巨集載入,因此很難找出 WORKSPACE 檔案中導入的確切依附元件。

使用工作區解析的檔案檢查外部依附元件

幸好,標記 --experimental_repository_resolved_file 可以提供協助。這個標記基本上會產生「鎖定檔案」,其中包含您在上一個 Bazel 指令中擷取的所有外部依附元件。詳情請參閱這篇網誌文章

使用方法有兩種:

  1. 擷取建構特定目標所需的外部依附元件資訊。

    bazel clean --expunge
    bazel build --nobuild --experimental_repository_resolved_file=resolved.bzl //foo:bar
    
  2. 擷取 WORKSPACE 檔案中定義的所有外部依附元件的資訊。

    bazel clean --expunge
    bazel sync --experimental_repository_resolved_file=resolved.bzl
    

    您可以使用 bazel sync 指令擷取 WORKSPACE 檔案中定義的所有依附元件,包括:

    • bind 個用量
    • register_toolchainsregister_execution_platforms 用量

    不過,如果專案是跨平台,Bazel 同步處理作業可能會在特定平台上中斷,因為某些存放區規則只能在支援的平台上正確執行。

執行指令後,resolved.bzl 檔案中應該就會出現外部依附元件的資訊。

使用 bazel query 檢查外部依附元件

您可能也會知道 bazel query 可用於檢查存放區規則

bazel query --output=build //external:<repo name>

雖然 bazel 查詢比外部依附元件版本來得方便且執行許多,但因此請謹慎使用!使用 Bzlmod 查詢及檢查外部依附元件,可透過新的子命令達成。

內建的預設依附元件

如果您檢查 --experimental_repository_resolved_file 產生的檔案,會發現許多依附元件並未在 WORKSPACE 中定義。這是因為 Bazel 會在使用者的 WORKSPACE 檔案內容中加入前置字串和後置字串,以便插入一些預設依附元件,這些依附元件通常是原生規則 (例如 @bazel_tools@platforms@remote_java_tools) 所需。在 Bzlmod 中,這些依附元件會透過內建模組 bazel_tools 引進,這是所有其他 Bazel 模組的預設依附元件。

漸進式遷移的混合模式

Bzlmod 和 WORKSPACE 可並行運作,因此可將依附元件從 WORKSPACE 檔案遷移至 Bzlmod 的過程逐步進行。

WORKSPACE.bzlmod

在遷移期間,Bazel 使用者可能需要在啟用及未啟用 Bzlmod 的情況下切換建構作業。實作 WORKSPACE.bzlmod 支援,讓程序更順暢。

WORKSPACE.bzlmod 的語法與 WORKSPACE 相同。啟用 Bzlmod 後,如果工作區根目錄也含有 WORKSPACE.bzlmod 檔案:

  • WORKSPACE.bzlmod 會生效,系統會忽略 WORKSPACE 的內容。
  • 不會在 WORKSPACE.bzlmod 檔案中新增前置或後置字串

使用 WORKSPACE.bzlmod 檔案可簡化遷移作業,因為:

  • 停用 Bzlmod 後,您會改為從原始 WORKSPACE 檔案擷取相依項目。
  • 啟用 Bzlmod 後,您可以使用 WORKSPACE.bzlmod 來更準確追蹤要遷移的依附元件。

存放區瀏覽權限

Bzlmod 能夠控制可在指定存放區中看見的其他存放區,詳情請參閱存放區名稱和嚴格依附元件

以下摘要說明不同類型存放區的存放區可見度,並同時考量 WORKSPACE。

從主要存放區 從 Bazel 模組存放區 來自模組擴充功能存放區 來自 WORKSPACE 存放區
主要存放區 顯示 如果根模組為直接依附元件 如果根模組是代管模組擴充功能的模組的直接依附元件 顯示
Bazel 模組 repos 直接依附元件 直接依附元件 代管模組擴充功能的模組直接依附元件 根模組的直接依附元件
模組擴充功能存放區 直接依附元件 直接依附元件 代管模組擴充功能的模組直接依附元件 + 由相同模組擴充功能產生的所有存放區 直接模組的依附元件
Workspace Repos 所有可見項目 隱藏 隱藏 所有可見項目
@bar@bar

遷移程序

典型的 Bzlmod 遷移程序如下:

  1. 瞭解 WORKSPACE 中的依附元件。
  2. 在專案根層級新增空白的 MODULE.bazel 檔案。
  3. 新增空白的 WORKSPACE.bzlmod 檔案,以便覆寫 WORKSPACE 檔案內容。
  4. 在啟用 Bzlmod 的情況下建構目標,並檢查缺少的存放區。
  5. 請在已解析的依附元件檔案中,檢查缺少存放區的定義。
  6. 透過模組擴充功能,將缺少的依附元件做為 Bazel 模組引入,或將其保留在 WORKSPACE.bzlmod 中,以便日後遷移。
  7. 返回步驟 4,重複執行,直到所有依附元件皆可使用為止。

遷移工具

您可以使用互動式 Bzlmod 遷移輔助指令碼,輕鬆開始遷移。

這個指令碼會執行下列作業:

  • 產生並剖析解析的 WORKSPACE 檔案。
  • 以使用者可理解的方式列印已解析檔案中的存放區資訊。
  • 執行 Bazel 建構指令、偵測已知的錯誤訊息,並建議遷移方式。
  • 檢查 BCR 中是否已提供依附元件。
  • 將依附元件新增至 MODULE.bazel 檔案。
  • 透過模組擴充功能新增依附元件。
  • 將依附元件新增至 WORKSPACE.bzlmod 檔案。

如要使用這項功能,請確認您已安裝最新的 Bazel 版本,然後執行下列指令:

git clone https://github.com/bazelbuild/bazel-central-registry.git
cd <your workspace root>
<BCR repo root>/tools/migrate_to_bzlmod.py -t <your build targets>

發布 Bazel 模組

如果您的 Bazel 專案是其他專案的依附元件,則可在 Bazel Central Registry 中發布專案。

若要在 BCR 中檢查專案,您需要專案的來源封存網址。建立來源封存檔時,請注意下列事項:

  • 確認封存檔指向特定版本。

    BCR 只能接受版本化的來源封存檔,因為 Bzlmod 需要在依附元件解析期間進行版本比較。

  • 確認封存網址穩定無誤。

    Bazel 會透過雜湊值驗證封存檔的內容,因此請確保下載檔案的總和檢查碼不會變更。如果網址來自 GitHub,請在發布頁面中建立並上傳發布檔案。GitHub 無法保證隨選產生的來源封存檔總和檢查碼。簡單來說,https://github.com/<org>/<repo>/releases/download/... 形式的網址會視為穩定網址,而 https://github.com/<org>/<repo>/archive/... 則不是。如需更多資訊,請參閱「GitHub 封存總和檢查碼服務中斷」。

  • 確認來源樹狀結構符合原始存放區的版面配置。

    如果您的存放區非常大,且您想透過移除不必要的來源來建立較小的發布封存檔,請確認移除的來源樹狀結構是原始來源樹狀結構的子集。如此一來,使用者就能更容易透過 archive_overridegit_override 將模組覆寫為非發布版本。

  • 在可測試您最常用的 API 的子目錄中加入測試模組。

    測試模組是 Bazel 專案,其中包含自己的 WORKSPACE 和 MODULE.bazel 檔案,位於來源封存檔的子目錄中,且取決於要發布的實際模組。其中應包含各種常見 API 的範例或整合測試。請參閱「測試模組」一文,瞭解如何設定測試模組。

準備好來源封存網址後,請按照 BCR 貢獻指南,使用 GitHub 提取要求將模組提交至 BCR。

我們強烈建議您為存放區設定發布至 BCR GitHub 應用程式,以便自動化將模組提交至 BCR 的程序。

最佳做法

本節說明幾項您應遵循的最佳做法,以更好地管理外部依附元件。

將目標分割成不同套件,避免擷取不必要的依附元件。

請檢查 #12835,其中會強制擷取測試的開發依附元件,用於建構不需要這些依附元件的目標。這並非 Bzlmod 專屬的做法,但遵循這些做法可讓您更輕鬆地正確指定開發依附元件。

指定開發依附元件

您可以將 dev_dependency 屬性設為 bazel_depuse_extension 指令的 true,這樣就不會將指令傳播至相依專案。您可以使用根模組的 --ignore_dev_dependency 標記,確認目標是否仍可在沒有開發依附元件的情況下建構。

社群遷移進度

您可以查看 Bazel 中央註冊中心,瞭解是否已提供所需的依附元件。如有其他問題,歡迎加入這個 GitHub 討論串,為阻礙遷移作業的依附元件按讚,或將這些依附元件貼出。

回報問題

如要查看已知的 Bzlmod 問題,請參閱 Bazel GitHub 問題清單。歡迎您提出新問題或功能要求,協助我們解決遷移問題!