4.7 Build Settings & Transitions
bazel build //app:firmware --//config:board=rp2040 looks like one command with one set of flags. Inside Bazel, it is a request to analyze many targets under many possible configurations. A library may be built for the target platform, a compiler for the execution platform, a test dependency with sanitizer flags, and the same binary twice for two CPU architectures. The command line starts the story. The graph finishes it.
This section is about configuration edges. Build settings give configuration values stable labels and types. Transitions move those values across dependency edges. Split transitions deliberately fork one edge into several configured copies. The hard part is not writing the transition function. The hard part is deciding which part of the graph is allowed to change.
Name The Value Before Moving It
Read 4.7.1 Build Settings for Rule Authors first even if you came here for transitions. A Starlark build setting is a target that represents one typed entry in the configuration map: a bool, int, string, string list, or string set. Label-valued configuration uses built-in label flags and settings. Users can set public settings with --//pkg:flag=value, config_setting(flag_values = {...}) can match them, and rule code can validate and expose them through providers.
That article is the bridge back to 3.2.4 Command Line Flags and 3.3.1 Configurable Attributes (select()). If a value can be selected rather than rewritten, do that. A transition is not the answer to "I wish users did not type this flag," and new project-specific knobs should be build settings rather than --define keys.
Name The Edge Before Writing The Transition
4.7.2 Starlark Transitions is the point where configuration stops being only a top-level input. A transition is a declared function from incoming settings to outgoing settings, attached either to the rule itself or to one of its dependency attributes. It turns "build this dependency differently" into graph structure: the parent remains one configured target, while the dependency subtree may be analyzed under a modified configuration. The attachment point matters: incoming edge versus outgoing edge, native options versus Starlark settings, and ordinary transitioned deps in ctx.attr versus split-transition deps in ctx.split_attr are different rule contracts.
4.7.3 Split Transitions is the intentional fan-out case. Instead of one modified configuration, the parent asks for several configured copies of the same dependency and combines them into one output. That shape is useful for universal binaries, multi-arch containers, Android APKs with native libraries, and release packages that deliberately aggregate per-platform artifacts. If you only need one dependency under one different setting, stay with the 1:1 transition model.
Contain The Blast Radius
The syntax invites overconfidence:
return {"//command_line_option:cpu": "darwin_arm64"}
That line is not just a value assignment. It may create another configured copy of every reachable target below the edge. It may make a shared library analyze twice. It may push target-side flags into data files that never needed configuration. It may make output directories look strange until 5.2.2 bazel cquery — Configured Graph and 5.2.4 bazel config — Configuration Inspection reveal that the graph contains more than one configuration.
4.7.4 Transition Boundaries & Gotchas is the production safety rail: boundary spread, config.none(), duplicate configuration diagnosis, and output-directory naming are what keep a clever rule from becoming an analysis-time tax. Before writing a transition for ergonomics, check the alternatives in order: build setting plus select(), a toolchain, platform(flags = [...]), and only then a narrow Starlark transition. 4.7.5 Platform-based Flags is the newer, narrower alternative for one common pattern: when a transition only bundles flags with a selected platform, move those values onto the platform() target instead of inventing another wrapper rule.
Configuration Is Not Global State
Before this section, configuration may feel like a bag of flags selected once by .bazelrc, --config, and the command line. That model is useful for operators, but it is not enough for rule authors. Rule authors design edges. They decide whether a dependency should stay in the target configuration, move to the execution configuration, reset to no configuration for pure data, fork into several platform variants, or inherit a build setting changed by a transition.
That is why this section follows 4.6 Toolchains & Platform Resolution. Toolchains already separated "what the artifact is for" from "where the tool runs." Build settings and transitions generalize that lesson: a configured target is a label plus a configuration, and rule APIs can create new configured targets by changing which configuration flows along which edge. The same graph-first habit prepares 4.8 Aspects, where the graph is traversed without rewriting configuration.
Trace: A transition changes one setting on a broad dependency attribute. Which targets receive the new configuration, and what boundary must you identify before keeping the transition?
Reveal
The changed configuration flows from that attribute into the reachable dependency subtree, potentially creating additional configured copies of shared targets. Identify the exact edge where the different configuration should begin and the edges below it that should retain or discard the caller's configuration.
If that boundary is vague, model the value with a build setting plus select(), a toolchain, or platform-based flags first. Keep a transition only when a real dependency subtree needs a distinct configuration and its spread can be contained.
Run the mini-ruleset to see typed build settings feed a focused family of outgoing, incoming, and split transitions.
Build settings and transitions are the rule-author tools for making configuration part of the build graph. A build setting gives a configuration value a typed target label. A transition changes which values flow across a dependency edge. A split transition intentionally asks for several configured copies at once. Use them when the graph boundary is real, and contain them as carefully as any other public rule contract.
Sections in this chapter · 5
Defining typed Starlark flags and reading their values from rule implementations.
Configuration transitions that actively modify build flags for dependency subtrees.
Fork the dependency graph into multiple configurations for multi-architecture artifacts.
Containing transition spread, using config.none() for data targets, and avoiding duplicate work.
Attaching build-setting values to platform() targets to avoid custom transition boilerplate in some domains.