工具鏈

回報問題 查看原始碼 夜間 7.2 7.1 7.0 6.5 6.4

本頁將說明工具鍊架構,這是規則作者 將規則邏輯與平台式工具分離。是 建議您參閱規則平台 再繼續進行。此頁面說明需要工具鍊的原因,以及如何 以及 Bazel 如何根據指標選擇適當的工具鍊 平台限制

動機

首先來看看問題工具鍊的訴求。假設您 撰寫規則支援程式設計語言你的bar_binary 規則會使用 barc 編譯器 (即本身的工具本身)*.bar 做為工作區中的另一個目標因為使用者寫入「bar_binary」 目標也不必指定編譯器的依附元件 隱含依附性,方法是將其新增至規則定義做為私有屬性。

bar_binary = rule(
    implementation = _bar_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        ...
        "_compiler": attr.label(
            default = "//bar_tools:barc_linux",  # the compiler running on linux
            providers = [BarcInfo],
        ),
    },
)

//bar_tools:barc_linux 現在是每個 bar_binary 目標的依附元件,所以 這類模型會在建立 bar_binary 目標前建立只要規則的 的實作函式,就像其他屬性一樣:

BarcInfo = provider(
    doc = "Information about how to invoke the barc compiler.",
    # In the real world, compiler_path and system_lib might hold File objects,
    # but for simplicity they are strings for this example. arch_flags is a list
    # of strings.
    fields = ["compiler_path", "system_lib", "arch_flags"],
)

def _bar_binary_impl(ctx):
    ...
    info = ctx.attr._compiler[BarcInfo]
    command = "%s -l %s %s" % (
        info.compiler_path,
        info.system_lib,
        " ".join(info.arch_flags),
    )
    ...

這裡的問題是,編譯器的標籤是以硬式編碼的方式寫入 bar_binary 視目標平台而定,不同目標可能需要不同的編譯器 以及建立在哪個平台時 目標平台執行平台。此外 不一定知道所有可用的工具和平台 您無法在規則定義中以硬式編碼的方式加入代碼。

更好的解決方案是將負擔轉化為使用者 _compiler 屬性 (非私人)接著,個別指定目標即可 都須經過硬式編碼,才能針對單一平台或平台

bar_binary(
    name = "myprog_on_linux",
    srcs = ["mysrc.bar"],
    compiler = "//bar_tools:barc_linux",
)

bar_binary(
    name = "myprog_on_windows",
    srcs = ["mysrc.bar"],
    compiler = "//bar_tools:barc_windows",
)

您可以使用 select 選擇compiler,讓這個解決方案更臻完善 不同平台

config_setting(
    name = "on_linux",
    constraint_values = [
        "@platforms//os:linux",
    ],
)

config_setting(
    name = "on_windows",
    constraint_values = [
        "@platforms//os:windows",
    ],
)

bar_binary(
    name = "myprog",
    srcs = ["mysrc.bar"],
    compiler = select({
        ":on_linux": "//bar_tools:barc_linux",
        ":on_windows": "//bar_tools:barc_windows",
    }),
)

但要逐一詢問每位 bar_binary 使用者既辛苦了, 如未在整個工作區中持續使用這個樣式, 適用於單一平台,但在延伸至 多平台的情境也未解決新增支援問題 ,不必修改現有規則或目標。

如要解決這個問題,工具鍊架構會增加額外的 間接基本上,您應宣告規則具有抽象依附元件 適用於部分目標系列 (工具鍊類型) 和 Bazel 的角色 會根據 適用的平台限制。規則作者和目標作者皆不包含 且需要全面瞭解可用的平台和工具鍊。

編寫使用工具鍊的規則

在工具鍊架構下,規則不應直接依賴工具 而是依附工具鍊類型。工具鍊類型是一項簡單的目標 代表的一系列工具為 平台。舉例來說,您可以宣告代表長條的類型 編譯器:

# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")

上一節的規則定義經過修改,因此 將編譯器視為屬性,就會宣告該函式會使用 //bar_tools:toolchain_type 工具鍊。

bar_binary = rule(
    implementation = _bar_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        ...
        # No `_compiler` attribute anymore.
    },
    toolchains = ["//bar_tools:toolchain_type"],
)

