Bazel uses a very opinionated scheme to store build artifacts that is a very common source of confusion for developers. This pages tries to clarify how things work.
The Bazel output_base
By design, bazel build commands will never write files to a project's source
directory (or one of its sub-directories). Instead Bazel uses a user-specific
parallel directory to store all outputs, called the user_output_root, which
is by default:
~/.cache/bazel/_bazel_$USERon Linux.%HOME%\_bazel_%USERNAME%on Windows.
For each workspace directory where bazel is run, a separate directory, named
the output_base is created under the user_output_root, as:
${user_output_root}/<WORKSPACE_HASH>`
Where <WORKSPACE_HASH> is a long hexadecimal hash (computed from the
directory's absolute path).
Since this path is completely unpredictable, the command
bazel info output_base will print it, when used inside a Bazel project.
For example:
$ mkdir -p /tmp/project1 && cd /tmp/project1 && touch WORKSPACE.bazel
$ bazel info output_base
Starting local Bazel server and connecting to it...
/usr/local/google/home/digit/.cache/bazel/_bazel_digit/6c7b78994da78136b5cb6b7607361ad3
$ mkdir -p /tmp/project2 && cd /tmp/project2 && touch WORKSPACE.bazel
$ bazel info output_base
Starting local Bazel server and connecting to it...
/usr/local/google/home/digit/.cache/bazel/_bazel_digit/c37b9d68308ee5abe2f781dd38b733b9
$ mkdir -p /tmp/not-a-project && cd /tmp/not-a-project
$ bazel info output_base
WARNING: Invoking Bazel in batch mode since it is not invoked from within a workspace (below a directory having a WORKSPACE file).
ERROR: The 'info' command is only supported from within a workspace (below a directory having a WORKSPACE file).
See documentation at https://bazel.build/concepts/build-ref#workspace
This scheme is flexible but not perfect:
Pro: multiple users on the same machine can share the same read-only project directory.
Pro: multiple project directories for the same user will always use independent output paths.
Con: Looking at generated files directly from the command-line or even graphical explorers is very difficult.
Con: Removing a project directory (e.g. with
rm -rf .../my-project) does not remove its outputs (a significant source of waste).Con: Moving a project directory (e.g. with
mv my-project my-project2) does not reuse previousoutput_basecontent (and leaves the old ones in place, now inaccessible).Con: The default location for the
user_output_root, and thus theoutput_baseis often not on the same filesystem / partition than the project. This can have unexpected consequences in terms of performance / disk usage.
Call bazel clean to remove build outputs from the current output_base.
This must be done before removing a source project directory.
In practice, it is easy to bloat the content of the user_output_root with
build artifacts from stale Bazel projects that were never properly cleaned.
Worse, trying to simply remove the user_output_root manually may not work,
because Bazel creates read-only build artifacts by default, which prevent a
command like rm -rf ~/.cache/bazel from working!
Bazel output_base content:
Several things are actually stored under the output_base:
Workspace directories for external repositories:
Those correspond to external project dependencies. Often these are not part of the project's source tree, but downloaded from the network or generated programmatically.
Their content is stored under
${output_base}/external/<repository_name>, where theexternalpart is hard-coded, and<repository_name>matches the external repository's canonical name.Build artifacts:
The files generated by running
bazel build. These are stored under:${output_base}/execroot/<workspace_name>/bazel-out/<config_dir>/bin/Where:
The
execroot,bazel-outandbinparts are hard-coded and cannot be changed.For targets defined in the project's own
BUILD.bazelfiles,<workspace_name>defaults to__main__, unless it is set in the project'sWORKSPACE.bazelfile with a directive like:
workspace( name = "my_project", ) ``` - For targets defined in external repositories, `<workspace_name>` matches the repository's canonical name. - The `<config_dir>` value is a name derived from the build configuration used to configure the target that generated the build artifact. This allows rebuilding the same target in different ways, each time using a different `<config_dir>` value. Note: The `<config_dir>` value is **generally unpredictable**. More on this [here][bazel-config-dirs]Test results:
The log files generated when
bazel testis called, stored under${output_base}/execroot/<workspace_name>/bazel-out/<config_dir>/testlogs/.Internal cache and configuration files:
Used by remote builds and remote cache features. These files can be ignored by developers.
Bazel execroot directory:
The execroot is used to run Bazel commands that generate build artifacts,
but how this is done depends on whether sandboxing is enabled for a specific action.
On Linux, sandboxing is enabled by default for all actions. There is no sandboxing support on Windows (as of Bazel 7).
A Bazel action can disable sandboxing intentionally by using the
no-sandboxtag in definition.Sandboxing can be disabled globally with an option like
--spawn_strategy=localwhen invokingbazel.
Without sandboxing:
When sandboxing is disabled, all build actions that generate artifacts for
a given workspace will put output files under
${output_base}/execroot/<workspace_name>.
All paths to source files and build artifacts that appear in the action's command will thus be relative to it.
Bazel ensures that symlinks to the input sources used by the command are created under the execroot before the command is launched.
For example, an action that would compile the //src/foo/foo.cc file,
which contains #include "foo.h", corresponding to //src/foo/foo.h could
look like:
gcc -c -o bazel-out/k8-fastbuild/bin/src/foo/foo.o src/foo/foo.cc -Isrc/foo
Which works because:
Before running the command, Bazel would create the symlink
${output_base}/execroot/__main__/srcpointing to$PROJECT/src, so thatsrc/foo/foo.ccandsrc/foo/foo.hresolve to$PROJECT/src/foo/foo.ccand$PROJECT/src/foo/foo.has expected.The location
bazel-out/k8-fastbuild/bin/src/foo/foo.ois the final output path, for the object file created by compilingfoo.ccin the build configuration used by this command.
With sandboxing:
When sandboxing is enabled, Bazel creates for each command a temporary
directory (e.g. ${output_base}/sandbox/linux-sandbox/<random-number>)
and will create a symlink tree to mimic the execroot layout under it, but
only for the inputs it know about. In this case, this would look like:
Symlink for the inputs
${sandbox}/execroot/__main__/src/foo/foo.ccand${sandbox}/execroot/__main__/src/foo/foo.hpointing to$PROJECT/src/foo/foo.ccand$PROJECT/src/foo/foo.hrespectively.Run the exact same command under
${sandbox}/execroot/__main__. instead of${output_base}/execroot/__main__.Once the command completes, copy the known output from the sandbox path at
${sandbox}/execroot/__main__/bazel-out/k8-fastbuild/bin/src/foo/foo.oto its final location at${output_base}/execroot/__main__/bazel-out/k8-fastbuild/bin/src/foo/foo.o.Finally, the sandbox directory, and all its content are removed. This also means that undeclared outputs are ignored.