概览
为了使用正确的选项调用编译器,Bazel 需要了解一些编译器内部信息,例如 include 目录和重要标志。换句话说,Bazel 需要一个简化的编译器模型来了解其运作方式。
Bazel 需要了解以下信息:
- 编译器是否支持 thinLTO、模块、动态链接或 PIC(位置无关代码)。
- 所需工具(例如 gcc、ld、ar、objcopy 等)的路径。
- 内置的系统 include 目录。Bazel 需要这些信息来验证源文件中包含的所有头文件是否已在 BUILD文件中正确声明。
- 默认 sysroot。
- 用于编译、链接、归档的标志。
- 要用于支持的编译模式(opt、dbg、fastbuild)的标志。
- 编译器专门要求的变量。
如果编译器支持多种架构,Bazel 需要分别配置这些架构。
CcToolchainConfigInfo 是一种提供程序,可提供必要的精细度来配置 Bazel 的 C++ 规则的行为。默认情况下,Bazel 会自动为您的 build 配置 CcToolchainConfigInfo,但您可以选择手动配置。为此,您需要一个提供 CcToolchainConfigInfo 的 Starlark 规则,并且需要将 cc_toolchain 的 toolchain_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_binary 或 cc_library 等规则实例化的编译或链接操作需要以下信息:
- 要使用的编译器或链接器
- 编译器/链接器的命令行标志
- 通过 --copt/--linkopt选项传递的配置标志
- 环境变量
- 执行操作的沙盒中所需的制品
除了沙盒中所需的制品之外,上述所有信息都在 cc_toolchain 指向的 Starlark 目标中指定。
要交付到沙盒的制品在 cc_toolchain 目标中声明。例如,借助 cc_toolchain.linker_files 属性,您可以指定要运送到沙盒中的链接器二进制文件和工具链库。
工具链选择
工具链选择逻辑的运作方式如下:
- 用户在 - BUILD文件中指定- cc_toolchain_suite目标,并使用- --crosstool_top选项将 Bazel 指向该目标。
- cc_toolchain_suite目标引用了多个工具链。- --cpu和- --compiler标志的值决定了选择哪个工具链,选择依据可以是- --cpu标志值,也可以是联合- --cpu | --compiler值。选择流程如下:- 如果指定了 - --compiler选项,Bazel 会从- cc_toolchain_suite.toolchains属性中选择具有- --cpu | --compiler的相应条目。如果 Bazel 未找到相应的条目,则会抛出错误。
- 如果未指定 - --compiler选项,Bazel 会从仅包含- --cpu的- cc_toolchain_suite.toolchains属性中选择相应条目。
- 如果未指定任何标志,Bazel 会检查宿主系统并根据检查结果选择 - --cpu值。请参阅检查机制代码。
 
选择工具链后,Starlark 规则中的相应 feature 和 action_config 对象会控制 build 的配置(即稍后介绍的项)。这些消息允许在 Bazel 中实现完善的 C++ 功能,而无需修改 Bazel 二进制文件。C++ 规则支持多种独特的动作,这些动作已在 Bazel 源代码中详细记录。
功能
功能是指需要命令行标志、操作、执行环境限制或依赖项更改的实体。功能可以很简单,例如允许 BUILD 文件选择标志的配置(如 treat_warnings_as_errors),或者与 C++ 规则互动并向编译添加新的编译操作和输入(如 header_modules 或 thin_lto)。
理想情况下,CcToolchainConfigInfo 包含一个功能列表,其中每个功能都包含一个或多个标志组,每个标志组都定义了适用于特定 Bazel 操作的标志列表。
功能由名称指定,这使得 Starlark 规则配置与 Bazel 版本完全分离。换句话说,只要 CcToolchainConfigInfo 配置不需要使用新功能,Bazel 版本就不会影响这些配置的行为。
可通过以下某种方式启用某项功能:
- 相应功能的 enabled字段设置为true。
- Bazel 或规则所有者明确启用它。
- 用户可以通过 --featureBazel 选项或features规则属性启用它。
功能之间可能存在相互依赖关系,也可能依赖于命令行标志、BUILD 文件设置和其他变量。
功能关系
依赖项通常直接通过 Bazel 进行管理,Bazel 只需强制执行要求并管理构建中定义的功能固有的冲突。借助工具链规范,您可以设置更精细的限制,以便直接在管理功能支持和扩展的 Starlark 规则中使用。它们是:
| 限制条件 | 说明 | 
| requires = [
   feature_set (features = [
       'feature-name-1',
       'feature-name-2'
   ]),
] | 功能级。仅当指定所需功能处于启用状态时,才支持该功能。例如,当某项功能仅在特定 build 模式( opt、dbg或fastbuild)下受支持时。如果 `requires` 包含多个 `feature_set`,则当满足任一 `feature_set`(即所有指定的功能均已启用)时,该功能受支持。 | 
| implies = ['feature'] | 功能级。此功能表示指定的功能。 启用某项功能也会隐式启用该功能所隐含的所有功能(即以递归方式运行)。 还能够从一组功能(例如 Sanitizers 的共同部分)中提取出常见的功能子集。无法停用隐含功能。 | 
| provides = ['feature'] | 功能级。表示此功能是多个互斥的替代功能之一。例如,所有清理器都可以指定  如果用户同时请求两个或更多互斥的功能,该功能会列出替代方案,从而改进错误处理。 | 
| with_features = [
  with_feature_set(
    features = ['feature-1'],
    not_features = ['feature-2'],
  ),
] | 标志设置级别。一项功能可以指定多个标志集,并使用 multiple.
     指定 with_features时,只有在以下情况下,标志集才会扩展到 build 命令:至少有一个with_feature_set满足以下条件:指定features集中的所有功能均已启用,并且not_features集中指定的所有功能均已停用。
     如果未指定with_features,则标志集将无条件地应用于指定的每个操作。 | 
