Bazel 可以使用许多不同版本的构建工具(如链接器和编译器)在各种硬件、操作系统和系统配置上构建和测试代码。为了帮助管理这种复杂性,Bazel 有一个“限制”和“平台”这一概念。限制条件是指构建或生产环境可能有所不同的维度,例如 CPU 架构、GPU 是否存在、系统安装的编译器版本。平台是这些约束条件选项的命名集合,表示在某些环境中可用的特定资源。
将环境建模为平台有助于 Bazel 自动为构建操作选择适当的工具链。平台还可与 config_setting 规则结合使用,以编写可配置的属性。
Bazel 认识到平台可能会提供的三种角色:
- 主机 - Bazel 本身所在的平台。
- Execution - 这是一个平台,构建工具可在其中执行构建操作以生成中间和最终输出。
- 目标 - 最终输出驻留和执行的平台。
Bazel 支持有关平台的以下构建场景:
单平台 build(默认)- 主机平台、执行平台和目标平台相同。例如,构建一个在 Intel x64 CPU 上运行的 Ubuntu 可执行文件。
交叉编译 build - 主机平台和执行平台相同,但目标平台不同。例如,在 macOS 上运行的 MacBook Pro 上构建 iOS 应用。
多平台 build - 托管平台、执行平台和目标平台都不同。
定义限制和平台
平台可使用 BUILD
文件中的 constraint_setting
和 constraint_value
规则来定义可能的平台空间。constraint_setting
会创建一个新的维度,而 constraint_value
则会为给定的维度创建一个新值;它们可以有效定义枚举及其可能的值。例如,BUILD
文件的以下代码段引入了对系统 glibc 版本的约束条件,并提供了两个可能的值。
constraint_setting(name = "glibc_version")
constraint_value(
name = "glibc_2_25",
constraint_setting = ":glibc_version",
)
constraint_value(
name = "glibc_2_26",
constraint_setting = ":glibc_version",
)
可以针对工作区中的不同软件包定义约束条件及其值。它们按标签引用,并受常规的可见性控制约束。如果可见性允许,您可以通过定义自己的约束条件来扩展现有约束条件设置。
platform
规则会引入具有特定限制条件值的新平台。以下代码会创建一个名为 linux_x86
的平台,并指出该平台描述了在 x86_64 架构上运行 glibc 2.25 版本的任何 Linux 操作系统。(如需详细了解 Bazel 的内置约束,请参阅下文。)
platform(
name = "linux_x86",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
":glibc_2_25",
],
)
通常有用的限制和平台
为保持生态系统的一致性,Bazel 团队维护着一个代码库,其中包含对于最常用的 CPU 架构和操作系统的限制定义。这些测试均位于 https://github.com/bazelbuild/platforms 中。
Bazel 附带以下特殊平台定义:@local_config_platform//:host
。这是自动检测到的主机平台值,表示运行 Bazel 的系统上自动检测到的平台。
为 build 指定平台
您可以使用以下命令行标志为 build 指定主机和目标平台:
--host_platform
- 默认为@bazel_tools//platforms:host_platform
--platforms
- 默认为@bazel_tools//platforms:target_platform
跳过不兼容的目标
针对特定目标平台进行构建时,人们通常希望跳过在该平台上永远不起作用的目标。例如,在配备 //...
的 Linux 计算机上进行构建时,您的 Windows 设备驱动程序可能会产生大量编译器错误。使用 target_compatible_with
属性告知 Bazel,您的代码有哪些目标平台限制。
使用此属性最简单的用途是将目标限制为单个平台。系统不会针对无法满足所有限制条件的任何平台构建目标。以下示例将 win_driver_lib.cc
限制为 64 位 Windows。
cc_library(
name = "win_driver_lib",
srcs = ["win_driver_lib.cc"],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
],
)
:win_driver_lib
仅兼容使用 64 位 Windows 进行构建,并且与所有其他框架都不兼容。不兼容性具有传递性。任何传递不兼容目标的任何传递目标本身均被视为不兼容。
系统会在何时跳过目标?
当目标被认为不兼容且包含在目标模式扩展的 build 中时,系统会跳过这些目标。例如,以下两个调用会跳过在目标模式扩展中找到的所有不兼容目标。
$ bazel build --platforms=//:myplatform //...
$ bazel build --platforms=//:myplatform //:all
如果在命令行中使用 --expand_test_suites
指定 test_suite
,同样会跳过 test_suite
中不兼容的测试。换言之,命令行中的 test_suite
目标的行为类似于 :all
和 ...
。使用 --noexpand_test_suites
可防止扩展,并且会导致包含不兼容测试的 test_suite
目标也不兼容。
在命令行中明确指定不兼容的目标会导致错误消息和构建失败。
$ bazel build --platforms=//:myplatform //:target_incompatible_with_myplatform
...
ERROR: Target //:target_incompatible_with_myplatform is incompatible and cannot be built, but was explicitly requested.
...
FAILED: Build did NOT complete successfully
如果启用了 --skip_incompatible_explicit_targets
,不兼容的显式目标会被静默跳过。
表现力更强的限制
若要更灵活地表示约束条件,请使用任何平台都不符合的 @platforms//:incompatible
constraint_value
。
将 select()
与 @platforms//:incompatible
结合使用,以表示更复杂的限制。例如,您可以使用它来实现基本的 OR 逻辑。以下标记库与 macOS 和 Linux 兼容,但与其他平台不兼容。
cc_library(
name = "unixish_lib",
srcs = ["unixish_lib.cc"],
target_compatible_with = select({
"@platforms//os:osx": [],
"@platforms//os:linux": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
)
上述内容可解读为:
- 以 macOS 为目标平台时,该目标没有限制。
- 以 Linux 为目标时,目标没有限制条件。
- 否则,目标将具有
@platforms//:incompatible
约束。由于@platforms//:incompatible
不属于任何平台,因此目标被视为不兼容。
若要提高约束条件的可读性,请使用 skylib 的 selects.with_or()
。
您可以通过类似方式表示反向兼容性。以下示例描述了一个与 ARM 除外兼容的所有库。
cc_library(
name = "non_arm_lib",
srcs = ["non_arm_lib.cc"],
target_compatible_with = select({
"@platforms//cpu:arm": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)
使用 bazel cquery
检测不兼容的目标
您可以使用 bazel cquery
的 Starlark 输出格式中的 IncompatiblePlatformProvider
来区分不兼容的目标和兼容的目标。
这可用于过滤掉不兼容的目标。以下示例将仅输出兼容目标的标签。系统不会输出不兼容的目标。
$ cat example.cquery
def format(target):
if "IncompatiblePlatformProvider" not in providers(target):
return target.label
return ""
$ bazel cquery //... --output=starlark --starlark:file=example.cquery
已知问题
不兼容的目标会忽略可见性限制。