Think of select() as a conditional expression that lives inside your build rules. It functions exactly like a switch or if/else statement in a programming language, but it operates during the “analysis phase” of a Bazel build.
See the other post for constraint_setting and constraint_values
1. The Core Purpose
When you build code, you often have minor differences based on the environment. Maybe you need a specific header file on Windows, or a different compiler flag for ARM processors. Instead of creating different build files for every platform, you use select() to keep your build files clean and unified.
2. How it works
You use select() inside an attribute (like srcs, deps, copts, or data). It takes a dictionary where:
- The keys are
config_settingtargets (which contain your constraints). - The values are the actual data you want to use if that condition is met.
3. A Practical Example
Imagine you are building a library that handles file paths. You need a different C++ source file depending on whether you are compiling for Windows or Linux.
cc_library(
name = "file_utils",
srcs = select({
"@platforms//os:windows": ["path_win.cc"],
"@platforms//os:linux": ["path_linux.cc"],
"//conditions:default": ["path_generic.cc"], # The "else" case
}),
)
Sometimes a single select() key isn’t enough. What if you want to do something only on “Linux + x86”? You can use config_setting to group them:
# 1. Define a specific condition
config_setting(
name = "is_linux_x86",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
)
# 2. Use that condition in a select
cc_library(
name = "optimized_lib",
srcs = select({
":is_linux_x86": ["optimized_x86.cc"],
"//conditions:default": ["generic.cc"],
}),
)
4. Anatomy of the select()
@platforms//os:windows: This is your condition. If the current--platformsflag results in an environment that has thisconstraint_value, this branch is chosen."//conditions:default": This is the required fallback. If none of your specific conditions match, Bazel uses this value. Your build will fail if you don’t provide this and no other condition matches.- Merging: If you use
select()on an attribute that accepts lists (likesrcs), you can even combine them with non-selected items:Pythonsrcs = ["common.cc"] + select({ ... }),Bazel will automatically concatenate the lists.
Why use select() instead of just writing two different targets?
- DRY (Don’t Repeat Yourself): You maintain one
cc_librarytarget instead of having separatecc_library_winandcc_library_linuxtargets. - Type Safety: Bazel validates that the constraints used in your
selectare validconstraint_valuetypes. - Efficiency: Bazel only computes the graph for the specific path you have chosen. It doesn’t waste time analyzing the branches that aren’t relevant to your current build.
In short, select() is the tool you reach for when you want your build logic to be flexible enough to handle different platforms without becoming a tangled mess of duplicated rules.
platforms() and select()
The Big Picture
- Platforms describe the “Where” (the environment).
select()describes the “What” (the specific code or flags needed for that environment).
1. Platforms (The Environment Orchestrator)
A Platform is a collection of constraint_values that defines a complete target hardware/OS environment.
- Role: Acts as the master configuration for your entire build.
- Purpose:
- Toolchain Resolution: Tells Bazel exactly which compiler, linker, and standard library to use.
- Compatibility: Defines what the environment is (e.g., “This is a 64-bit ARM Linux machine”).
- Global Context: You set it once at the top level via the command line (e.g.,
--platforms=//platforms:my_custom_platform), and the entire build graph adopts that context.
2. select() (The Conditional Switch)
select() is a function used inside individual rules to perform conditional logic.
- Role: Acts as an
if/elseorswitchstatement for specific build attributes. - Purpose:
- Code Variation: Swap source files (
srcs), dependencies (deps), or compiler flags (copts) based on specific features. - Granularity: Allows a single target to be “smarter” about its requirements without needing separate rules for every platform.
- Code Variation: Swap source files (
- How it decides: It looks at the constraints provided by the active
platform. If the active platform contains the constraint specified in yourselect()key, that branch is chosen.
Comparison Summary
| Feature | Platforms | select() |
| Concept | The “Environment” | The “Conditional Logic” |
| Scope | Global (entire build graph) | Local (specific target attributes) |
| Function | Identifies hardware/OS capabilities | Swaps code/flags based on those capabilities |
| Command Line | Set by --platforms | Automatically reacts to --platforms |
| Best Analogy | The OS running on a server | An if statement in your code |
The Complete Cross-Platform Workflow
- Define your Constraints: Create the
constraint_settingandconstraint_valuetargets (or just use the standard ones from@platforms).- Example: “I need to distinguish between Windows and Linux.”
- Define your Platform: Group those constraints into a
platformtarget.- Example: “Create a
my_windows_machineplatform that includes@platforms//os:windows.”
- Example: “Create a
- Update your Targets (The crucial link!): Modify your existing
cc_library,cc_binary, etc., to useselect()on their attributes. This tells the rule how to change its behavior based on the constraints.- Before:
srcs = ["default_file.cc"] - After:
srcs = select({"@platforms//os:windows": ["win_file.cc"], "//conditions:default": ["default_file.cc"]})
- Before:
- Build with the Platform: Run your build and tell Bazel which platform to act as.
- Example:
bazel build //my_app --platforms=//:my_windows_machine
- Example:
- Bazel Connects the Dots: Bazel sees the
--platformsflag, checks the platform’s constraints, looks at yourcc_library‘sselect()statement, and automatically pickswin_file.cc.