操作
借助操作,您可以灵活地修改操作的执行环境,而无需假设操作的运行方式。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_name 的 action_config。这样可避免工具路径出现歧义,并强制执行 action_config 背后的意图,即在工具链中的单个位置清楚地描述操作的属性。
使用工具构造函数
action_config 可以通过其 tools 参数指定一组工具。tool() 构造函数接受以下参数:
| 字段 | 说明 | 
| path | 相关工具的路径(相对于当前位置)。 | 
| with_features | 功能集列表,其中至少一个必须满足,此工具才能应用。 | 
对于给定的 action_config,只有一个 tool 会将其工具路径和执行要求应用于 Bazel 操作。通过迭代 action_config 上的 tools 属性来选择工具,直到找到 with_feature 设置与功能配置匹配的工具为止(如需了解详情,请参阅本页上文中的功能关系)。您应以与空功能配置对应的默认工具结束工具列表。
用法示例
功能和操作可以结合使用,以实现具有各种跨平台语义的 Bazel 操作。例如,在 macOS 上生成调试符号需要在编译操作中生成符号,然后在链接操作期间调用专用工具来创建压缩的 dsym 归档,接着解压缩该归档以生成可供 Xcode 使用的应用 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}"],
)
在这种情况下,标志的内容将被操作的输出文件路径替换。
标志组会按其在列表中的显示顺序(从上到下、从左到右)展开为 build 命令。
对于添加到 build 命令时需要重复使用不同值的标志,标志组可以迭代 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_available、expand_if_not_available、expand_if_true、expand_if_false 或 expand_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++ 规则所需的 build 变量、功能和其他信息的参考。
CcToolchainConfigInfo build 变量
以下是 CcToolchainConfigInfo 构建变量的参考。
| 变量 | 操作 | 说明 | 
| 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_flag、optional_compiler_flag、cxx_flag和optional_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 | 链接 | 在 Windows 上使用 MSVC 时所用 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 | 链接 | 来自 --linkopt或linkopts属性的链接器标志。 | 
| linkstamp_paths | 链接 | 提供 linkstamp 路径的 build 变量。 | 
| force_pic | 链接 | 如果存在此变量,则表示应生成 PIC/PIE 代码(已传递 Bazel 选项 `--force_pic`)。 | 
| strip_debug_symbols | 链接 | 如果存在此变量,则表示应剥离调试符号。 | 
| is_cc_test | 链接 | 如果当前操作是关联操作 ( cc_test),则为 true;否则为 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++ 运行时,并在动态链接模式下动态链接 C++ 运行时。 cc_toolchain.static_runtime_lib或cc_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 后将其移除。好奇的读者可以查看 CppActionConfigs 中的实现,对于生产工具链,可以考虑添加 no_legacy_features 以使工具链更加独立。