平台

简介

Bazel 可以在各种硬件、操作系统和系统配置上构建和测试代码。这可能涉及不同版本的构建工具,例如链接器和编译器。为了帮助管理这种复杂性,Bazel 引入了 “约束条件”和“平台”的概念。

约束条件是指构建或生产机器的显著属性。 常见的约束条件包括 CPU 架构、是否存在 GPU,或本地安装的编译器的版本。但是,在编排构建工作时,约束条件可以是任何能有意义地区分机器的属性。

“平台”是指指定完整机器的一组约束条件。 Bazel 使用此概念让开发者选择要 构建的机器、应运行编译和测试操作的机器,以及 构建操作应使用哪些工具链进行编译。

开发者还可以使用约束条件来选择 构建规则的自定义属性或依赖项。例如:“使用 src_arm.cc当构建面向 Arm 机器时”。

平台类型

Bazel 识别平台可能扮演的三种角色:

  • 宿主 - Bazel 本身运行的平台。
  • 执行 - 运行编译操作以生成构建输出的平台。
  • 目标 - 构建的代码应运行的平台。

构建通常与平台有三种关系:

  • 单平台构建 - 宿主平台、执行平台和目标平台 相同。例如,在开发机器上进行构建,而不进行远程执行,然后在同一机器上运行构建的二进制文件。

  • 交叉编译构建 - 宿主平台和执行平台相同, 但目标平台不同。例如,在 Macbook Pro 上构建 iOS 应用,而不进行远程执行。

  • 多平台构建 - 宿主平台、执行平台和目标平台都 不同。例如,在 Macbook Pro 上构建 iOS 应用,并使用远程 Linux 机器来编译不需要 Xcode 的 C++ 操作。

指定平台

开发者使用平台的最常见方式是使用 --platforms 标志指定所需的目标机器:

$ bazel build //:my_linux_app --platforms=//myplatforms:linux_x86

组织通常维护自己的平台定义,因为不同组织的构建机器设置各不相同。

如果未设置 --platforms,则默认设置为 @platforms//host。这是专门定义的,用于自动检测宿主机器的操作系统和 CPU 属性,以便构建面向 Bazel 运行的同一机器。构建规则可以使用 选择这些属性,并使用 @platforms/os@platforms/cpu 约束条件。

通常有用的约束条件和平台

为了保持生态系统的一致性,Bazel 团队维护了一个代码库,其中包含最常用的 CPU 架构和操作系统的约束条件定义。这些定义都在 https://github.com/bazelbuild/platforms 中。

Bazel 附带以下特殊平台定义:@platforms//host(别名为 @bazel_tools//tools:host_platform)。这会自动检测 Bazel 运行的机器的操作系统和 CPU 属性。

定义约束条件

约束条件使用 constraint_settingconstraint_value 构建规则进行建模。

constraint_setting 声明一种属性类型。例如:

constraint_setting(name = "cpu")

constraint_value 声明该属性的可能值:

constraint_value(
    name = "x86",
    constraint_setting = ":cpu"
)

在定义平台或自定义其构建规则时,这些可以作为标签引用。如果在 cpus/BUILD 中定义了上述示例,您可以将 x86 约束条件引用为 //cpus:x86

如果可见性允许,您可以为现有的 constraint_setting 定义自己的值,从而对其进行扩展。

定义平台

The platform 构建规则 将平台定义为 constraint_value 的集合:

platform(
    name = "linux_x86",
    constraint_values = [
        "//oses:linux",
        "//cpus:x86",
    ],
)

这会为必须同时具有 //oses:linux//cpus:x86 约束条件的机器建模。

对于给定的 constraint_setting,平台可能只有一个 constraint_value。 这意味着,例如,除非您创建另一个 constraint_setting 类型来为第二个值建模,否则平台不能有两个 CPU。

跳过不兼容的目标

在为特定目标平台构建时,通常需要跳过永远无法在该平台上运行的目标。例如,您的 Windows 设备驱动程序在 Linux 机器上使用 //... 进行构建时,可能会生成大量编译器错误。使用 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 进行构建兼容,与其他所有内容都不兼容。 不兼容是可传递的。任何以传递方式依赖于不兼容目标的任何目标本身也被视为不兼容。

何时跳过目标?

当目标被视为不兼容并作为目标模式扩展的一部分包含在构建中时,系统会跳过这些目标。例如,以下两个调用会跳过在目标模式扩展中找到的任何不兼容目标。

$ 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"],
    }),
)

上述内容可解读为:

  1. 当面向 macOS 时,目标没有约束条件。
  2. 当面向 Linux 时,目标没有约束条件。
  3. 否则,目标具有 @platforms//:incompatible 约束条件。由于 @platforms//:incompatible 不属于任何平台,因此目标被视为不兼容。

为了使您的约束条件更易于阅读,请使用 skylib's 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 检测不兼容的目标

您可以使用 IncompatiblePlatformProviderbazel cquery's Starlark 输出 格式中区分不兼容的目标和兼容的目标。

这可用于过滤掉不兼容的目标。以下示例将仅输出兼容目标的标签。不兼容的目标不会输出。

$ cat example.cquery

def format(target):
  if "IncompatiblePlatformProvider" not in providers(target):
    return target.label
  return ""


$ bazel cquery //... --output=starlark --starlark:file=example.cquery

已知问题

不兼容的目标会忽略可见性限制。