實作函式現在會在 ctx.toolchains 底下存取這個依附元件 而非 ctx.attr,使用工具鍊類型做為金鑰。

def _bar_binary_impl(ctx):
    ...
    info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
    # The rest is unchanged.
    command = "%s -l %s %s" % (
        info.compiler_path,
        info.system_lib,
        " ".join(info.arch_flags),
    )
    ...

ctx.toolchains["//bar_tools:toolchain_type"] 會傳回 ToolchainInfo 供應商 任何目標 Bazel 都會解析工具鍊依附元件。Deployment 的欄位 ToolchainInfo 物件由基礎工具的規則設定;未來 該規則,會定義讓 barcinfo 欄位用來包裝 BarcInfo 物件。

說明 Bazel 將工具鍊解析至目標的程序 下文。實際上,只有已解析的工具鍊目標 做出了 bar_binary 目標的依附元件,而非整個候選空間 工具鍊。

必要和選用工具鍊

根據預設,當規則使用裸標籤表示工具鍊類型依附元件時 (如上所示),工具鍊類型視為「必要」。如果 Bazel 找不到相符的工具鍊 (請參閱 工具鍊解決方法) 則為錯誤且分析停止。

您可改為宣告「選用」工具鍊類型依附元件,如下所示: 如下:

bar_binary = rule(
    ...
    toolchains = [
        config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
    ],
)

如果無法解析選用工具鍊類型,系統會繼續進行分析,然後 ctx.toolchains["//bar_tools:toolchain_type"] 的結果是 None

config_common.toolchain_type 函式預設為必要

你可以使用以下表單:

  • 必要工具鍊類型:
    • toolchains = ["//bar_tools:toolchain_type"]
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
  • 選用工具鍊類型:
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
    ...
    toolchains = [
        "//foo_tools:toolchain_type",
        config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
    ],
)

您也可以在同一項規則中混和比對表單。不過,如果 工具鍊類型已多次列出,只有最嚴格的版本 強制要求比選填項目更嚴格

編寫使用工具鍊的切面

切面可存取與規則相同的工具鍊 API:您可以定義 工具鍊型別、透過結構定義存取工具鍊,以及使用這些工具鍊來產生新的 建立動作

bar_aspect = aspect(
    implementation = _bar_aspect_impl,
    attrs = {},
    toolchains = ['//bar_tools:toolchain_type'],
)

def _bar_aspect_impl(target, ctx):
  toolchain = ctx.toolchains['//bar_tools:toolchain_type']
  # Use the toolchain provider like in a rule.
  return []

定義工具鍊

如要定義特定工具鍊類型的工具鍊,您需要具備以下三件事:

  1. 代表特定工具或工具套件種類的語言專屬規則。變更者: 請使用「_工具鍊」結尾。

    1. 注意:\_toolchain 規則無法建立任何建構動作。 而是從其他規則收集構件,並轉送至 建立使用工具鍊的規則該規則負責 建構動作
  2. 這個規則類型的多個目標,代表工具或工具的版本 分別適用於不同平台

  3. 針對每個這類目標,一般通用的 toolchain 來提供工具鍊架構使用的中繼資料。此toolchain 目標也是指與這個工具鍊相關聯的 toolchain_type。 這表示指定的 _toolchain 規則可與任何 toolchain_type,而且只限於使用 toolchain 執行個體的 這項 _toolchain 規則與 toolchain_type 相關聯。

以下是我們舉例說明的 bar_toolchain 規則定義。我們的 其中只有編譯器,但連結器等其他工具 歸入同一個群組

def _bar_toolchain_impl(ctx):
    toolchain_info = platform_common.ToolchainInfo(
        barcinfo = BarcInfo(
            compiler_path = ctx.attr.compiler_path,
            system_lib = ctx.attr.system_lib,
            arch_flags = ctx.attr.arch_flags,
        ),
    )
    return [toolchain_info]

bar_toolchain = rule(
    implementation = _bar_toolchain_impl,
    attrs = {
        "compiler_path": attr.string(),
        "system_lib": attr.string(),
        "arch_flags": attr.string_list(),
    },
)

規則必須傳回 ToolchainInfo 供應器,這個提供者會成為 使用規則擷取時,會使用 ctx.toolchains 和 工具鍊類型。ToolchainInfo (例如 struct) 可存放任意欄位/值 配對。新增至 ToolchainInfo 欄位的確切規格 都應在工具鍊類型中明確記錄在本例中 傳回納入 BarcInfo 物件以重複使用上述定義的結構定義;本 樣式有助於驗證及重複使用程式碼。

您現在可以為特定 barc 編譯器定義目標。

bar_toolchain(
    name = "barc_linux",
    arch_flags = [
        "--arch=Linux",
        "--debug_everything",
    ],
    compiler_path = "/path/to/barc/on/linux",
    system_lib = "/usr/lib/libbarc.so",
)

bar_toolchain(
    name = "barc_windows",
    arch_flags = [
        "--arch=Windows",
        # Different flags, no debug support on windows.
    ],
    compiler_path = "C:\\path\\on\\windows\\barc.exe",
    system_lib = "C:\\path\\on\\windows\\barclib.dll",
)

最後,您要為兩個 bar_toolchain 目標建立 toolchain 定義。 這些定義會將語言特定目標連結至工具鍊類型 提供了限制資訊,告知 Bazel 在工具鍊 指定不同平台適用的資源

toolchain(
    name = "barc_linux_toolchain",
    exec_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    target_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":barc_linux",
    toolchain_type = ":toolchain_type",
)

