本页面介绍了工具链框架,规则作者可以借助该框架 将其规则逻辑与基于平台的工具选择分离开来。时间是 建议阅读规则和平台 然后再继续。本页面介绍为什么需要使用工具链,以及如何 定义和使用它们,以及 Bazel 如何根据上下文选择合适的工具链 平台限制。
设计初衷
我们先来了解一下工具链旨在解决的问题。假设您
编写规则来支持编程语言。您的bar_binary
*.bar
则使用编译器 barc
编译程序
已构建为工作区中的另一个目标。因为撰写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 的位置。其中
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
则默认为必需项。
可以使用以下表单:
- 必需的工具链类型:
<ph type="x-smartling-placeholder">
- </ph>
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)]
- 可选工具链类型:
<ph type="x-smartling-placeholder">
- </ph>
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),
],
)
您还可以在同一规则中混搭表单。但是,如果 工具链类型多次列出,则采用最严格的版本, 其中“required”比“optional”更严格。
编写使用工具链的切面
切面可以访问规则所用的工具链 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 []
定义工具链
如需为给定工具链类型定义一些工具链,您需要具备三项内容:
表示工具或工具套件类型的特定于语言的规则。修改者 惯例,此规则的名称以“_toolchain”为后缀。
- 注意:
\_toolchain
规则无法创建任何构建操作。 相反,它会从其他规则中收集工件并将其转发到 使用工具链的规则。该规则负责创建 构建操作
- 注意:
此规则类型的多个目标,表示相应工具或工具的版本 套件。
对于每个此类定位条件,通用
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
添加哪些字段的确切规范
都应该在工具链类型中明确记录。在此示例中,值
return 封装在 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 解析过程的工具链。这是通过
使用以下代码在 MODULE.bazel
文件中注册工具链:
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:toolchain_type
对 //bar_tools:barc_linux_toolchain
的引用。
这将最终构建 //bar_tools:barc_linux
,但不会构建
//bar_tools:barc_windows
。
工具链解决方案
对于每个使用工具链的目标,Bazel 的工具链解析过程 则决定了目标的具体工具链依赖项。该过程采用 输入一组所需的工具链类型、目标平台、 可用的执行平台和可用工具链列表。其输出 是针对每种工具链类型和选定执行选定的工具链 平台。
可用的执行平台和工具链是从
通过
register_execution_platforms
和
register_toolchains
次来电
MODULE.bazel
文件。
您也可以在
通过
--extra_execution_platforms
和
--extra_toolchains
。
托管平台会自动添加为可用的执行平台。
可用平台和工具链作为确定性的有序列表进行跟踪,
并优先考虑列表中较早的项
可用工具链集按优先级顺序创建,
--extra_toolchains
和 register_toolchains
:
- 首先添加使用
--extra_toolchains
注册的工具链。(在 它们,最后一个工具链具有最高优先级。) - 使用
register_toolchains
在可传递外部中注册的工具链 依赖关系图的顺序如下:(在这些图表中,第一个 提及的工具链具有最高优先级。)- 由根模块注册的工具链(例如,
MODULE.bazel
工作区根目录); - 在用户的
WORKSPACE
文件中注册的工具链,包括在 从该处调用的宏 - 由非根模块注册的工具链(例如,由 根模块及其依赖项等等);
- 在“WORKSPACE 后缀”中注册的工具链;只有 与 Bazel 安装捆绑在一起的特定原生规则
- 由根模块注册的工具链(例如,
注意:伪目标,如 :all
、:*
和
/...
按 Bazel 的软件包进行排序
这种加载机制使用字典排序方法。
解决步骤如下。
target_compatible_with
或exec_compatible_with
子句与 platform,如果对于其列表中的每个constraint_value
,该平台还具有 该constraint_value
(显式或默认)。如果平台的
constraint_value
来自constraint_setting
而非 子句引用,这些都不会影响匹配。如果构建的目标指定了
exec_compatible_with
属性 (或其规则定义指定了exec_compatible_with
参数), 已过滤可用执行平台列表,以移除 任何不符合执行约束条件的对象。已过滤可用工具链列表,以移除所有工具链 指定与当前配置不匹配的
target_settings
。对于每个可用的执行平台,您可以将每种工具链类型与 与此执行兼容的第一个可用工具链(如果有) 平台和目标平台
任何未能找到兼容的强制性工具链的执行平台 将被排除在其他平台中 第一个成为当前目标的执行平台, 工具链(如果有的话)成为目标的依赖项。
所选的执行平台用于运行目标 生成的内容。
如果可以在多个配置中构建同一目标(例如 针对不同 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