C++ 工具链配置

报告问题 查看源代码

概览

要使用合适的选项调用编译器,Bazel 需要了解编译器的内部原理,例如目录和重要标志。 换句话说,Bazel 需要一个简化的编译器模型以了解它的工作原理。

Bazel 需要知道以下信息:

  • 编译器是支持 ThinLTO、模块、动态链接还是 PIC(与位置无关的代码)。
  • 指向所需工具(例如 gcc、ld、ar、objcopy 等)的路径。
  • 内置系统包含目录。Bazel 需要使用这些信息来验证源文件中包含的所有头文件是否已在 BUILD 文件中正确声明。
  • 默认的 sysroot。
  • 哪些标记用于编译、链接和归档。
  • 哪些标记将用于受支持的编译模式(opt、dbg、fastbuild)。
  • 将特定变量设为编译器需要的变量。

如果编译器支持多个架构,则 Bazel 需要单独对其进行配置。

CcToolchainConfigInfo 是为 Bazel 的 C++ 规则配置行为提供必要细化程度的提供程序。默认情况下,Bazel 会自动为您的 build 配置 CcToolchainConfigInfo,但您可以选择手动对其进行配置。为此,您需要一个提供 CcToolchainConfigInfo 的 Starlark 规则,并且需要将 cc_toolchaintoolchain_config 属性指向您的规则。您可以通过调用 cc_common.create_cc_toolchain_config_info() 来创建 CcToolchainConfigInfo。您可以在 @rules_cc//cc:cc_toolchain_config_lib.bzl 中找到进程中需要的所有结构体的 Starlark 构造函数。

当 C++ 目标进入分析阶段时,Bazel 会根据 BUILD 文件选择合适的 cc_toolchain 目标,并从 cc_toolchain.toolchain_config 属性中指定的目标获取 CcToolchainConfigInfo 提供程序。cc_toolchain 目标通过 CcToolchainProvider 将此类信息传递给 C++ 目标。

例如,由 cc_binarycc_library 等规则实例化的编译或链接操作需要以下信息:

  • 要使用的编译器或链接器
  • 编译器/链接器的命令行标记
  • 通过 --copt/--linkopt 选项传递的配置标志
  • 环境变量
  • 执行操作所需的沙盒所需的工件

上述所有信息(沙盒中所需的工件除外)均在 cc_toolchain 指向的 Starlark 目标中指定。

要传送到沙盒中的工件是在 cc_toolchain 目标中声明的。例如,借助 cc_toolchain.linker_files 属性,您可以指定要传送到沙盒中的链接器二进制文件和工具链库。

工具链选择

工具链选择逻辑的工作原理如下:

  1. 用户在 BUILD 文件中指定 cc_toolchain_suite 目标,并使用 --crosstool_top 选项将 Bazel 指向该目标。

  2. cc_toolchain_suite 目标引用多个工具链。--cpu--compiler 标志的值决定了选择哪一个工具链,是仅根据 --cpu 标志值,还是根据联合 --cpu | --compiler 值进行选择。选择过程如下所示:

    • 如果指定了 --compiler 选项,则 Bazel 会使用 --cpu | --compilercc_toolchain_suite.toolchains 特性中选择相应条目。如果 Bazel 未找到相应的条目,则会抛出错误。

    • 如果未指定 --compiler 选项,Bazel 仅使用 --cpucc_toolchain_suite.toolchains 特性中选择相应条目。

    • 如果未指定标志,Bazel 会检查主机系统并根据其发现结果选择 --cpu 值。请参阅检查机制代码

选择工具链后,Starlark 规则中的相应 featureaction_config 对象(即稍后描述的项)会管理 build 的配置。这些消息允许在 Bazel 中实现成熟的 C++ 功能,而无需修改 Bazel 二进制文件。C++ 规则支持多个独特操作(Bazel 源代码中对此进行了详细说明)。

功能

功能是需要命令行标记、操作、执行环境限制或依赖项更改的实体。功能可以很简单,例如允许 BUILD 文件选择标志配置(例如 treat_warnings_as_errors),或者与 C++ 规则进行交互,并添加指向编译的新编译操作和输入(例如 header_modulesthin_lto)。

