select(), is a Bazel feature that lets users toggle the values
of build rule attributes at the command line.
This can be used, for example, for a multiplatform library that automatically
chooses the appropriate implementation for the architecture, or for a
feature-configurable binary that can be customized at build time.
Example
cc_binary that “chooses” its deps based on the flags at the
command line. Specifically, deps becomes:
| Command | deps |
|---|---|
bazel build //myapp:mybinary --cpu=arm | [":arm_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=x86 | [":x86_dev_lib"] |
bazel build //myapp:mybinary --cpu=ppc | [":generic_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=ppc | [":generic_lib"] |
select() serves as a placeholder for a value that will be chosen based on
configuration conditions, which are labels referencing config_setting
targets. By using select() in a configurable attribute, the attribute
effectively adopts different values when different conditions hold.
Matches must be unambiguous: if multiple conditions match then either
- They all resolve to the same value. For example, when running on linux x86, this is unambiguous
{"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}because both branches resolve to “hello”. - One’s
valuesis a strict superset of all others’. For example,values = {"cpu": "x86", "compilation_mode": "dbg"}is an unambiguous specialization ofvalues = {"cpu": "x86"}.
//conditions:default automatically matches when
nothing else does.
While this example uses deps, select() works just as well on srcs,
resources, cmd, and most other attributes. Only a small number of attributes
are non-configurable, and these are clearly annotated. For example,
config_setting’s own
values attribute is non-configurable.
select() and dependencies
Certain attributes change the build parameters for all transitive dependencies
under a target. For example, genrule’s tools changes --cpu to the CPU of
the machine running Bazel (which, thanks to cross-compilation, may be different
than the CPU the target is built for). This is known as a
configuration transition.
Given
x86 developer machine binds the build to g_arm.src, tool1, and
x86tool.cc. Both of the selects attached to my_genrule use my_genrule’s
build parameters, which include --cpu=arm. The tools attribute changes
--cpu to x86 for tool1 and its transitive dependencies. The select on
tool1 uses tool1’s build parameters, which include --cpu=x86.
Configuration conditions
Each key in a configurable attribute is a label reference to aconfig_setting or
constraint_value.
config_setting is just a collection of
expected command line flag settings. By encapsulating these in a target, it’s
easy to maintain “standard” conditions users can reference from multiple places.
constraint_value provides support for multi-platform behavior.
Built-in flags
Flags like--cpu are built into Bazel: the build tool natively understands
them for all builds in all projects. These are specified with
config_setting’s
values attribute:
flagN is a flag name (without --, so "cpu" instead of "--cpu"). valueN
is the expected value for that flag. :meaningful_condition_name matches if
every entry in values matches. Order is irrelevant.
valueN is parsed as if it was set on the command line. This means:
values = { "compilation_mode": "opt" }matchesbazel build -c optvalues = { "force_pic": "true" }matchesbazel build --force_pic=1values = { "force_pic": "0" }matchesbazel build --noforce_pic
config_setting only supports flags that affect target behavior. For example,
--show_progress isn’t allowed because
it only affects how Bazel reports progress to the user. Targets can’t use that
flag to construct their results. The exact set of supported flags isn’t
documented. In practice, most flags that “make sense” work.
Custom flags
You can model your own project-specific flags with Starlark build settings. Unlike built-in flags, these are defined as build targets, so Bazel references them with target labels. These are triggered withconfig_setting’s
flag_values
attribute:
--define
is an alternative legacy syntax for custom flags (for example
--define foo=bar). This can be expressed either in the
values attribute
(values = {"define": "foo=bar"}) or the
define_values attribute
(define_values = {"foo": "bar"}). --define is only supported for backwards
compatibility. Prefer Starlark build settings whenever possible.
values, flag_values, and define_values evaluate independently. The
config_setting matches if all values across all of them match.
The default condition
The built-in condition//conditions:default matches when no other condition
matches.
Because of the “exactly one match” rule, a configurable attribute with no match
and no default condition emits a "no matching conditions" error. This can
protect against silent failures from unexpected settings:
select()’s
no_match_error attribute.
Platforms
While the ability to specify multiple flags on the command line provides flexibility, it can also be burdensome to individually set each one every time you want to build a target. Platforms let you consolidate these into simple bundles.config_settings that contain a subset of the platform’s constraint_values,
allowing those config_settings to match in select() expressions.
For example, in order to set the srcs attribute of my_rocks to calcite.sh,
you can simply run
select() can also directly read constraint_values:
config_settings when you only need to
check against single values.
Platforms are still under development. See the
documentation for details.
Combining select()s
select can appear multiple times in the same attribute:
selects values:
- Duplicate labels can appear in different paths of the same
select. - Duplicate labels can not appear within the same path of a
select. - Duplicate labels can not appear across multiple combined
selects (no matter what path)
select cannot appear inside another select. If you need to nest selects
and your attribute takes other targets as values, use an intermediate target:
select to match when multiple conditions match, consider AND
chaining.
OR chaining
Consider the following:[":standard_lib"] multiple
times.
One option is to predefine the value as a BUILD variable:
selects.with_or
The
with_or
macro in Skylib’s
selects
module supports ORing conditions directly inside a select:
selects.config_setting_group
The
config_setting_group
macro in Skylib’s
selects
module supports ORing multiple config_settings:
selects.with_or, different targets can share :config1_or_2 across
different attributes.
It’s an error for multiple conditions to match unless one is an unambiguous
“specialization” of the others or they all resolve to the same value. See here for details.
AND chaining
If you need aselect branch to match when multiple conditions match, use the
Skylib macro
config_setting_group:
config_settings can’t be directly ANDed
inside a select. You have to explicitly wrap them in a config_setting_group.
Custom error messages
By default, when no condition matches, the target theselect() is attached to
fails with the error:
no_match_error
attribute:
Rules compatibility
Rule implementations receive the resolved values of configurable attributes. For example, given:ctx.attr.some_attr as [":foo"].
Macros can accept select() clauses and pass them through to native
rules. But they cannot directly manipulate them. For example, there’s no way
for a macro to convert
select will choose cannot work
because macros are evaluated in Bazel’s loading phase,
which occurs before flag values are known.
This is a core Bazel design restriction that’s unlikely to change any time soon.
Second, macros that just need to iterate over all select paths, while
technically feasible, lack a coherent UI. Further design is necessary to change
this.
Bazel query and cquery
Bazelquery operates over Bazel’s
loading phase.
This means it doesn’t know what command line flags a target uses since those
flags aren’t evaluated until later in the build (in the
analysis phase).
So it can’t determine which select() branches are chosen.
Bazel cquery operates after Bazel’s analysis phase, so it has
all this information and can accurately resolve select()s.
Consider:
query overapproximates :my_lib’s dependencies:
cquery shows its exact dependencies:
FAQ
Why doesn’t select() work in macros?
select() does work in rules! See Rules compatibility for details. The key issue this question usually means is that select() doesn’t work in macros. These are different than rules. See the documentation on rules and macros to understand the difference. Here’s an end-to-end example: Define a rule and macro:sad_macro can’t process the select():
sad_macro:
select()s as opaque blobs to rules:
Why does select() always return true?
Because macros (but not rules) by definition can’t evaluateselect()s, any attempt to do so
usually produces an error:
select().
So what they’re really evaluting is the select() object itself. According to
Pythonic design
standards, all objects aside from a very small number of exceptions
automatically return true.
Can I read select() like a dict?
Macros can’t evaluate select(s) because macros evaluate before Bazel knows what the build’s command line parameters are. Can they at least read theselect()’s dictionary to, for example, add a suffix to each value?
Conceptually this is possible, but it isn’t yet a Bazel feature.
What you can do today is prepare a straight dictionary, then feed it into a
select():
select() and native types, you can do this:
Why doesn’t select() work with bind()?
First of all, do not usebind(). It is deprecated in favor of alias().
The technical answer is that bind() is a repo
rule, not a BUILD rule.
Repo rules do not have a specific configuration, and aren’t evaluated in
the same way as BUILD rules. Therefore, a select() in a bind() can’t
actually evaluate to any specific branch.
Instead, you should use alias(), with a select() in
the actual attribute, to perform this type of run-time determination. This
works correctly, since alias() is a BUILD rule, and is evaluated with a
specific configuration.
--define ssl_library=alternative, and any target
that depends on either //:ssl or //external:ssl will see the alternative
located at @alternative//:ssl.
But really, stop using bind().
Why doesn’t my select() choose what I expect?
If//myapp:foo has a select() that doesn’t choose the condition you expect,
use cquery and bazel config to debug:
If //myapp:foo is the top-level target you’re building, run:
//bar that depends on
//myapp:foo somewhere in its subgraph, run:
(12e23b9a2b534a) next to //myapp:foo is a hash of the
configuration that resolves //myapp:foo’s select(). You can inspect its
values with bazel config:
config_setting.
//myapp:foo may exist in different configurations in the same build. See the
cquery docs for guidance on using somepath to get the right
one.
Caution: To prevent restarting the Bazel server, invoke bazel config with the
same command line flags as the bazel cquery. The config command relies on
the configuration nodes from the still-running server of the previous command.
Why doesn’t select() work with platforms?
Bazel doesn’t support configurable attributes checking whether a given platform
is the target platform because the semantics are unclear.
For example:
BUILD file, which select() should be used if the target platform has both the
@platforms//cpu:x86 and @platforms//os:linux constraints, but is not the
:x86_linux_platform defined here? The author of the BUILD file and the user
who defined the separate platform may have different ideas.
What should I do instead?
Instead, define aconfig_setting that matches any platform with
these constraints:
What if I really, really want to select on the platform?
If your build requirements specifically require checking the platform, you
can flip the value of the --platforms flag in a config_setting: