Structuring a Monorepo with Packages
We will start by understanding the Bazel configuration for the first-party code in our repository.
By the end of this section, you'll be able to run bazel query
and similar
commands to explore Bazel's dependency graph for at least one language.
Complex BUILD files
In the 101 course, you learned the basics of BUILD
files. As a reminder, these
files are usually named BUILD.bazel
in newer or updated workspaces, but the
old name will probably stick around as common parlance forever.
The language of BUILD
files is a subset of Starlark.
We talked about this in the Configuration section.
Starlark is a configuration language, and the BUILD file subset is roughly the declarative constructs.
Things like for
loops are not legal syntax in BUILD
files, but list comprehensions are.
Combined with glob
these can be a powerful way to stamp out targets, for example:
https://github.com/aspect-build/bazel-examples/blob/main/speller/data_driven_tests/BUILD.bazel
Generating and Updating BUILD files
Gazelle or aspect configure
should be expected to maintain 80% of the BUILD
files,
since most of their content may be inferred from the source files.
Add an import statement, run the tool, and the relevant BUILD
file is updated to reflect it.
The configure
subcommand is specific to Aspect CLI, providing a
straightforward and uniform way to invoke build file generation. Projects using
core Bazel only can direct developer to run a suitable script instead.
Hand-editing BUILD files
While the situation has improved tremendously in recent years, 20% of BUILD
files are typically hand-edited, because:
- They do things not described by the source files, or
- They are for languages with no Gazelle support available (yet)
Machine-editing BUILD files
You can use buildozer
to script around printing and modifying BUILD file content, which is an essential skill for doing
repository-wide refactoring.
Buildozer is purely syntactic, operating on the Starlark Abstract Syntax Tree (AST). This can be convenient if you want to see what the user typed, before loading and macro expansion occur. It's also guaranteed to be fast, while loading might take a long time.
There's a dedicated 'print' command to make this feature easier to access:
bazel print //some:target
The Dependency Graph
In the Loading phase, Bazel loads all of the BUILD.bazel
files needed for whatever targets or target patterns you request.
These targets form a Directed Acyclic Graph (DAG) called the "dependency graph".
Exercise: bazel cquery
cquery
means "configured query" which is typically what you want.
The Bazel query guide and cquery documentation are quite good. Here are some queries you can try now to get started.
- What are all the binary targets in the repository?
bazel cquery 'kind(.*_binary, //...)'
- What are all the binary targets in the
logger
project?
bazel cquery 'kind(.*_binary, //logger/...)'
- Draw a diagram of all the dependency of a target.
# apt install graphviz xdot
# brew install graphviz xdot
xdot <(bazel query "deps(//logger/client/src/build/aspect:JavaLoggingClient)" --notool_deps --noimplicit_deps --output graph)
- Why does your binary depend on a particular third-party library?
Exercise: bazel query
The unconfigured query command is just bare query
; unfortunately the shorter
name is taken by the less-often-needed command. If we visit code where the
difference is important, we can run the alternatives to observe how the output
changes.