理想情况下,CcToolchainConfigInfo 包含一个特征列表,其中每个特征都包含一个或多个标志组,每个标志组定义适用于特定 Bazel 操作的标志列表。

功能由名称指定,这允许 Starlark 规则配置与 Bazel 版本完全分离。换言之,Bazel 版本不会影响 CcToolchainConfigInfo 配置的行为,前提是这些配置不需要使用新功能。

某项功能通过以下任一方式启用:

  • 地图项的 enabled 字段设为 true
  • Bazel 或规则所有者明确启用了它。
  • 用户通过 --feature Bazel 选项或 features 规则特性将其启用。

功能可以相互依赖,具体取决于命令行标志、BUILD 文件设置和其他变量。

功能关系

依赖项通常由 Bazel 直接管理,后者只是强制执行要求,还会管理与 build 中所定义功能的性质固有冲突。工具链规范支持更精细的限制,可直接在控制功能支持和扩展的 Starlark 规则中使用。它们是:

限制条件 说明
requires = [
   feature_set (features = [
       'feature-name-1',
       'feature-name-2'
   ]),
]
功能级。仅当启用指定的必需功能时,此功能才受支持。例如,某项功能仅在特定构建模式(optdbgfastbuild)中受支持时。如果“Requires”包含多个“feature_set”,则当任一“feature_set”满足条件时(启用所有指定的功能时),该功能均受支持。
implies = ['feature']

功能级。此功能隐含指定功能。启用某项功能也会隐式启用它隐含的所有功能(即以递归方式运行)。

此外,您还可以从一组功能(例如排错程序的常见部分)中分解部分功能。您无法停用隐含功能。

provides = ['feature']

功能级。表示此功能是若干互斥的备用功能之一。例如,所有排错程序都可以指定 provides = ["sanitizer"]

如果用户一次请求使用两个或更多个互斥功能,这样可以列出其他替代方案,从而改进错误处理。

with_features = [
  with_feature_set(
    features = ['feature-1'],
    not_features = ['feature-2'],
  ),
]
标志集级别。一个地图项可以指定多个带多个标志集。如果指定了 with_features,则只有在至少有一个 with_feature_set 为指定的 features 集中的所有功能启用,并且 not_features 集中指定的所有功能均已停用时,该标记集才会扩展至 build 命令。 如果未指定 with_features,则对于指定的每个操作,系统会无条件应用标记集。

Action

操作非常灵活,可以在不考虑操作运行方式的情况下修改操作在哪些情况下执行。action_config 指定操作调用的工具二进制文件,而 feature 指定配置(标志)以确定该工具在调用操作时的行为。

特征会参考操作来指示它们会影响哪些 Bazel 操作,因为操作可以修改 Bazel 操作图。CcToolchainConfigInfo 提供程序包含具有关联的标志和工具的操作,例如 c++-compile。每个操作通过将标志与地图项相关联来分配标志。

每个操作名称表示 Bazel 执行的单一操作类型,例如编译或链接。不过,操作与 Bazel 操作类型之间存在多对一关系,其中 Bazel 操作类型是指实现操作(例如 CppCompileAction)的 Java 类。具体而言,下表中的“汇编操作”和“编译器操作”是 CppCompileAction,而链接操作是 CppLinkAction

汇编程序操作

操作 说明
preprocess-assemble 通过预处理组建。通常适用于 .S 文件。
assemble 无需预处理即可组装。通常适用于 .s 文件。

编译器操作

操作 说明
cc-flags-make-variable CC_FLAGS 传播到 Genrule。
c-compile 编译为 C。
c++-compile 编译为 C++ 语言。
c++-header-parsing 对头文件运行编译器的解析器,确保头文件是独立的,否则会生成编译错误。仅适用于支持模块的工具链。
操作 说明
c++-link-dynamic-library 关联包含所有依赖项的共享库。
c++-link-nodeps-dynamic-library 关联仅包含 cc_library 来源的共享库。
c++-link-executable 关联一个最终可运行的库。

AR 操作

