docs/versions/7.0.0/tutorials/ccp-toolchain-config.mdx
This tutorial uses an example scenario to describe how to configure C++
toolchains for a project. It's based on an
example C++ project
that builds error-free using clang.
In this tutorial you learn how to:
cc_toolchain so that Bazel can build the application
with clangbazel build --config=clang_config //main:hello-world on a Linux machineThis tutorial assumes you are on Linux and have successfully built
C++ applications and installed the appropriate tooling and libraries.
The tutorial uses clang version 9.0.1, which you can install on your system.
Set up your build environment as follows:
If you have not already done so, download and install Bazel 0.23 or later.
Download the example C++ project from GitHub and place it in an empty directory on your local machine.
Add the following cc_binary target to the main/BUILD file:
cc_binary(
name = "hello-world",
srcs = ["hello-world.cc"],
)
Create a .bazelrc file at the root of the workspace directory with the
following contents to enable the use of the --config flag:
# Use our custom-configured c++ toolchain.
build:clang_config --crosstool_top=//toolchain:clang_suite
# Use --cpu as a differentiator.
build:clang_config --cpu=k8
# Use the default Bazel C++ toolchain to build the tools used during the
# build.
build:clang_config --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
For an entry build:{config_name} --flag=value, the command line flag
--config={config_name} is associated with that particular flag. See
documentation for the flags used:
crosstool_top,
cpu and
host_crosstool_top.
When you build your target
with bazel build --config=clang_config //main:hello-world, Bazel uses your
custom toolchain from the
cc_toolchain_suite
//toolchain:clang_suite. The suite may list different
toolchains for different CPUs,
and that's why it is differentiated with the flag --cpu=k8.
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, so that these tools are built using that toolchain instead of the one created in this tutorial.
To configure the C++ toolchain, repeatedly build the application and eliminate each error one by one as described below.
Note: This tutorial assumes you're using Bazel 0.23 or later. If you're
using an older release of Bazel, look for the "Configuring CROSSTOOL" tutorial.
It also assumes clang version 9.0.1, although the details should only change
slightly between different versions of clang.
Run the build with the following command:
bazel build --config=clang_config //main:hello-world
Because you specified --crosstool_top=//toolchain:clang_suite in the
.bazelrc file, Bazel throws the following error:
No such package `toolchain`: BUILD file not found on package path.
In the workspace directory, create the toolchain directory for the package
and an empty BUILD file inside the toolchain directory.
Run the build again. Because the toolchain package does not yet define the
clang_suite target, Bazel throws the following error:
No such target '//toolchain:clang_suite': target 'clang_suite' not declared
in package 'toolchain' defined by .../toolchain/BUILD
In the toolchain/BUILD file, define an empty filegroup as follows:
package(default_visibility = ["//visibility:public"])
filegroup(name = "clang_suite")
Run the build again. Bazel throws the following error:
'//toolchain:clang_suite' does not have mandatory providers: 'ToolchainInfo'
Bazel discovered that the --crosstool_top flag points to a rule that
doesn't provide the necessary ToolchainInfo
provider. So you need to point --crosstool_top to a rule that does provide
ToolchainInfo - that is the cc_toolchain_suite rule. In the
toolchain/BUILD file, replace the empty filegroup with the following:
cc_toolchain_suite(
name = "clang_suite",
toolchains = {
"k8": ":k8_toolchain",
},
)
The toolchains attribute automatically maps the --cpu (and also
--compiler when specified) values to cc_toolchain. You have not yet
defined any cc_toolchain targets and Bazel will complain about that
shortly.
Run the build again. Bazel throws the following error:
Rule '//toolchain:k8_toolchain' does not exist
Now you need to define cc_toolchain targets for every value in the
cc_toolchain_suite.toolchains attribute. Add the following to the
toolchain/BUILD file:
filegroup(name = "empty")
cc_toolchain(
name = "k8_toolchain",
toolchain_identifier = "k8-toolchain",
toolchain_config = ":k8_toolchain_config",
all_files = ":empty",
compiler_files = ":empty",
dwp_files = ":empty",
linker_files = ":empty",
objcopy_files = ":empty",
strip_files = ":empty",
supports_param_files = 0,
)
Run the build again. Bazel throws the following error:
Rule '//toolchain:k8_toolchain_config' does not exist
Next, add a ":k8_toolchain_config" target to the toolchain/BUILD file:
filegroup(name = "k8_toolchain_config")
Run the build again. Bazel throws the following error:
'//toolchain:k8_toolchain_config' does not have mandatory providers:
'CcToolchainConfigInfo'
CcToolchainConfigInfo is a provider that you use to configure
your C++ toolchains. To fix this error, create a Starlark rule
that provides CcToolchainConfigInfo to Bazel by making a
toolchain/cc_toolchain_config.bzl file 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 provider
CcToolchainConfigInfo. To use the cc_toolchain_config rule, add a load
statement to toolchain/BUILD right below the package statement:
load(":cc_toolchain_config.bzl", "cc_toolchain_config")
And replace the "k8_toolchain_config" filegroup with a declaration of a
cc_toolchain_config rule:
cc_toolchain_config(name = "k8_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/clang and /usr/bin/ld are the correct paths
for your system.
Run the build again. Bazel throws the following error:
..../BUILD:3:1: undeclared inclusion(s) in rule '//main:hello-world':
this rule is missing dependency declarations for the following files included by 'main/hello-world.cc':
'/usr/include/c++/9/ctime'
'/usr/include/x86_64-linux-gnu/c++/9/bits/c++config.h'
'/usr/include/x86_64-linux-gnu/c++/9/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 includes attribute of
cc_binary, but here this is solved at the toolchain level with the
cxx_builtin_include_directories
parameter of cc_common.create_cc_toolchain_config_info. Beware that if
you are using a different version of clang, 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.bzl to look
like this:
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
cxx_builtin_include_directories = [ # NEW
"/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,
)
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 linkopts attribute of cc_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 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",
"flag_group",
"flag_set",
"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],
)
If you run bazel build --config=clang_config //main:hello-world, it should
finally build.
In this tutorial you learned how to configure a basic C++ toolchain, but toolchains are more powerful than this simple example.
The key take-aways are:
--crosstool_top flag in the command line which should
point to a cc_toolchain_suite.bazelrc
filecc_toolchains for different CPUs and
compilers. You can use command line flags like --cpu to differentiate.cc_toolchain with target dependencies on attributes, such as
compiler_files. The tool_paths would need to be changed as well.For more details, see C++ toolchain configuration