It’s fairly common to need to alter your builds based on a command-line flag. Maybe you want to change the distribution target for a mobile app or enable additional debug functionality. An easy way to do that is via a custom command-line flag.
This is a topic where Bazel newcomers often struggle, so let’s explore how to create custom flags in Bazel.
Build Settings
A build setting is a rule—just like any other rule—but with additional capabilities. Specifically, build settings allow us to define custom command-line flags that influence the build configuration.
Pre-defined Settings
Because build settings are rules, we can write our own. However, bazel-skylib provides several commonly used build settings out of the box.
You can see the full list in the skylib repository.
For simplicity, we’ll use a pre-defined setting in this article. Keep in mind, though, that you’re not limited to what skylib offers—you can implement your own build setting if needed.
Creating a Flag
Suppose we want to build differently for our local development environment versus what we release to the App Store. We’ll call this a flavor, with two variants:
devstore
To create a flag, we need to instantiate a build setting in BUILD.bazel:
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
string_flag(
name = "flavor",
values = ["dev", "store"],
build_setting_default = "dev",
)
Because this is a target, it has a label. That’s why we pass it on the command line using label syntax:
bazel build //:my_target --//:flavor=store
However, defining the flag alone is not enough.
Reacting to Build Settings
Bazel does not branch directly on build settings. Instead, it evaluates configuration conditions, represented by config_setting targets.
So we need to associate our flag with configuration conditions:
config_setting(
name = "dev",
flag_values = {":flavor": "dev"},
visibility = ["//visibility:public"],
)
config_setting(
name = "store",
flag_values = {":flavor": "store"},
visibility = ["//visibility:public"],
)
Here’s the mental model:
string_flagdefines a configurable value.config_settingdefines a configuration condition.select()switches onconfig_setting.
This separation is important: select() operates on config_setting targets, not directly on flags.
Using select()
Now that we have configuration conditions, we can branch using select().
To demonstrate that our flag works, we’ll create a simple genrule that writes which flavor was selected:
genrule(
name = "which_flavor",
outs = ["output.txt"],
cmd = select({
":dev": "echo dev > $@",
":store": "echo store > $@",
}),
)
Now build the target:
bazel build :which_flavor --//:flavor=store
You should see something like this:
INFO: Analyzed target //:which_flavor (6 packages loaded, 10 targets configured).
INFO: Found 1 target...
Target //:which_flavor up-to-date:
bazel-bin/output.txt
INFO: Elapsed time: 0.297s, Critical Path: 0.02s
INFO: 2 processes: 1 internal, 1 darwin-sandbox.
INFO: Build completed successfully, 2 total actions
Opening bazel-bin/output.txt will reveal:
store
Closing Thoughts
To create a custom command-line flag in Bazel, remember:
- You need a build setting (
string_flag,bool_flag, etc.). - You need one or more
config_settingtargets that describe configuration conditions. - You use
select()to branch on those configuration conditions.
If you’re not writing custom rules, using pre-defined settings from skylib is probably the right approach in most cases. If you need more flexibility, you can write your own build setting rule—but that’s a topic for another day.