AR 操作会通过 ar 将对象文件组合成归档库(.a 文件),并将一些语义编码为名称。

操作 说明
c++-link-static-library 创建静态库(归档)。

LTO 操作

操作 说明
lto-backend ThinLTO 操作,将位码编译到原生对象中。
lto-index 生成全局索引的 ThinLTO 操作。

使用 action_config

action_config 是一个 Starlark 结构体,通过指定要在操作期间调用的工具(二进制文件)以及由特征定义的标志集来描述 Bazel 操作。这些标志会对操作的执行施加限制。

action_config() 构造函数包含以下参数:

属性 说明
action_name 此操作对应的 Bazel 操作。 Bazel 使用此属性来发现每个操作的工具和执行要求。
tools 要调用的可执行文件。应用于该操作的工具将是列表中第一个具有与功能配置匹配的功能集的工具。必须提供默认值。
flag_sets 一个应用于一组操作的标志列表。与功能相同。
env_sets 适用于一组操作的环境限制条件列表。 与地图项相同。

根据前面所述的功能关系action_config 可以要求和暗示其他功能和 action_config。此行为类似于某项功能。

最后两个属性与地图项的相应属性是多余的,其中包含它们的,因为有些 Bazel 操作需要某些标志或环境变量,目标是避免不必要的 action_config+feature 对。通常,最好在多个 action_config 之间共享单个功能。

您不能在同一工具链中定义多个具有相同 action_nameaction_config。这样可以防止工具路径产生歧义,并强制执行 action_config 后面的 intent,即操作的属性在工具链中的一个位置明确描述。

使用工具构造函数

action_config 可以通过其 tools 参数指定一组工具。tool() 构造函数接受以下参数:

字段 说明
path 相关工具的路径(相对于当前位置)。
with_features 必须至少符合一个特征集才能应用此工具。

对于给定的 action_config,只有一个 tool 会将其工具路径和执行要求应用于 Bazel 操作。系统会通过迭代 action_config 上的 tools 属性来选择工具,直到找到具有功能配置匹配的 with_feature 工具(如需了解详情,请参阅本页面前面的功能关系)。您应该使用与空功能配置对应的默认工具结束工具列表。

用法示例

您可以结合使用特性和操作来实现具有各种跨平台语义的 Bazel 操作。例如,在 macOS 上生成调试符号需要在编译操作中生成符号,然后在链接操作期间调用专用工具创建压缩的 dsym 归档文件,然后解压缩该归档文件以生成可供 Xcode 使用的 app bundle 和 .plist 文件。

使用 Bazel 时,此过程可以如下所示实现,其中 unbundle-debuginfo 是 Bazel 操作:

load("@rules_cc//cc:defs.bzl", "ACTION_NAMES")

action_configs = [
    action_config (
        action_name = ACTION_NAMES.cpp_link_executable,
        tools = [
            tool(
                with_features = [
                    with_feature(features=["generate-debug-symbols"]),
                ],
                path = "toolchain/mac/ld-with-dsym-packaging",
            ),
            tool (path = "toolchain/mac/ld"),
        ],
    ),
]

features = [
    feature(
        name = "generate-debug-symbols",
        flag_sets = [
            flag_set (
                actions = [
                    ACTION_NAMES.c_compile,
                    ACTION_NAMES.cpp_compile
                ],
                flag_groups = [
                    flag_group(
                        flags = ["-g"],
                    ),
                ],
            )
        ],
        implies = ["unbundle-debuginfo"],
   ),
]

对于使用 fission 的 Linux 或生成 .pdb 文件的 Windows,可以完全实现相同的功能。例如,生成基于 fission 的调试符号的实现可能如下所示:

load("@rules_cc//cc:defs.bzl", "ACTION_NAMES")

action_configs = [
    action_config (
        name = ACTION_NAMES.cpp_compile,
        tools = [
            tool(
                path = "toolchain/bin/gcc",
            ),
        ],
    ),
]

