Practical Bazel: Implementing compilation_mode in Rules
Practical Bazel bazel
Published: 2022-10-13
Practical Bazel: Implementing compilation_mode in Rules

As developers know, virtually all compilers support the notion of compilation mode, e.g.. whether to compile the code in debug or release mode. In Bazel, this concept is built natively into the build system itself. When compiling software using bazel build, one can select a compilation mode using the --compilation_mode (often shorted to -c) flag.

The compilation modes supported by Bazel are:

  • fastbuild means build as fast as possible: generate minimal debugging information (e.g. in gcc, build with -gmlt -Wl,-S), and don’t optimize. This is the default.
  • dbg means build with debugging enabled (e.g. in gcc, build with -g), so that you can use a debugger such as gdb
  • opt means build with optimization enabled and with assert() calls disabled (e.g. in gcc, build with -O2 -DNDEBUG).

These compilation modes can and should be respected by all custom rules so that a Bazel user can use a single bazel build -c command to compile any software in either fastbuild, dbg, or opt mode. In addition, a rule author can also implement an optional compilation mode attribute which can be used to override the mode on an individual target.

Here’s how to do so.

First, start with a basic build rule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# foo_binary: A build rule which compiles code in a hypothetical
# language "foo" into a binary

def _impl(ctx):
  # Rule implementation goes here

foo_binary = rule(
  attrs = { ... },
  implementation = _impl,
  ...
)

Next, add an attribute to the rule which a user can optionally set to set the compilation mode on a particular target:

1
2
3
4
5
6
7
8
9
foo_binary = rule(
  attrs = {
    "compilation_mode": attr.string(
      mandatory = False,
      values = ["dbg", "opt", "fastbuild"],
      doc = "The compilation mode to use for this target.  If not set, infer from --compilation_mode.",
    ),
  }
)

Next, in the rule implementation, determine the effective compilation mode as follows:

1
2
3
4
5
6
7
8
def _impl(ctx):
  # Determine the actual build configuration from the combination of
  # ctx.attr.compilation_mode and --compilation_mode
  if ctx.attr.compilation_mode:
    compilation_mode = ctx.attr.compilation_mode
  else:
    compilation_mode = ctx.var["COMPILATION_MODE"]
  # compilation_mode is one of dbg, opt, or fastbuild

A user of the rule can either specify the compilation mode on an individual target as follows:

1
2
3
4
5
6
7
foo_binary(
  name = "...",
  ...,
  # Always compile this target in optimized mode regardless of the
  # bazel build -c option
  compilation_mode = "opt",
)

Alternatively, they can affect all targets globally with the build -c option, as in bazel build -c opt //....