Bazel Evaluation Model
Bazel’s runtime is a series of phases. Developers won’t typically interact with these phases. If you ask Bazel to run a test, it automatically performs the prerequisite phases. Phases are executed as a partially ordered pipeline - a phase may begin before the previous one has finished. For example, starting in Bazel 7, execution can start before analysis is complete, thanks to Skymeld.1. Configuration pseudo-phase
Read all the source files and update theBUILD files.
In this exercise, you will run the Aspect CLI’s gazelle task (aspect gazelle), which is configured to use a pre-compiled “Gazelle” with an easy extension model. Be aware that most repositories use a Go source distribution instead, so developers compile a gazelle_binary and then run that.
This phase is manual. Bazel doesn’t do it for you because it may take longer than Bazel is willing to spend on a “no-op” build. In Bazel terminology, it’s not even regarded as a “phase,” but it is the first step in the process, so it’s modeled this way in the course.
Developers are expected to “configure” when they get an error message that BUILD files are out-of-date. It’s also possible to automate this in other ways, such as with editor extensions, autogazelle, etc.
Try it out in the examples repo:
- Modify a
BUILDfile to make it outdated. For example, comment out a line in thedepsof theserver_libtarget inlogger/backend/cmd/server/BUILD.bazel - Run
aspect gazelle. All the source files for enabled languages are parsed, to infer their dependency graph. - Observe that the
BUILDfile is updated.
2. Fetching pseudo-phase
Download external resources that are required by the requested targets. This phase is implicit. During the “Loading” phase, any references that walk outside the sources in the monorepo automatically trigger fetching to occur. Bazel’s dependency graph should always determine which dependencies are fetched lazily as needed. For example, a third-party package not used anywhere in the repository should never be fetched. If you see something fetched that shouldn’t be, raise an issue with your DevInfra team! As a result, you ought to be able to turn off WiFi, unplug the network, or run Bazel with--nofetch, and still perform the remaining phases.
In the
logger folder of the example repo, try:- Run
bazel fetch cli/...to download all the Python dependencies from PyPI. - Run
bazel fetch frontend/...to download all the JavaScript dependencies from npm. - Fetch some third-party packages directly, for example:
Caching fetches: the repository cache
Fetching external dependencies can be slow. In a big monorepo, you’ll download many large files for hermetic toolchains. Bazel caches these in the$(bazel info repository_cache) folder, taking the following steps:
- Caches the downloaded files.
- Always give the integrity hash, that’s the key
https://bazel.build/extending/concepts#evaluation-model shows only the remaining phases:
3. Loading phase
Load and evaluate all extensions and allBUILD files that are needed for the build.
This triggers fetching as well. The execution of the BUILD files instantiates rules (each time a rule is called, it gets added to a graph). This is where macros are evaluated.
For example, you can trigger loading to occur by performing a query.
- Run
bazel query 'somepath(client, @@zlib~1.3//:zlib.h)'to see why the Java application depends on zlib. - As a result, Bazel constructs the “Dependency Graph” (where the nodes are “targets” and the edges are dependencies between them).
4. Analysis phase
The implementation function for each rule is executed, and actions are instantiated. An “action” describes how to generate a set of outputs from a set of inputs, such as “rungcc on hello.c to get hello.o”.
You must list explicitly which files will be generated before executing the actual commands. In other words, the analysis phase takes the graph generated by the loading phase and generates an action graph.
You can think of this as a “dry run” of the build. Bazel will figure out all the build steps (“actions”) that it needs to perform.
Bazel doesn’t actually have a command called analyze. It’s spelled build --nobuild instead.
This command is rarely useful.
You might use it if you’re making a big, breaking refactoring, so that you can resolve all the analysis failures first before attempting to build anything.
You could also use it to reason about what is the slow step in your CI pipeline.
Try this in the examples:
- Run analysis:
bazel build --nobuild //backend/... - As a result, you know the build has been defined without any errors.
5. Execution phase
Actions are executed, but only if at least one of their outputs is requested by the user. Thebazel-out folder is up-to-date at the end of the execution phase.
Finally you have everything in-place for Bazel to spawn sub-processes which do the actual work of the build.
Try it out with:
bazel build //...
