Bazel多编译器编译:如何为不同构建目标指定对应GCC版本
问题根因说明
- 你之前在
cc_binary的toolchains属性中直接传入cc_toolchain目标的写法不符合Bazel工具链框架设计:该属性要求传入的目标实现对应工具链类型的接口,返回TemplateVariableInfoprovider,而原生cc_toolchain目标本身不会直接暴露这个provider——它是通过工具链匹配框架在构建阶段按配置筛选后,自动注入到编译动作的上下文中,不能直接作为规则依赖硬绑定。 - 你之前对自定义工具链的认知偏差在于:把「全局默认平台绑定」和「自定义工具链本身」搞混了。自定义工具链可以配合平台约束、目标级配置过渡实现单目标的编译器选择,不会强制作用于全项目。
- 原生C++规则没有提供直接按目标指定编译器版本的属性,所有编译器匹配逻辑都是基于配置值做筛选的。
具体实现步骤
1. 定义编译器版本的平台约束
在项目下新建toolchain/BUILD文件,先定义用来区分编译器版本的自定义约束:
# 定义可识别的编译器版本约束 constraint_setting( name = "compiler_version", default_constraint_value = ":gcc9_version", ) constraint_value( name = "gcc9_version", constraint_setting = ":compiler_version", ) constraint_value( name = "gcc10_version", constraint_setting = ":compiler_version", ) # 定义对应编译器版本的独立平台 platform( name = "gcc9_platform", constraint_values = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ":gcc9_version", ], ) platform( name = "gcc10_platform", constraint_values = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ":gcc10_version", ], )
2. 配置双版本gcc的工具链
在同个toolchain/BUILD文件中分别定义gcc9、gcc10对应的C++工具链,并将工具链和对应的平台约束做匹配绑定:
load("@bazel_tools//tools/cpp:cc_toolchain_config.bzl", "cc_toolchain_config") # gcc9 工具链配置 cc_toolchain_config( name = "gcc9_toolchain_config", toolchain_identifier = "linux_gcc9", host_system = "linux", target_system = "linux", cpu = "x86_64", compiler = "gcc", cc = "/usr/bin/gcc-9", cxx = "/usr/bin/g++-9", # 在此配置对应gcc9的编译参数、链接参数、系统库路径 ) cc_toolchain( name = "gcc9_toolchain", toolchain_config = ":gcc9_toolchain_config", all_files = ":gcc9_runtime_files", ) # gcc10 工具链配置 cc_toolchain_config( name = "gcc10_toolchain_config", toolchain_identifier = "linux_gcc10", host_system = "linux", target_system = "linux", cpu = "x86_64", compiler = "gcc", cc = "/usr/bin/gcc-10", cxx = "/usr/bin/g++-10", # 在此配置对应gcc10的编译参数、链接参数、系统库路径 ) cc_toolchain( name = "gcc10_toolchain", toolchain_config = ":gcc10_toolchain_config", all_files = ":gcc10_runtime_files", ) # 关键:将工具链和平台约束做匹配配对,供Bazel自动选择 toolchain( name = "gcc9_toolchain_bind", toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", exec_compatible_with = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ":gcc9_version", ], target_compatible_with = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ":gcc9_version", ], toolchain = ":gcc9_toolchain", ) toolchain( name = "gcc10_toolchain_bind", toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", exec_compatible_with = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ":gcc10_version", ], target_compatible_with = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ":gcc10_version", ], toolchain = ":gcc10_toolchain", )
在项目根目录的WORKSPACE文件中注册这两个绑定好的工具链:
register_toolchains( "//toolchain:gcc9_toolchain_bind", "//toolchain:gcc10_toolchain_bind", )
3. 实现目标级的配置切换
Bazel原生的--platforms参数是全局生效的,会影响整个构建流程的所有目标,要实现单目标绑定编译器,需要用到*构建过渡(Build Transition)*能力——给特定目标单独注入配置值,不影响全局构建。
- 新建
toolchain/compiler_transition.bzl,定义过渡逻辑:
def _compiler_transition_impl(settings, attr): # 根据目标传入的compiler_version属性,切换对应的平台约束值 return { "//toolchain:compiler_version": ("//toolchain:gcc10_version" if attr.compiler_version == "gcc10" else "//toolchain:gcc9_version") } compiler_transition = transition( implementation = _compiler_transition_impl, inputs = [], outputs = ["//toolchain:compiler_version"], )
- 新建
toolchain/rules.bzl,定义带编译参数选择能力的通用C++目标封装规则:
load("//toolchain:compiler_transition.bzl", "compiler_transition") def _cc_target_wrapper_impl(ctx): # 透传被切换配置的目标的所有构建产物 wrapped_target = ctx.attr.dep[0] return [wrapped_target[DefaultInfo], wrapped_target[CcInfo]] cc_target_with_compiler = rule( implementation = _cc_target_wrapper_impl, attrs = { "dep": attr.label( mandatory = True, providers = [CcInfo], ), "compiler_version": attr.string( default = "gcc9", values = ["gcc9", "gcc10"], ), # 过渡规则的Bazel内置白名单配置 "_whitelist_function_transition": attr.label( default = "@bazel_tools//tools/whitelists/function_transition_whitelist", ), }, cfg = compiler_transition, )
4. 按需求给目标绑定编译器
- 修改
lib/BUILD,先定义原始的动态库目标,再用封装规则包一层,指定用gcc9编译:
load("//toolchain:rules.bzl", "cc_target_with_compiler") # 原始动态库定义 cc_library( name = "cpplib_raw", srcs = ["lib.cpp"], hdrs = ["lib.h"], linkshared = True, ) # 绑定gcc9编译器的对外可依赖目标 cc_target_with_compiler( name = "cpplib", dep = ":cpplib_raw", compiler_version = "gcc9", )
- 修改
app/BUILD,先定义原始的可执行文件目标,再用封装规则包一层,指定用gcc10编译,并依赖前面的动态库目标:
load("//toolchain:rules.bzl", "cc_target_with_compiler") # 原始可执行文件定义 cc_binary( name = "main_raw", srcs = ["main.cpp"], deps = ["//lib:cpplib_raw"], ) # 绑定gcc10编译器的对外可执行目标 cc_target_with_compiler( name = "main", dep = ":main_raw", compiler_version = "gcc10", )
注意事项
- gcc9和gcc10编译的C目标存在ABI差异,如果动态库和可执行文件之间传递C类型(比如STL容器、自定义类对象),会出现运行时崩溃,建议跨编译器版本的动态库边界统一使用C接口。
- 因为Bazel的配置是按目标的依赖传递的,如果把lib的原始目标作为app的源码依赖,当app被切换到gcc10配置时,lib的源码也会跟着被gcc10重新编译一次。要实现严格的“lib固定用gcc9编译、app固定用gcc10编译”的效果,需要将lib编译为独立的动态库产物,通过
cc_import规则引入app的链接依赖,避免源码依赖的配置传递。
内容的提问来源于stack exchange,提问作者Eugene Kolesnikov
相关产品推荐
相关产品推荐

