This tutorial uses an example scenario to describe how to configure C++ toolchains for a project.
What you'll learn
In this tutorial you learn how to:
- Set up the build environment
- Use
--toolchain_resolution_debugto debug toolchain resolution - Configure the C++ toolchain
- Create a Starlark rule that provides additional configuration for the
cc_toolchainso that Bazel can build the application withclang - Build the C++ binary for by running
bazel build //main:hello-worldon a Linux machine - Cross-compile the binary for android by running
bazel build //main:hello-world --platforms=//:android_x86_64
Before you begin
This tutorial assumes you are on Linux and have successfully built C++
applications and installed the appropriate tooling and libraries. The tutorial
uses clang version 16, which you can install on your system.
Set up the build environment
Set up your build environment as follows:
If you have not already done so, download and install Bazel 7.0.2 or later.
Add an empty
MODULE.bazelfile at the root folder.Add the following
cc_binarytarget to themain/BUILDfile:cc_binary( name = "hello-world", srcs = ["hello-world.cc"], )Because Bazel uses many internal tools written in C++ during the build, such as
process-wrapper, the pre-existing default C++ toolchain is specified for the host platform. This enables these internal tools to build using that toolchain of the one created in this tutorial. Hence, thecc_binarytarget is also built with the default toolchain.Run the build with the following command:
bazel build //main:hello-worldThe build succeeds without any toolchain registered in
MODULE.bazel.To further see what's under the hood, run:
bazel build //main:hello-world --toolchain_resolution_debug='@bazel_tools//tools/cpp:toolchain_type' INFO: ToolchainResolution: Target platform @@platforms//host:host: Selected execution platform @@platforms//host:host, type @@bazel_tools//tools/cpp:toolchain_type -> toolchain @@bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8Without specifying
--platforms, Bazel builds the target for@platforms//hostusing@bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8.
Configure the C++ toolchain
To configure the C++ toolchain, repeatedly build the application and eliminate each error one by one as described as following.
It also assumes clang version 9.0.1, although the details should only change
slightly between different versions of clang.
Add
toolchain/BUILDwithfilegroup(name = "empty") cc_toolchain( name = "linux_x86_64_toolchain", toolchain_identifier = "linux_x86_64-toolchain", toolchain_config = ":linux_x86_64_toolchain_config", all_files = ":empty", compiler_files = ":empty", dwp_files = ":empty", linker_files = ":empty", objcopy_files = ":empty", strip_files = ":empty", supports_param_files = 0, ) toolchain( name = "cc_toolchain_for_linux_x86_64", toolchain = ":linux_x86_64_toolchain", toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", exec_compatible_with = [ "@platforms//cpu:x86_64", "@platforms//os:linux", ], target_compatible_with = [ "@platforms//cpu:x86_64", "@platforms//os:linux", ], )Then add appropriate dependencies and register the toolchain with
MODULE.bazelwithbazel_dep(name = "platforms", version = "0.0.10") register_toolchains( "//toolchain:cc_toolchain_for_linux_x86_64" )This step defines a
cc_toolchainand binds it to atoolchaintarget for the host configuration.Run the build again. Because the
toolchainpackage doesn't yet define thelinux_x86_64_toolchain_configtarget, Bazel throws the following error:ERROR: toolchain/BUILD:4:13: in toolchain_config attribute of cc_toolchain rule //toolchain:linux_x86_64_toolchain: rule '//toolchain:linux_x86_64_toolchain_config' does not exist.In the
toolchain/BUILDfile, define an empty filegroup as follows:package(default_visibility = ["//visibility:public"]) filegroup(name = "linux_x86_64_toolchain_config")Run the build again. Bazel throws the following error:
'//toolchain:linux_x86_64_toolchain_config' does not have mandatory providers: 'CcToolchainConfigInfo'.CcToolchainConfigInfois a provider that you use to configure your C++ toolchains. To fix this error, create a Starlark rule that providesCcToolchainConfigInfoto Bazel by making atoolchain/cc_toolchain_config.bzlfile with the following content:def _impl(ctx): return cc_common.create_cc_toolchain_config_info( ctx = ctx, toolchain_identifier = "k8-toolchain", host_system_name = "local", target_system_name = "local", target_cpu = "k8", target_libc = "unknown", compiler = "clang", abi_version = "unknown", abi_libc_version = "unknown", ) cc_toolchain_config = rule( implementation = _impl, attrs = {}, provides = [CcToolchainConfigInfo], )cc_common.create_cc_toolchain_config_info()creates the needed providerCcToolchainConfigInfo. To use thecc_toolchain_configrule, add a load statement totoolchain/BUILDright below the package statement:load(":cc_toolchain_config.bzl", "cc_toolchain_config")And replace the "linux_x86_64_toolchain_config" filegroup with a declaration of a
cc_toolchain_configrule:cc_toolchain_config(name = "linux_x86_64_toolchain_config")Run the build again. Bazel throws the following error:
.../BUILD:1:1: C++ compilation of rule '//:hello-world' failed (Exit 1) src/main/tools/linux-sandbox-pid1.cc:421: "execvp(toolchain/DUMMY_GCC_TOOL, 0x11f20e0)": No such file or directory Target //:hello-world failed to build`At this point, Bazel has enough information to attempt building the code but it still does not know what tools to use to complete the required build actions. You will modify the Starlark rule implementation to tell Bazel what tools to use. For that, you need the
tool_path()constructor from@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl:# toolchain/cc_toolchain_config.bzl: # NEW load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "tool_path") def _impl(ctx): tool_paths = [ # NEW tool_path( name = "gcc", path = "/usr/bin/clang", ), tool_path( name = "ld", path = "/usr/bin/ld", ), tool_path( name = "ar", path = "/usr/bin/ar", ), tool_path( name = "cpp", path = "/bin/false", ), tool_path( name = "gcov", path = "/bin/false", ), tool_path( name = "nm", path = "/bin/false", ), tool_path( name = "objdump", path = "/bin/false", ), tool_path( name = "strip", path = "/bin/false", ), ] return cc_common.create_cc_toolchain_config_info( ctx = ctx, toolchain_identifier = "local", host_system_name = "local", target_system_name = "local", target_cpu = "k8", target_libc = "unknown", compiler = "clang", abi_version = "unknown", abi_libc_version = "unknown", tool_paths = tool_paths, # NEW )Make sure that
/usr/bin/clangand/usr/bin/ldare the correct paths for your system.Run the build again. Bazel throws the following error:
ERROR: main/BUILD:3:10: Compiling main/hello-world.cc failed: absolute path inclusion(s) found in rule '//main:hello-world': the source file 'main/hello-world.cc' includes the following non-builtin files with absolute paths (if these are builtin files, make sure these paths are in your toolchain): '/usr/include/c++/13/ctime' '/usr/include/x86_64-linux-gnu/c++/13/bits/c++config.h' '/usr/include/x86_64-linux-gnu/c++/13/bits/os_defines.h' ...Bazel needs to know where to search for included headers. There are multiple ways to solve this, such as using the
includesattribute ofcc_binary, but here this is solved at the toolchain level with thecxx_builtin_include_directoriesparameter ofcc_common.create_cc_toolchain_config_info. Beware that if you are using a different version ofclang, the include path will be different. These paths may also be different depending on the distribution.Modify the return value in
toolchain/cc_toolchain_config.bzlto look like this:return cc_common.create_cc_toolchain_config_info( ctx = ctx, cxx_builtin_include_directories = [ # NEW "/usr/lib/llvm-16/lib/clang/16/include", "/usr/include", ], toolchain_identifier = "local", host_system_name = "local", target_system_name = "local", target_cpu = "k8", target_libc = "unknown", compiler = "clang", abi_version = "unknown", abi_libc_version = "unknown", tool_paths = tool_paths, )Run the build command again, you will see an error like:
/usr/bin/ld: bazel-out/k8-fastbuild/bin/main/_objs/hello-world/hello-world.o: in function `print_localtime()': hello-world.cc:(.text+0x68): undefined reference to `std::cout'The reason for this is because the linker is missing the C++ standard library and it can't find its symbols. There are many ways to solve this, such as using the
linkoptsattribute ofcc_binary. Here it is solved by making sure that any target using the toolchain doesn't have to specify this flag.Copy the following code to
toolchain/cc_toolchain_config.bzl:# NEW load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") # NEW load( "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "feature", # NEW "flag_group", # NEW "flag_set", # NEW "tool_path", ) all_link_actions = [ # NEW ACTION_NAMES.cpp_link_executable, ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, ] def _impl(ctx): tool_paths = [ tool_path( name = "gcc", path = "/usr/bin/clang", ), tool_path( name = "ld", path = "/usr/bin/ld", ), tool_path( name = "ar", path = "/bin/false", ), tool_path( name = "cpp", path = "/bin/false", ), tool_path( name = "gcov", path = "/bin/false", ), tool_path( name = "nm", path = "/bin/false", ), tool_path( name = "objdump", path = "/bin/false", ), tool_path( name = "strip", path = "/bin/false", ), ] features = [ # NEW feature( name = "default_linker_flags", enabled = True, flag_sets = [ flag_set( actions = all_link_actions, flag_groups = ([ flag_group( flags = [ "-lstdc++", ], ), ]), ), ], ), ] return cc_common.create_cc_toolchain_config_info( ctx = ctx, features = features, # NEW cxx_builtin_include_directories = [ "/usr/lib/llvm-9/lib/clang/9.0.1/include", "/usr/include", ], toolchain_identifier = "local", host_system_name = "local", target_system_name = "local", target_cpu = "k8", target_libc = "unknown", compiler = "clang", abi_version = "unknown", abi_libc_version = "unknown", tool_paths = tool_paths, ) cc_toolchain_config = rule( implementation = _impl, attrs = {}, provides = [CcToolchainConfigInfo], )Running
bazel build //main:hello-world, it should finally build the binary successfully for host.In
toolchain/BUILD, copy thecc_toolchain_config,cc_toolchain, andtoolchaintargets and replacelinux_x86_64withandroid_x86_64in target names.In
MODULE.bazel, register the toolchain for androidregister_toolchains( "//toolchain:cc_toolchain_for_linux_x86_64", "//toolchain:cc_toolchain_for_android_x86_64" )Run
bazel build //main:hello-world --android_platforms=//toolchain:android_x86_64to build the binary for Android.
In practice, Linux and Android should have different C++ toolchain configs. You
can either modify the existing cc_toolchain_config for the differences or
create a separate rules (i.e. CcToolchainConfigInfo provider) for separate
platforms.
Review your work
In this tutorial you learned how to configure a basic C++ toolchain, but toolchains are more powerful than this example.
The key takeaways are:
- You need to specify a matching
platformsflag in the command line for Bazel to resolve to the toolchain for the same constraint values on the platform. The documentation holds more information about language specific configuration flags. - You have to let the toolchain know where the tools live. In this tutorial
there is a simplified version where you access the tools from the system. If
you are interested in a more self-contained approach, you can read about
external dependencies. Your tools could come from a
different module and you would have to make their files available to the
cc_toolchainwith target dependencies on attributes, such ascompiler_files. Thetool_pathswould need to be changed as well. - You can create features to customize which flags should be passed to different actions, be it linking or any other type of action.
Further reading
For more details, see C++ toolchain configuration