2.2.1 Loading, Analysis & Execution
A Bazel build is not one opaque step. It is a pipeline that turns declarations into a plan and only then into artifacts: loading reads the BUILD / .bzl world, analysis turns reachable targets into an action graph, and execution runs the subset of actions whose outputs are actually needed. Keeping those boundaries straight explains how the artifact model from P.2.1 Task-Based vs Artifact-Based becomes the concrete behavior teased in P.2.4 Five Properties.1,2,3
Loading reads declarations
Loading is where Bazel discovers the structure of the request. It loads the BUILD files for the initial targets and their transitive dependency closure, evaluates the .bzl files reached by load(), and instantiates targets from those declarations. Macros live here: they expand while Bazel is reading BUILD declarations, before any build action runs.2,3,4
For targets in the main repository, this is still not "building" in the everyday sense. Bazel is reading BUILD-related files, not running compilers or other build tools on your source tree. That design is why the reachability story from 2.1.2 Laziness & Slicing already matters at the start: if a package is not reachable from the requested targets, Bazel does not need to load it for this request.3,5
Loading also includes fetching an external repository when Bazel needs a target from it. If a loaded label points into a repository that is not yet available, Bazel runs its repository rule on demand, materializes the repository, and can then continue loading packages from it. This is loading-phase work, not a fourth phase and not a normal build action.6
That repository boundary is the important exception to "loading only reads declarations." A repository rule has direct host and network APIs, including repository_ctx.execute(), so loading can perform substantial setup work. For example, repository_ctx.execute(["ghc", ...]) may legally compile a bootstrap tool while creating a repository. This is setup for the repository as a whole, not a normal compile action: Bazel cannot represent its per-module work as action nodes, reuse those steps through the normal action cache, or schedule them through remote execution. Prefer a verified prebuilt GHC exposed through a toolchain, then let ordinary compile actions consume that toolchain. Keep repository-time compilation for a narrow bootstrap that cannot start in the ordinary action graph.7
Judge the exception: A toolchain cannot build anything until a small bootstrap compiler exists. Its repository rule uses a compiler binary and source archive pinned by checksum to produce that bootstrap compiler. What did pinning improve, and what costs remain compared with a normal build action?
Reveal
Checksums make the bootstrap inputs explicit and reproducible enough to audit. They remove the worst "whatever is on $PATH" ambiguity. That can make repository-time compilation a defensible, narrow bootstrap exception when no ordinary toolchain exists yet.7
Pinning does not turn it into a normal action. The compilation is still coarse repository setup, without per-step action-graph visibility, normal action-cache reuse, or remote-execution scheduling. It is invalidated and repeated at repository granularity, and platform differences still need explicit handling. Prefer a prebuilt pinned toolchain archive when practical. Otherwise record the OS, architecture, environment, and helper inputs deliberately, then use the resulting compiler only to unlock normal declared actions.
Analysis turns targets into actions
Analysis takes the reachable target graph from 2.1.1 Nodes, Edges & Acyclicity and asks each rule what it would need in order to produce its outputs. Rule implementation functions run in this phase, validate attributes and dependencies, declare output files, and register actions describing commands plus their declared inputs and outputs.2,4,8
The result is the action graph. It is concrete enough for Bazel to plan caching and parallel execution, but it is still only a plan. Declaring an action does not run it. That is also why rules cannot inspect arbitrary source contents or spawn tools to discover more edges on the fly: loading and analysis define the graph. Execution consumes it. The stronger consequence of that "plan first" model is the topic of 2.2.3 Static Action Graph.2,4,5
Execution runs the needed subset
Execution is where ordinary target build work runs as actions. Bazel starts from the requested outputs, walks backward through the action graph, and runs only the reachable actions that are not already satisfied by valid cached results. Those actions may run locally or remotely, and independent ones can run in parallel.2,3,5,8
At this point tools finally get normal file I/O: compilers read source inputs, linkers produce binaries, tests run, and sandboxes can restrict actions to their declared inputs. That execution-time isolation is the next topic in 2.3.2 Sandboxing. And once you move from "produce declared artifacts" to broader workflow coordination, you have crossed into the territory of 3.7 Workflow Orchestration (Outside the Graph).5,8
Keep the units straight
A useful mnemonic is package -> target -> action -> artifact. Loading works in terms of packages and BUILD declarations. Analysis works in terms of targets, declared outputs, and actions. Execution works in terms of running those actions to materialize artifacts. That is why Bazel can report "packages loaded" and "targets configured" before it ever tells you how many actions ran. 2.2.2 Reading Build Output turns those counters into a practical debugging tool.3
The three phases are not merely labels in a log. They are the mental boundary between "what the build means" and "what the build does": loading reads declarations and fetches required repositories on demand, analysis turns reachable targets into an action plan, and execution runs the uncached part of that plan. If you can place a behavior in the right phase, most Bazel behavior becomes easier to explain.1,2,5,6
Check your understanding · 4 questions
1.Match each build phase to its primary responsibility:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.Which phase produces the action graph that execution later runs?
Select one answer
3.Which of the following happen during the loading phase?
Select all that apply
4.A repository rule fetches Haskell sources and invokes the host ghc while creating an external repository. What is the best assessment?
Select one answer
Footnotes
-
Intro to Bazel — the three-step build process and the action graph as the output of analysis ↩1 ↩2
-
Extension Overview — loading evaluates BUILD and
.bzlfiles, macros expand there, analysis instantiates actions, and execution runs them only when outputs are needed ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 -
Build programs with Bazel — request-scoped loading of the dependency closure, user-facing phase descriptions, and the progress counters Bazel prints during builds ↩1 ↩2 ↩3 ↩4 ↩5
-
Rules — macros are a loading-phase construct, while rule implementation functions run in analysis and register actions instead of executing commands ↩1 ↩2 ↩3
-
Frequently Asked Questions — no tool execution or arbitrary file I/O during loading or analysis, and only requested outputs cause actions to execute ↩1 ↩2 ↩3 ↩4 ↩5
-
Repository Rules — repository rule implementations execute in the loading phase when Bazel needs a target from the repository ↩1 ↩2
-
repository_ctx — repository rules can execute host commands. Their loading-time context has different file-I/O powers from ordinary target rules ↩1 ↩2
-
Writing Bazel rules: simple binary rule — plain-language explanation of the three phases, the file-action graph, and execution-time sandboxing ↩1 ↩2 ↩3