Now we'll finally build some code.
By the end of this section, you'll be able to get desired outputs with
bazel build and execute
some application with
bazel run for at least the language you picked.
If you weren't able to create working
BUILD.bazel files in the previous section, you can skip ahead
by checking out the
packages branch, e.g.
git reset --hard packages
The Action Cache
Earlier, we introduced the Repository Cache (actually a downloader cache). This is entirely separate from the Action Cache, because only actions are hermetic and know all their inputs, and therefore a correct cache key can be computed.
When a file in
bazel-out is replaced, then Bazel normally needs to run the action again to restore it.
You can have an "L1" cache locally on the machine to avoid this -
--disk_cache is simple, but grows unbounded since there is no eviction so beware!
The cache key is typically dependent on the execution platform. This makes it very hard to share intermediate results between a MacOS development machine and a Linux CI system. Non-determinism will cause cache misses, because the cache key changes anytime a dependency produces a different output. Hash ordering and timestamps are typical sources of non-determinism.
Build some targets in the codelab repo. This may be where you discover errors in the BUILD files that weren't evident earlier when you only analyzed, as this is the first time we're running the tools.
Make some minor change to the sources and build again. Can you get some cache hits in this second build?
Bazel prints the paths to the resulting output files.
You can predict where the outputs will be written, if a script wants to do something with them:
bazel help outputs
Bazel can take several approaches to execute an action. See https://bazel.build/docs/user-manual#execution-strategy
sandboxedcauses commands to be executed inside a sandbox on the local machine.
- This requires that all input files, data dependencies and tools are listed as direct dependencies in the srcs, data and tools attributes.
- Bazel enables local sandboxing by default, on systems that support sandboxed execution.
localcauses commands to be executed as local subprocesses.
workercauses commands to be executed using a persistent worker, if available.
dockercauses commands to be executed inside a docker sandbox on the local machine.
- This requires that docker is installed.
remotecauses commands to be executed remotely; this is only available if a remote executor has been configured separately.
dynamicis a special value to try both remote and local execution, take the first to complete.
Checking in generated files
Bazel output files are always written beneath the
But other tools allow, or even expect, that files are written to the source tree.
For example, a
go_proto_library target produces a
foo.pb.go file which the editor expects to
read in order to provide valuable completion (intellisense) to a developer navigating the API.
While it's recently possible to use the GoPackagesDriver to teach the editor to look in
for this file, this is a difficult, brittle configuration to setup on developer machines.
And then you'll just have the next tool or language where the
bazel-out location breaks things.
We can un-break the situation if we relax the Bazel dogma around writing to the source tree.
bazel build itself cannot result in files in the source tree, but we can get a close
approximation with a rule called
It uses a simple pattern:
- the build target writes to a file like
- a generated test target asserts that
pkg/foo.pb.goin the source folder has the same content
- a generated executable copies files from
bazel-outback to the source tree
- when the test fails, it prints an instruction how the developer can run the executable
This is the same pattern we use for "golden" or "snapshot" files which live in the source tree.
Exercise: Running programs
Running programs in Bazel is just a syntax sugar. It really means "build this program, then spawn the resulting executable.
bazel help run
You should be able to run one of the programs you've built so far, in whichever language you have working.
Bazel's naming convention is that any
*_binary target should be executable, so you can
bazel run it.
It's really nice to have a "live" development server where code changes are immediately visible.
To make this possible, we need Bazel to run in "watch mode" where changes to sources are automatically
reflected in the
bazel-out tree without us having to manually run
bazel after every edit.
Sadly, Bazel doesn't have a watch mode built-in, but bazel-watcher does. You can install it in a couple ways:
brew install ibazel
- If you have npm installed:
npm install -g @bazel/ibazel
- Download a binary from the releases page
and install it as
We plan to include this feature directly in a future release of Aspect CLI
Exercise: A live devserver
If you didn't write a BUILD file for the frontend, you can checkout the
The target we run has to be aware of the Bazel-watcher protocol:
- The target must be tagged to indicate its awareness:
tags = ["ibazel_notify_changes"]This tag prevents the binary being restarted every time the files change.
- The program must read from stdin to find out when a build has finished.
bazel-watcher will write lines like
js_run_devserver is one rule that is aware of the protocol.
$ ibazel run frontend
Click the link to open the site in the browser, then make some edits to
example change the
Get Server Logs text that appears on the button.
We have a recipe for making the browser hot-reload as well.