概览
如需使用正确的选项调用编译器,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_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 会使用--cpu | --compiler
从cc_toolchain_suite.toolchains
属性中选择相应的条目。如果 Bazel 找不到相应的条目,则会抛出错误。如果未指定
--compiler
选项,Bazel 只会使用--cpu
从cc_toolchain_suite.toolchains
属性中选择相应的条目。如果未指定任何标志,Bazel 会检查主机系统,并根据其发现结果选择
--cpu
值。请参阅检查机制代码。
选择工具链后,Starlark 规则中的相应 feature
和 action_config
对象将用于管理 build 的配置(即后面介绍的各项)。借助这些消息,您可以在不修改 Bazel 二进制文件的情况下在 Bazel 中实现完整的 C++ 功能。C++ 规则支持多种独特的操作,详见 Bazel 源代码。
功能
功能是指需要命令行标志、操作、对执行环境的约束条件或依赖项更改的实体。功能可以很简单,例如允许 BUILD
文件选择标志的配置(例如 treat_warnings_as_errors
),或与 C++ 规则交互,并在编译中添加新的编译操作和输入(例如 header_modules
或 thin_lto
)。
理想情况下,CcToolchainConfigInfo
应包含一个功能列表,其中每个功能都由一个或多个标志组组成,每个标志组都定义了适用于特定 Bazel 操作的标志列表。
功能通过名称指定,这允许将 Starlark 规则配置与 Bazel 版本完全解耦。换句话说,只要 CcToolchainConfigInfo
配置不需要使用新功能,Bazel 版本就不会影响其行为。
您可以通过以下任一方式启用功能:
- 地图项的
enabled
字段设置为true
。 - Bazel 或规则所有者明确启用该功能。
- 用户可以通过
--feature
Bazel 选项或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'] |
地图项级。此功能暗示了指定的功能。 启用某项功能还会隐式启用该功能所暗示的所有功能(即,它会递归地运行)。 此外,还能够从一组功能(例如,sanitizer 的常用部分)中提取常用功能子集。无法停用隐含功能。 |
provides = ['feature'] |
地图项级。表示此功能是多项互斥备选功能之一。例如,所有过滤器都可以指定 这样一来,如果用户同时请求两项或更多互斥功能,系统就会列出替代方案,从而改进错误处理。 |
with_features = [ with_feature_set( features = ['feature-1'], not_features = ['feature-2'], ), ] |
标志集级别。地图项可以使用多个标志集指定多个标志。
指定 with_features 后,只有当至少有一个 with_feature_set 的指定 features 集合中的所有功能都处于启用状态,并且 not_features 集中指定的所有功能都处于停用状态时,标志集才会扩展到 build 命令。
如果未指定 with_features ,系统将无条件地对指定的每个操作应用标志集。
|
操作
借助操作,您可以灵活地修改操作的执行环境,而无需假定操作的运行方式。action_config
用于指定操作调用的工具二进制文件,而 feature
用于指定配置(标志),这些配置决定了在调用操作时该工具的行为方式。
功能会引用操作,以指明它们会影响哪些 Bazel 操作,因为操作可以修改 Bazel 操作图。CcToolchainConfigInfo
提供程序包含与标志和工具相关联的操作,例如 c++-compile
。通过将操作与功能相关联,为每项操作分配标志。
每个操作名称都代表 Bazel 执行的单一操作类型,例如编译或链接。不过,操作与 Bazel 操作类型之间存在多对一关系,其中 Bazel 操作类型是指实现操作的 Java 类(例如 CppCompileAction
)。具体而言,下表中的“汇编器操作”和“编译器操作”均为 CppCompileAction
,而链接操作为 CppLinkAction
。
汇编器操作
操作 | 说明 |
preprocess-assemble
|
使用预处理进行组装。通常适用于 .S 文件。
|
assemble
|
在不预处理的情况下进行组装。通常适用于 .s 文件。
|
编译器操作
操作 | 说明 |
cc-flags-make-variable
|
将 CC_FLAGS 传播到 genrules。
|
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++ 规则所需的构建变量、功能和其他信息的参考信息。
CcToolchainConfigInfo build 变量
以下是 CcToolchainConfigInfo
构建变量的参考文档。
变量 | 操作 | 说明 |
source_file
|
compile | 要编译的源文件。 |
input_file
|
strip | 要剥离的工件。 |
output_file
|
compile、strip | 编译输出。 |
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 ) 文件。
|
stripopts
|
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
|
链接 | 链接器输出的 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
|
链接 | 用于提供链接标记路径的构建变量。 |
force_pic
|
链接 | 此变量的存在表示应生成 PIC/PIE 代码(传递了 Bazel 选项 `--force_pic`)。 |
strip_debug_symbols
|
链接 | 如果存在此变量,则表示应剥离调试符号。 |
is_cc_test
|
链接 | 如果当前操作是 cc_test 关联操作,则为 true,否则为 false。
|
is_using_fission
|
编译、链接 | 如果存在此变量,则表示已启用 fission(按对象的调试信息)。调试信息将位于 .dwo 文件中,而不是 .o 文件中,编译器和链接器需要知道这一点。
|
fdo_instrument_path
|
编译、链接 | 存储 FDO 插桩配置文件的目录的路径。 |
fdo_profile_path
|
compile | FDO 配置文件的路径。 |
fdo_prefetch_hints_path
|
compile | 缓存预提取配置文件的路径。 |
cs_fdo_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 中的交叉工具完成后移除它们。如有兴趣,请参阅 CppActionConfigs 中的实现;对于生产型工具链,不妨考虑添加 no_legacy_features
,以使工具链更加独立。