Bazel build configurations

A build configuration is a set of settings, like cpu = "arm64", where each setting assigns a configuration value to a known configuration variable that Bazel knows about. These values affect how Bazel builds things.

Build configurations can be seen as immutable dictionaries mapping variable names (string keys) to configuration values (booleans, integers, strings, list of strings, label or list of labels).

They are only meaningful during the Bazel analysis phase, and are identified internally by a unique hash of their content. They do not have a label, and cannot appear directly in Bazel build files or Starlark code.

Each build configuration instance has its own output directory under the Bazel output_base, i.e.:

${output_base}/execroot/<workspace_name>/bazel-out/<config_dir>/bin/

The build artifacts of targets evaluated in the context of the build configuration are stored there. The <config_dir> value depends on the content of the build configuration. Its computation is a Bazel implementation detail, that can actually vary between Bazel releases.

In the simplest cases, <config_dir> would look like ${cpu}-${compilation_mode}, for example k8-fastbuild1 or aarch64-dbg, but can become much more complex very easily.

Some example config_dir values for the build configurations of a small but non-trivial example Bazel project:

  • k8-fastbuild
  • k8-fastbuild-ST-6f89e1bee3ea
  • k8-fastbuild-ST-bd2abcc18995
  • k8-fastbuild-ST-ec22d3eeb595
  • k8-opt
  • k8-opt-exec-2B5CBBC6
  • k8-opt-exec-2B5CBBC6-ST-5934bb4653e4

Note that the hexadecimal values above are not related to the configuration's ID (i.e. its unique hash).

Common build configurations

By default, Bazel manages two configurations:

  • The default configuration, also known ambiguously as the target configuration, because it corresponds to build artifacts that must run on the final "target" system.

    Its settings match the host system by default, but can be changed with command-line options (see below).

  • The host configuration, which corresponds to code that must run on the machine where Bazel runs. This contains the same settings as the default configuration, except when cross-compiling (e.g. with --cpu=<name>).

There is also:

  • The exec configuration, which corresponds to code that runs during build actions. In practice, this contains the same settings as the host one, except when using remote builders.

A Bazel project can also define extra configurations to accommodate certain use cases, for example to cross-compile for more than one target architecture in a single build invocation.

Native configuration variables

The bazel build command supports a large number of command-line options that change native configuration variables (a.k.a. native settings), for example:

  • --cpu=<name> and --host_cpu=<name>:

    Change the CPU architecture of the code generated for the default or host configuration, respectively. For example:

    bazel build --cpu=arm64 --host_cpu=x86_64 ...
  • -c <mode>, --compilation_mode=<mode> or --host_compilation_mode=<mode>:

    Where <mode> is one of "fastbuild", "dbg" or "opt", which describe a set of compiler linker flags. For example:

    bazel build -c opt --host_compilation_mode=dbg ...
  • --crosstool=<file_path> or --host_crosstool=<file_path>:

    Point to a CROSSTOOL file used to describe a custom C++ toolchain to Bazel, for compiling and linking either target or host binaries.

There is a very large set of native configuration variables (and related command-line flags). For example Bazel 5.2.0 supports more than 340 ones (including 83 experimental ones).

Custom configuration flags (deprecated)

It is possible to define custom configuration variables (confusingly named custom configuration flags in the Bazel documentation), that will be set on the command-line with "--define <name>=<value>", where <name> is an arbitrary build configuration variable name, and <value> is recorded as an arbitrary string (the value is never interpreted).

These names can be tested in BUILD.bazel files using special constructs, for example:

# This represents a named configuration condition which will be True whenever
# `--define foo=bar` is used in the `bazel build` command-line.
config_setting(
  name = "is_foo_bar",
  define_flags = {
    "foo": "bar",
  }
)

User-defined build settings

To overcome the limitations of custom --define flags, Bazel projects can now define build configuration variables that:

  • Are named with a label (which provides workspace and package scoping, plus availability checks).

  • Can have a specific type, one of: boolean, integer, string, string list, label and label list.

  • Always provide a default value.

These can be set on the command line simply with "--<label>=<value>" as in:

# Set the build setting named enable_logs defined in
# $WORKSPACE/my_settings/BUILD.bazel to True.
#
# Note that the double-slash must follow the double caret directly
# without spaces between them.
bazel build --//my_settings:enable_logs=True 

Which matches a definition that must appear in the file $PROJECT/my_settings/BUILD.bazel, e.g.:

# From my_settings/BUILD.bazel
load("@bazel_skylib//rules/common_settings.bzl", "bool_flag")

bool_flag(
  name = "enable_logs",
  default_value = False
)

The build setting can also be defined in an external repository, as in:

# Set the build setting named enable_logs defined in the config/BUILD.bazel
# file of the @project_settings external repository.
bazel build --@project_settings//config:enable_logs=True 

