Skip to main content

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.

Aspect CLI Only

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.

Aspect CLI Only

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".

Result of querying Java dependency graph

Exercise: bazel cquery

danger

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.