toolchain(
    name = "barc_windows_toolchain",
    exec_compatible_with = [
        "@platforms//os:windows",
        "@platforms//cpu:x86_64",
    ],
    target_compatible_with = [
        "@platforms//os:windows",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":barc_windows",
    toolchain_type = ":toolchain_type",
)

使用上述相對路徑語法暗示這些定義都位於 但就沒有理由使用工具鍊類型、特定語言 工具鍊目標和 toolchain 定義目標不得各自獨立 套件

請參閱 go_toolchain 查看實際範例

工具鍊和設定

規則作者的一個重要問題是,當 bar_toolchain 目標如下 分析的畫面、可檢視的設定,以及可進行哪些轉換 是否應用於依附元件?上述範例使用字串屬性, 複雜工具鍊會發生什麼情況,並依附於其他目標 該如何設定?

以下是更複雜的 bar_toolchain 版本:

def _bar_toolchain_impl(ctx):
    # The implementation is mostly the same as above, so skipping.
    pass

bar_toolchain = rule(
    implementation = _bar_toolchain_impl,
    attrs = {
        "compiler": attr.label(
            executable = True,
            mandatory = True,
            cfg = "exec",
        ),
        "system_lib": attr.label(
            mandatory = True,
            cfg = "target",
        ),
        "arch_flags": attr.string_list(),
    },
)

attr.label 的使用方式與標準規則相同。 但 cfg 參數的含意略有不同

透過工具鍊從目標 (稱為「父項」) 到工具鍊的依附元件 解析時,我們會使用稱為「工具鍊」的特殊設定轉換 轉換」。工具鍊轉換設定維持不變,但 強制讓工具鍊的執行平台與 父項 (否則,工具鍊的工具鍊解析可以選擇任何 執行平台,不一定會與父項相同)。這個 讓工具鍊的任何 exec 依附元件也都能對 父項的建構動作。任何使用 cfg = "target" (或未指定 cfg,因為「target」為預設值) 的工具鍊依附元件都是 。這可讓工具鍊規則 同時提供程式庫 (上述 system_lib 屬性) 和工具 ( compiler 屬性) 傳送至需要這些規則的建構規則。系統程式庫 會連結至最終成果,因此必須針對相同項目進行建構 而編譯器則是在建構期間叫用的工具,必須 以及您要在執行平台上運行的系統

使用工具鍊註冊及建構

到目前為止,所有建構模塊都已組成,您只需在 適用於 Bazel 解析程序的工具鍊。做法是 在 WORKSPACE 檔案中 register_toolchains(),或傳遞工具鍊指令的標籤 方法是使用 --extra_toolchains 旗標,

register_toolchains(
    "//bar_tools:barc_linux_toolchain",
    "//bar_tools:barc_windows_toolchain",
    # Target patterns are also permitted, so you could have also written:
    # "//bar_tools:all",
    # or even
    # "//bar_tools/...",
)

使用目標模式註冊工具鍊時, 個別工具鍊的註冊方式取決於下列規則:

  • 套件子套件中定義的工具鍊會註冊 套件本身中定義的工具鍊。
  • 在套件中,工具鍊會按照 他們的名稱

現在要建立依附工具鍊類型的目標時 根據目標和執行平台選擇工具鍊。

# my_pkg/BUILD

platform(
    name = "my_target_platform",
    constraint_values = [
        "@platforms//os:linux",
    ],
)

bar_binary(
    name = "my_bar_binary",
    ...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform

Bazel 會注意到 //my_pkg:my_bar_binary 正透過符合下列條件的平台建構: 具有 @platforms//os:linux,因此會解析 //bar_tools:barc_linux_toolchain//bar_tools:toolchain_type 參照。 這會導致建立 //bar_tools:barc_linux,但不會 //bar_tools:barc_windows

工具鍊解析度

針對使用工具鍊的每個目標,Bazel 的工具鍊解析程序 決定目標的具體工具鍊依附元件。模型會接收輸入內容 目標平台、可用清單、 執行平台和可用工具鍊清單。其輸出內容是 為每種工具鍊類型和選定的執行作業選取的工具鍊 和目前的目標平台

可用的執行平台和工具鍊是從 WORKSPACE 個檔案透過以下應用程式存取: register_execution_platformsregister_toolchains。 您也可以在 透過指令列 --extra_execution_platforms--extra_toolchains。 系統會自動將主機平台納入為可用的執行平台。 可用的平台和工具鍊會以排序清單追蹤,以做出判斷、 優先採用清單中較早的項目

這組工具鍊 (按優先順序排列) 是從 --extra_toolchainsregister_toolchains

  1. 系統會先新增使用 --extra_toolchains 註冊的工具鍊。
    1. 在這些中,「最後一個」工具鍊的優先順序最高。
  2. 使用 register_toolchains 註冊的工具鍊
    1. 在這些中,第一個提到的工具鍊是最高的優先順序。

注意: 虛擬目標,例如 :all:*/... 會依照 Bazel 的套件排序 採用字母順序的載入機制

解決步驟如下。

  1. target_compatible_withexec_compatible_with 子句「符合」 如果平台中的每個 constraint_value,平台也 constraint_value (明確或預設為預設值)。

    如果平台有來自 constraint_settingconstraint_value 所參照的子句,這不會影響比對。

  2. 如果建構的目標指定 exec_compatible_with 屬性 (或其規則定義指定 exec_compatible_with 引數), 可用的執行平台清單經過篩選,即可移除 不符合執行限制條件的任何通知

  3. 針對每個可用的執行平台,您可將每個工具鍊類型與 第一個與此執行作業相容的工具鍊 (若有) 平台和目標平台

  4. 任何執行平台找不到相容的必要工具鍊 適用的工具鍊類型排除流量。其餘平台的 第一個是目前目標的執行平台 toolchains (若有) 會成為目標的依附元件。

所選執行平台會用來執行目標的所有動作 產生的內容

能夠以多種設定建構同一個目標 (例如 如果使用的是相同版本中的不同 CPU,則會套用解析度程序 獨立於目標版本

如果規則使用執行群組,每次執行時 每個群組會分別執行工具鍊解析,且各自會有自己的執行 平台和工具鍊

偵錯工具鍊

如果您要為現有規則新增工具鍊支援,請使用 --toolchain_resolution_debug=regex 標記。在工具鍊解析期間,標記 可針對與規則運算式變數相符的工具鍊類型或目標名稱,提供詳細輸出。個人中心 可以使用 .* 輸出所有資訊。Bazel 會輸出工具鍊的名稱 會在解決過程中檢查和略過

想瞭解哪些 cquery 依附元件來自工具鍊 解析度,請使用 cquery--transitions 標記:

# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)

# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
  [toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211