Bzlmod 遷移指南

回報問題 查看來源

由於 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 檔案,例如以下註解:

  • 工作區

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

指定工作區的存放區名稱

  • 工作區

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

    ## WORKSPACE
    workspace(name = "com_foo_bar")
    
  • 布茲爾莫

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

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

以 Bazel 模組擷取外部依附元件

如果您的依附元件是 Bazel 專案,而當依附元件也採用 Bzlmod 時,您應該能夠以 Bazel 模組做為依附元件。

  • 工作區

    使用 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 都依附於 platoformplatform 依附元件的確切版本取決於巨集順序。

  • 布茲爾莫

    使用 Bzlmod 時,只要您的依附元件儲存在 Bazel Central Registry 或自訂 Bazel Registry 中,您就可以直接使用 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 模組依附元件。

詳情請參閱「overrides」一節。

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

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

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

  • 工作區

    請使用 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 時,您必須將定義移至 .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,專案 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,您可以使用模組擴充功能來導入相同的存放區,這類似上一節介紹 @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 時,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")
    

導入本機存放區

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

  • 工作區

    使用 WORKSPACE 時,這項作業是由兩個原生存放區規則 local_repositorynew_local_repository 來完成。

    ## WORKSPACE
    local_repository(
        name = "rules_java",
        path = "/Users/bazel_user/workspace/rules_java",
    )
    
  • 布茲爾莫

    透過 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,系統會持續為所有原生存放區規則加上星號 (請查看 #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 模組存放區 直接依附元件 直接依附元件 代管模組擴充功能的模組直接依附元件 直接模組的依附元件
模組擴充功能存放區 直接依附元件 直接依附元件 代管模組擴充功能的模組直接依附元件 + 由相同模組擴充功能產生的所有存放區 直接模組的依附元件
Workspace 重寫 所有可見項目 不會顯示 不會顯示 所有可見項目
@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 特,但遵循這項做法可讓您更輕鬆正確指定開發依附元件。

指定開發依附元件

您可以將 bazel_depuse_extension 指令的 dev_dependency 屬性設為 true,這樣這兩個指令就不會套用至相依專案。根模組可以使用 --ignore_dev_dependency 標記,驗證目標是否仍在建構,且沒有開發依附元件。

社群遷移進度

您可以檢查 Bazel Central Registry,確認是否有可用的依附元件。否則,歡迎加入這個 GitHub 討論,投票支持或發布妨礙遷移作業的依附元件。

回報問題

請參閱 Bazel GitHub 問題清單,瞭解已知的 Bzlmod 問題。歡迎您隨時提出新問題或提出功能要求,以便解除封鎖遷移作業!