Get started with pw_toolchain_bazel#
pw_toolchain_bazel: A modular toolkit for declaring C/C++ toolchains in Bazel
Quick start#
The fastest way to get started using pw_toolchain_bazel
is to use Pigweed’s
upstream toolchains.
Enable required features in your project’s
//.bazelrc
file:# Required for new toolchain resolution API. build --incompatible_enable_cc_toolchain_resolution # Do not attempt to configure an autodetected (local) toolchain. common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
Configure
pw_toolchain_bazel
in your project’s//WORKSPACE
file:# Add Pigweed itself, as a submodule from `//third_party/pigweed`. # # TODO: b/300695111 - Support depending on Pigweed as a git_repository, # even if you use pw_toolchain. local_repository( name = "pigweed", path = "third_party/pigweed", ) local_repository( name = "pw_toolchain", path = "third_party/pigweed/pw_toolchain_bazel", ) # Set up CIPD. load( "@pigweed//pw_env_setup/bazel/cipd_setup:cipd_rules.bzl", "cipd_client_repository", "cipd_repository", ) cipd_client_repository() # Set up and register Pigweed's toolchains. load( "@pigweed//pw_toolchain:register_toolchains.bzl", "register_pigweed_cxx_toolchains" ) register_pigweed_cxx_toolchains()
And you’re done! You should now be able to compile for macOS, Linux, and ARM Cortex-M devices.
Overview#
This guide shows you how to use pw_toolchain_bazel
to assemble a fully
working toolchain. There are three core elements in a C/C++ toolchain in
Bazel:
The underlying tools used to perform compile and link actions.
Flag declarations that may or may not apply to a given toolchain configuration.
The final toolchain definition that binds tools and flag declarations together to produce working C/C++ compile and link commands.
This guide assumes you have a good grasp on writing Bazel build files, and also assumes you have a working understanding of what flags are typically passed to various compile and link tool invocations.
Adding Pigweed to your WORKSPACE#
Before you can use Pigweed and pw_toolchain_bazel
in your project, you must
register Pigweed in your //WORKSPACE
file:
# Add Pigweed itself, as a submodule from `//third_party/pigweed`.
#
# TODO: b/300695111 - Support depending on Pigweed as a git_repository,
# even if you use pw_toolchain.
local_repository(
name = "pigweed",
path = "third_party/pigweed",
)
local_repository(
name = "pw_toolchain",
path = "third_party/pigweed/pw_toolchain_bazel",
)
Note
b/300695111: You must add Pigweed
as a submodule to use Pigweed in a Bazel project. Pigweed does not yet work
when added as a http_repository
.
Configure .bazelrc#
To use this module’s toolchain rules, you must first add a couple
flags that tell Bazel how to find toolchain definitions. Bazel’s .bazelrc
lives at the root of your project, and is the source of truth for your
project-specific build flags that control Bazel’s behavior.
# Required for new toolchain resolution API.
build --incompatible_enable_cc_toolchain_resolution
# Do not attempt to configure an autodetected (local) toolchain. We vendor
# all our toolchains, and CI VMs may not have any local toolchain to detect.
common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
Assemble a tool suite#
The fastest way to get started is using a toolchain tool repository template.
pw_toolchain_bazel
provides pre-assembled templates for clang
and
arm-none-eabi-gcc
toolchains in the
@pw_toolchain//build_external
package. These build files can be attached to an external repository in your
WORKSPACE
file using the build_file
attribute of http_archive
,
git_repository
, or cipd_repository
.
# Declare a toolchain tool suite for Linux.
http_archive(
name = "linux_clang_toolchain",
build_file = "@pw_toolchain//build_external:llvm_clang_legacy.BUILD",
sha256 = "884ee67d647d77e58740c1e645649e29ae9e8a6fe87c1376be0f3a30f3cc9ab3",
strip_prefix = "clang+llvm-17.0.6-x86_64-linux-gnu-ubuntu-22.04",
url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.6/clang+llvm-17.0.6-x86_64-linux-gnu-ubuntu-22.04.tar.xz",
)
Create toolchain definition#
To set up a complete toolchain definition, you’ll need toolchain
and
pw_cc_toolchain
rules that serve as the core of your toolchain.
A simplified example is provided below.
load("@pw_toolchain//cc_toolchain:defs.bzl", "pw_cc_toolchain")
pw_cc_toolchain(
name = "host_toolchain",
action_configs = [
"@linux_clang_toolchain//:ar",
"@linux_clang_toolchain//:clang",
"@linux_clang_toolchain//:clang++",
"@linux_clang_toolchain//:lld",
"@linux_clang_toolchain//:llvm-cov",
"@linux_clang_toolchain//:llvm-objcopy",
"@linux_clang_toolchain//:llvm-objdump",
"@linux_clang_toolchain//:llvm-strip",
],
cxx_builtin_include_directories = [
"%package(@linux_clang_toolchain//)%/include/x86_64-unknown-linux-gnu/c++/v1",
"%package(@linux_clang_toolchain//)%/include/c++/v1",
"%package(@linux_clang_toolchain//)%/lib/clang/17/include",
],
toolchain_identifier = "host-linux-toolchain",
flag_sets = [
"@pw_toolchain//flag_sets:c++17",
"@pw_toolchain//flag_sets:debugging",
"@pw_toolchain//flag_sets:no_canonical_prefixes",
],
)
toolchain(
name = "host_cc_toolchain_linux",
# This is the list of constraints that must be satisfied for the suite of
# toolchain tools to be determined as runnable on the current machine.
exec_compatible_with = [
"@platforms//os:linux",
],
# This is the list of constraints that dictates compatibility of the final
# artifacts produced by this toolchain.
target_compatible_with = [
"@platforms//os:linux",
],
toolchain = ":host_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)
The toolchain
rule#
The toolchain
rule communicates to Bazel what kind of toolchains are
available, what environments the tools can run on, and what environment the
artifacts are intended for. A quick overview of the critical parts of this
rule are outlined below.
name
: The name of the toolchain rule. This is the label that you reference when registering a toolchain so Bazel knows it may use this toolchain.toolchain_type
: The language this toolchain is designed for. Today,pw_toolchain_bazel
only supports C/C++ toolchains via the@bazel_tools//tools/cpp:toolchain_type
type.exec_compatible_with
: What constraints must be satisfied for this toolchain to be compatible with the execution environment. In simpler terms, if the machine that is currently running the build is a Linux x86_64 machine, it can only use toolchains designed to run on that OS and architecture.exec_compatible_with
is what prevents a Linux machine from trying to compile using tools designed for a Windows machine and vice versa.target_compatible_with
: What constraints must be satisfied for this toolchain to be compatible with the targeted environment. Rather than specifying whether the tools are compatible, this specifies the compatibility of the final artifacts produced by this toolchain. For example,target_compatible_with
is what tells Bazel that a toolchain is building firmware for a Cortex-M4.toolchain
: The rule that implements the toolchain behavior. When usingpw_toolchain_bazel
, this points to apw_cc_toolchain
rule. Multipletoolchain
rules can point to the samepw_cc_toolchain
, which can be useful for creating parameterized toolchains that have a lot in common.
The pw_cc_toolchain
rule#
This is the heart of your C/C++ toolchain configuration, and has two main configuration surfaces of interest.
pw_cc_toolchain.action_configs
: This is a list of bindings that map various toolchain actions to the appropriate tools. Typically you’ll just want to list all of thepw_cc_action_config
rules included in your toolchain repository from Assemble a tool suite. If you need to swap out a particular tool, you can just create a custompw_cc_tool
andpw_cc_action_config
and list it here.pw_cc_toolchain.flag_sets
: This lists all the flags that are applied when compiling with this toolchain. Eachpw_cc_flag_set
listed here includes at least one flag that applies to at least one kind of action.
While the other attributes of a pw_cc_toolchain
are still required,
their behaviors are less interesting from a configuration perspective and are
required for correctness and completeness reasons. See the full
API reference for pw_cc_toolchain
for more information.
Register your toolchain#
Once you’ve declared a complete toolchain to your liking, you’ll need to
register it in your project’s WORKSPACE
file so Bazel knows it can use the
new toolchain. An example for a toolchain
with the name
host_cc_toolchain_linux
living in //toolchains/BUILD
is illustrated
below.
register_toolchains(
"//toolchains:host_cc_toolchain_linux",
)
At this point, you should have a custom, working toolchain! For more extensive examples, consider taking a look at Pigweed’s fully instantiated and supported toolchains
Customize behavior with flag sets#
Now that your toolchain is working, you can customize it by introducing new flag sets.
Configure warnings#
Enabling compiler warnings and setting them to be treated as errors is a great way to prevent unintentional bugs that stem from dubious code.
load(
"@pw_toolchain//cc_toolchain:defs.bzl",
"pw_cc_flag_set",
)
pw_cc_flag_set(
name = "warnings",
actions = [
"@pw_toolchain//actions:all_c_compiler_actions",
"@pw_toolchain//actions:all_cpp_compiler_actions",
],
flags = [
"-Wall",
"-Wextra",
"-Werror", # Make all warnings errors, except for the exemptions below.
"-Wno-error=cpp", # preprocessor #warning statement
"-Wno-error=deprecated-declarations", # [[deprecated]] attribute
],
)
Omit unreferenced symbols#
If a function, variable, or data section isn’t used anywhere in your binaries, it can be omitted with the following flag sets.
load(
"@pw_toolchain//cc_toolchain:defs.bzl",
"pw_cc_flag_set",
)
# Treats symbols representing functions and data as individual sections.
# This is mostly relevant when using `:omit_unused_sections`.
pw_cc_flag_set(
name = "function_and_data_sections",
actions = [
"@pw_toolchain//actions:all_c_compiler_actions",
"@pw_toolchain//actions:all_cpp_compiler_actions",
],
flags = [
"-ffunction-sections",
"-fdata-sections",
],
)
pw_cc_flag_set(
name = "omit_unused_sections",
actions = ["@pw_toolchain//actions:all_link_actions"],
# This flag is parameterized by operating system. macOS and iOS require
# a different flag to express this concept.
flags = select({
"@platforms//os:macos": ["-Wl,-dead_strip"],
"@platforms//os:ios": ["-Wl,-dead_strip"],
"//conditions:default": ["-Wl,--gc-sections"],
}),
)
Set global defines#
Toolchains may declare preprocessor defines that are available for all compile actions.
load(
"["@pw_toolchain//cc_toolchain:defs.bzl"]",
"pw_cc_flag_set",
)
# Specify global defines that should be available to all compile actions.
pw_cc_flag_set(
name = "global_defines",
actions = [
"@pw_toolchain//actions:all_asm_compiler_actions",
"@pw_toolchain//actions:all_c_compiler_actions",
"@pw_toolchain//actions:all_cpp_compiler_actions",
],
flags = [
"-DPW_LOG_LEVEL=PW_LOG_LEVEL_INFO", # Omit all debug logs.
],
)
Bind custom flags to a toolchain#
After you’ve assembled a selection of custom flag sets, you can bind them to
your toolchain definition by listing them in
pw_cc_toolchain.flag_sets
:
pw_cc_toolchain(
name = "host_toolchain",
action_configs = [
"@linux_clang_toolchain//:ar",
"@linux_clang_toolchain//:clang",
"@linux_clang_toolchain//:clang++",
...
flag_sets = [
"@pw_toolchain//flag_sets:c++17",
"@pw_toolchain//flag_sets:debugging",
"@pw_toolchain//flag_sets:no_canonical_prefixes",
":warnings", # Newly added pw_cc_flag_set from above.
":function_and_data_sections", # Newly added pw_cc_flag_set from above.
":omit_unused_sections", # Newly added pw_cc_flag_set from above.
":global_defines", # Newly added pw_cc_flag_set from above.
],
)
Note
Flags appear in the tool invocations in the order as they are listed
in pw_cc_toolchain.flag_sets
, so if you need a flag
to appear earlier in the command-line invocation of the tool just move it to
towards the beginning of the list.
You can use pw_cc_flag_set
rules to add support for new architectures,
enable/disable warnings, add preprocessor defines, enable LTO, and more.