features = [
    feature (
        name = "generate-debug-symbols",
        requires = [with_feature_set(features = ["dbg"])],
        flag_sets = [
            flag_set(
                actions = [ACTION_NAMES.cpp_compile],
                flag_groups = [
                    flag_group(
                        flags = ["-gsplit-dwarf"],
                    ),
                ],
            ),
            flag_set(
                actions = [ACTION_NAMES.cpp_link_executable],
                flag_groups = [
                    flag_group(
                        flags = ["-Wl", "--gdb-index"],
                    ),
                ],
            ),
      ],
    ),
]

标记群组

CcToolchainConfigInfo 可让您将标志捆绑到用于特定用途的组中。您可以使用标记值中的预定义变量指定标记,编译器在将标记添加到 build 命令时会将其展开。例如:

flag_group (
    flags = ["%{output_execpath}"],
)

在本示例中,该标志的内容将替换为操作的输出文件路径。

标志组会按照在列表中从上到下、从左到右的顺序扩展到构建命令。

对于需要在构建命令中使用不同的值的重复标志,该标志组可以迭代 list 类型的变量。例如,list 类型的变量 include_path

flag_group (
    iterate_over = "include_paths",
    flags = ["-I%{include_paths}"],
)

会展开为 include_paths 列表中的每个路径元素的 -I<path>。标志组声明正文中的所有标志(或 flag_group)会作为一个单元展开。例如:

flag_group (
    iterate_over = "include_paths",
    flags = ["-I", "%{include_paths}"],
)

会展开为 include_paths 列表中的每个路径元素的 -I <path>

一个变量可以重复多次。例如:

flag_group (
    iterate_over = "include_paths",
    flags = ["-iprefix=%{include_paths}", "-isystem=%{include_paths}"],
)

会展开为:

-iprefix=<inc0> -isystem=<inc0> -iprefix=<inc1> -isystem=<inc1>

变量可以对应于使用点分表示法访问的结构。例如:

flag_group (
    flags = ["-l%{libraries_to_link.name}"],
)

结构可以嵌套,还可能包含序列。为防止名称冲突并明确指定,您必须通过字段指定完整路径。例如:

flag_group (
    iterate_over = "libraries_to_link",
    flag_groups = [
        flag_group (
            iterate_over = "libraries_to_link.shared_libraries",
            flags = ["-l%{libraries_to_link.shared_libraries.name}"],
        ),
    ],
)

条件扩展

标志组支持使用 expand_if_availableexpand_if_not_availableexpand_if_trueexpand_if_falseexpand_if_equal 属性根据特定变量或其字段是否存在来支持条件扩展。例如:

flag_group (
    iterate_over = "libraries_to_link",
    flag_groups = [
        flag_group (
            iterate_over = "libraries_to_link.shared_libraries",
            flag_groups = [
                flag_group (
                    expand_if_available = "libraries_to_link.shared_libraries.is_whole_archive",
                    flags = ["--whole_archive"],
                ),
                flag_group (
                    flags = ["-l%{libraries_to_link.shared_libraries.name}"],
                ),
                flag_group (
                    expand_if_available = "libraries_to_link.shared_libraries.is_whole_archive",
                    flags = ["--no_whole_archive"],
                ),
            ],
        ),
    ],
)

CcToolchainConfigInfo 参考文档

本部分提供了成功配置 C++ 规则所需的构建变量、功能和其他参考信息。

CcToolchainConfigInfo build 变量

以下是 CcToolchainConfigInfo build 变量的参考代码。