Storing user configuration values

Instead of passing them directly on the command-line, arguments that set configuration values can be stored in the .bazelrc file, located at the top of the project's workspace directory, as in:

# from $PROJECT/.bazelrc
build --host_copt=-O3 -s
build --copt=-O1 -g -Wall
build --//my_settings:enable_logs=True

When calling bazel build <target>, the options provided by the .bazelrc are automatically included, resulting in a command that would be equivalent to:

bazel build --host_copy="-O3 -s" --copt="-O1 -g -Wall" --//my_settings:enable_logs=True <target>

It is also possible to group several definitions with a custom name as in:

# from $PROJECT/.bazelrc
build:conf_x64 --cpu=x86_64
build:conf_x64 --copt=-mavx2

build:conf_arm64 --cpu=aarch64
build:conf_arm64 --copt=-marmv8.1-a+simd

Then use --config=<name> to select all options from one group, as in:

bazel build --config=conf_x64 <target>

Which will be equivalent to:

bazel build --cpu=x86_64 --copt=-mavx2 <target>

It is possible to combine --config with other options as well, as in:

bazel build --config=conf_arm64 --copy="-DENABLE_ASSERTS=1" ...

===

IMPORTANT: Despite its name, the --config option does not create a new build configuration. This is just a convenient way to group command-line options for the user. Each <name> is simply recursively expanded to its

canonical constituents, then completely ignored by Bazel.

Configuration transitions (quick overview)

Besides the standard target, host and exec configurations, Bazel supports the creation of extra build configurations, using the notion of transitions.

Transitions are an advanced feature that won't be detailed here, but it is important to understand that:

  • Build configurations are conceptually just dictionaries which map configuration variable names to configuration values. E.g.:

    {
       "copt": "-O2 -g",
       "cpu": "x86_64",
       "compiler": "gcc",
       "//my_settings:enable_logs": True,
       
    }
    
  • A transition provides a function which takes the current build configuration as input, and returns a new build configuration as output.

  • A transition function can only modify the values in the dictionary, i.e. it cannot add or remove keys from the input.

  • Transition functions are called during the analysis phase (explained later), under specific conditions, and cannot be triggered directly by the user on the command-line or the .bazelrc file.

Inspecting build configurations

After a Bazel build command, it is possible to look at all the configurations that were needed. Use the bazel config command, to print a list of all configurations, and their config_dir value, e.g.:

$ bazel clean
$ bazel build //src:program
$ bazel config
Available configurations:
20222e2616387f00ae3814d79f82a650829d1aea962d558edda5cf54c459f4a2 k8-fastbuild
30f5a27e1bd054836d97b800bdfe61c6d1cffccdf6b82c9963e94020e3852026 k8-fastbuild-ST-bd2abcc18995
34e198ac81dafe34b82cf62466cc84f5b1af6ef8dce44b9283b3152a6635f64a k8-fastbuild-ST-6f89e1bee3ea
aaa26904110821b8e0f87aabfffcfb8e5003a5620aee74de3d7ddea9a654c0f6 k8-opt (host)
d6b1a93e6619d845f29ee752e338ec2636e7fbf4c88e50168602cc21f5ae6f13 k8-opt-exec-2B5CBBC6-ST-5934bb4653e4 (exec)

NOTE: configuration information accumulates until the next bazel clean.

Use bazel config <id> to dump the configuration to stdout to inspect its values. WARNING: The output is very long.

$ bazel config 20222e2616387f00ae3814d79f82a650829d1aea962d558edda5cf54c459f4a2 | wc -l
Loading:
INFO: Displaying config with id 20222e2616387f00ae3814d79f82a650829d1aea962d558edda5cf54c459f4a2
436

Link to the real output here.

Note that in practice, each configuration is a set of dictionaries, each one covering a specific topic. These are called fragments, and only a few of them can be accessed directly from Starlark:

$ bazel config 20222e2616387f00ae3814d79f82a650829d1aea962d558edda5cf54c459f4a2 | grep FragmentOptions
FragmentOptions com.google.devtools.build.lib.analysis.PlatformOptions {
FragmentOptions com.google.devtools.build.lib.analysis.ShellConfiguration$Options {
FragmentOptions com.google.devtools.build.lib.analysis.config.CoreOptions {
FragmentOptions com.google.devtools.build.lib.analysis.test.CoverageConfiguration$CoverageOptions {
FragmentOptions com.google.devtools.build.lib.analysis.test.TestConfiguration$TestOptions {
FragmentOptions com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider$StrictActionEnvOptions {
FragmentOptions com.google.devtools.build.lib.bazel.rules.python.BazelPythonConfiguration$Options {
FragmentOptions com.google.devtools.build.lib.rules.android.AndroidConfiguration$Options {

  1. k8 is an old naming convention for what is now called the x86_64 CPU architecture.