Bazel 可以使用多种不同版本的构建工具(例如链接器和编译器)在各种硬件、操作系统和系统配置上构建和测试代码。为了帮助管理这种复杂性,Bazel 引入了限制条件和平台的概念。限制是指构建或生产环境可能不同的维度,例如 CPU 架构、是否存在 GPU 或系统安装的编译器的版本。平台是这些限制的命名选择集合,表示在某些环境中可用的特定资源。
将环境建模为平台有助于 Bazel 自动为构建操作选择合适的工具链。平台还可以与 config_setting 规则结合使用,以编写可配置的属性。
Bazel 可识别平台可能发挥的三种作用:
- 宿主 - Bazel 本身运行的平台。
- 执行 - 一种平台,构建工具可在该平台上执行构建操作,以生成中间输出和最终输出。
- 目标 - 最终输出所在并执行的平台。
在平台方面,Bazel 支持以下 build 场景:
单平台 build(默认)- 宿主平台、执行平台和目标平台相同。例如,在搭载 Intel x64 CPU 的 Ubuntu 上构建 Linux 可执行文件。
交叉编译 build - 宿主平台和执行平台相同,但目标平台不同。例如,在 MacBook Pro 上运行的 macOS 上构建 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 架构上运行 Linux 操作系统且 glibc 版本为 2.25 的任何环境。(如需详细了解 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
- 默认为@local_config_platform//:host
@local_config_platform
是一种用于检测宿主操作系统和 CPU 并写入平台目标的代码库规则。- 它还会创建
@local_config_platform//:constraintz.bzl
,其中公开了一个名为HOST_CONSTRAINTS
的数组,该数组可用于其他 BUILD 和 Starlark 文件。
--platforms
- 默认为宿主平台- 这意味着,如果没有设置其他标志,
@local_config_platform//:host
就是目标平台。 - 如果设置了
--host_platform
但未设置--platforms
,则--host_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
已知问题
不兼容的目标会忽略可见性限制。