变量 操作 说明
source_file compile 要编译的源文件。
input_file strip 要移除的工件。
output_file compile 编译输出。
output_assembly_file compile 发出的汇编文件。仅在 compile 操作发出汇编文本时适用,通常在使用 --save_temps 标志时适用。其内容与 output_file 相同。
output_preprocess_file compile 预处理输出。仅适用于编译仅预处理源文件的操作(通常在使用 --save_temps 标志时)。其内容与 output_file 相同。
includes compile 编译器必须无条件地包含在已编译源代码中的文件序列。
include_paths compile 编译器在其中搜索使用 #include<foo.h>#include "foo.h" 包含的头文件的序列目录。
quote_include_paths compile -iquote 序列包含 - 编译器在其中搜索使用 #include "foo.h" 包含的头文件的目录。
system_include_paths compile -isystem 序列包含 - 编译器在其中搜索使用 #include <foo.h> 包含的头文件的目录。
dependency_file compile 编译器生成的 .d 依赖项文件。
preprocessor_defines compile defines 序列,例如 --DDEBUG
pic compile 将输出编译为与位置无关的代码。
gcov_gcno_file compile gcov 覆盖率文件。
per_object_debug_info_file compile 每个对象的调试信息 (.dwp) 文件。
stripotps strip stripopts 序列。
legacy_compile_flags compile 来自旧版 CROSSTOOL 字段(例如 compiler_flagoptional_compiler_flagcxx_flagoptional_cxx_flag)的标志序列。
user_compile_flags compile copt 规则属性或 --copt--cxxopt--conlyopt 标志中的标志序列。
unfiltered_compile_flags compile unfiltered_cxx_flag 旧版 CROSSTOOL 字段或 unfiltered_compile_flags 功能中的标志序列。这些规则不会按 nocopts 规则特性进行过滤。
sysroot sysroot
runtime_library_search_directories 链接 链接器运行时搜索路径中的条目(通常使用 -rpath 标志设置)。
library_search_directories 链接 链接器搜索路径中的条目(通常使用 -L 标志设置)。
libraries_to_link 链接 这些标记提供要在链接调用过程中作为输入进行链接的文件。
def_file_path 链接 使用 MSVC 在 Windows 上运行的 def 文件的位置。
linker_param_file 链接 由 bazel 创建的链接器参数文件的位置,用于克服命令行长度限制。
output_execpath 链接 链接器输出的豁免。
generate_interface_library 链接 "yes""no",具体取决于是否应生成接口库。
interface_library_builder_path 链接 指向接口库构建器的路径。
interface_library_input_path 链接 接口库 ifso 构建器工具的输入。
interface_library_output_path 链接 使用 ifso 构建器工具生成接口库的路径。
legacy_link_flags 链接 来自旧版 CROSSTOOL 字段的链接器标记。
user_link_flags 链接 来自 --linkoptlinkopts 属性的链接器标志。
linkstamp_paths 链接 提供 linktampamp 路径的 build 变量。
force_pic 链接 此变量的存在表示应生成 PIC/PIE 代码(传递了 Bazel 选项 `--force_pic`)。
strip_debug_symbols 链接 该变量的存在表示应该去掉调试符号。
is_cc_test 链接 当当前操作是 cc_test 关联操作时,值为 false,否则为 false。
is_using_fission 编译、链接 此变量的存在表示激活(按对象调试信息)已激活。调试信息将位于 .dwo 文件(而非 .o 文件)中,编译器和链接器需要知道此信息。
fdo_instrument_path 编译、链接 存储 FDO 插桩配置文件的目录的路径。
fdo_profile_path compile FDO 配置的路径。
fdo_prefetch_hints_path compile 缓存预提取配置文件的路径。
csfdo_instrument_path 编译、链接 用于存储上下文相关 FDO 插桩配置文件的目录的路径。

知名功能

以下是功能及其激活条件的参考。

功能 文档
opt | dbg | fastbuild 根据编译模式默认处于启用状态。
static_linking_mode | dynamic_linking_mode 根据关联模式默认处于启用状态。
per_object_debug_info 如果指定了 supports_fission 功能且已启用,并且在 --fission 标志中指定了当前的编译模式,则启用此项。
supports_start_end_lib 启用(并且设置了选项 --start_end_lib)后,Bazel 不会链接到静态库,而是使用 --start-lib/--end-lib 链接器选项直接链接到对象。这可以加快构建速度,因为 Bazel 不必构建静态库。
supports_interface_shared_libraries 启用后(选项 --interface_shared_objects 已启用)Bazel 会将 linkstatic 设置为 False(默认为 cc_test)的目标与接口共享库相关联。这样能更快地进行重新关联。
supports_dynamic_linker 启用后,C++ 规则将知道工具链可以生成共享库。
static_link_cpp_runtimes 启用后,Bazel 将在静态链接模式下静态链接 C++ 运行时,并在动态链接模式下动态链接。在 cc_toolchain.static_runtime_libcc_toolchain.dynamic_runtime_lib 属性中指定的工件(具体取决于链接模式)会添加到链接操作中。
supports_pic 启用后,工具链将为动态库使用 PIC 对象。 每当需要 PIC 编译时,都会出现 `pic` 变量。如果默认情况下未启用,且传递的是“--force_pic”,Bazel 将请求“supports_pic”并验证是否已启用该功能。如果该功能缺失或无法启用,将无法使用“--force_pic”。
static_linking_mode | dynamic_linking_mode 根据关联模式默认处于启用状态。
no_legacy_features 防止 Bazel 向 C++ 配置添加旧版功能(如果存在)。请参阅完整的功能列表。

旧版功能修补逻辑

Bazel 会对工具链的功能进行以下更改,以实现向后兼容性:

  • legacy_compile_flags 功能移至工具链顶部
  • default_compile_flags 功能移至工具链顶部
  • 向工具链顶部添加 dependency_file(如果不存在)功能
  • 向工具链顶部添加 pic(如果不存在)功能
  • 向工具链顶部添加 per_object_debug_info(如果不存在)功能
  • 向工具链顶部添加 preprocessor_defines(如果不存在)功能
  • 向工具链顶部添加 includes(如果不存在)功能
  • 向工具链顶部添加 include_paths(如果不存在)功能
  • 向工具链顶部添加 fdo_instrument(如果不存在)功能
  • 向工具链顶部添加 fdo_optimize(如果不存在)功能
  • 向工具链顶部添加 cs_fdo_instrument(如果不存在)功能
  • 向工具链顶部添加 cs_fdo_optimize(如果不存在)功能
  • 向工具链顶部添加 fdo_prefetch_hints(如果不存在)功能
  • 向工具链顶部添加 autofdo(如果不存在)功能
  • 向工具链顶部添加 build_interface_libraries(如果不存在)功能
  • 向工具链顶部添加 dynamic_library_linker_tool(如果不存在)功能
  • 向工具链顶部添加 shared_flag(如果不存在)功能
  • 向工具链顶部添加 linkstamps(如果不存在)功能
  • 向工具链顶部添加 output_execpath_flags(如果不存在)功能
  • 向工具链顶部添加 runtime_library_search_directories(如果不存在)功能
  • 向工具链顶部添加 library_search_directories(如果不存在)功能
  • 向工具链顶部添加 archiver_flags(如果不存在)功能
  • 向工具链顶部添加 libraries_to_link(如果不存在)功能
  • 向工具链顶部添加 force_pic_flags(如果不存在)功能
  • 向工具链顶部添加 user_link_flags(如果不存在)功能
  • 向工具链顶部添加 legacy_link_flags(如果不存在)功能
  • 向工具链顶部添加 static_libgcc(如果不存在)功能
  • 向工具链顶部添加 fission_support(如果不存在)功能
  • 向工具链顶部添加 strip_debug_symbols(如果不存在)功能
  • 向工具链顶部添加 coverage(如果不存在)功能
  • 向工具链顶部添加 llvm_coverage_map_format(如果不存在)功能
  • 向工具链顶部添加 gcc_coverage_map_format(如果不存在)功能
  • 向工具链底部添加 fully_static_link(如果不存在)功能
  • 向工具链底部添加 user_compile_flags(如果不存在)功能
  • 向工具链底部添加 sysroot(如果不存在)功能
  • 向工具链底部添加 unfiltered_compile_flags(如果不存在)功能
  • 向工具链底部添加 linker_param_file(如果不存在)功能
  • 向工具链底部添加 compiler_input_flags(如果不存在)功能
  • 向工具链底部添加 compiler_output_flags(如果不存在)功能

这里列出了很多功能。按照计划,一旦 Starlark 中的 Crosstool 完成,清除这些 APK。如有兴趣了解,请参阅 CppActionConfigs 中的实现;对于生产工具链,请考虑添加 no_legacy_features 以使工具链更独立。