Structuring a Monorepo with Packages
Now we'll start to add Bazel configuration for the first-party code in our repository.
By the end of this section, you'll be able to run bazel query
to explore Bazel's dependency and action graphs for at least the language you picked.
If you weren't able to get things working in the previous section, you can advance your codelabs
repo to the dependencies
branch, e.g. with git reset --hard dependencies
Exercise: create BUILD
files
The schema folder has our protobuf definition, and you'll want to depend on that from other folders.
Since we use Aspect CLI, you can just run bazel configure
to get a BUILD file generated for our logger.proto
.
Next, for the language you've chosen, try creating the BUILD.bazel
file.
Since our examples are simple and only have a single source file or folder of source files,
you'll only need one for each language.
JavaScript
ts_project
was already created.
We can create a http_server_binary
for the http-server
npm package and call it using js_run_devserver
.
Java
The java_grpc_library
isn't available under bzlmod yet,
so we've just stashed the resulting generated code next to the sources.
Go
bazel configure
or Gazelle should work 100%.
Python
A gazelle extension is available from rules_python, but a three-line py_binary
is enough to make the CLI work.
Swift
A gazelle extension for Swift source files is available in rules_swift_package_manager
.
When preparing your workspace in a previous step, you defined a target called //:update_build_files
.
Running this target will generate BUILD.bazel
files with the appropriate Swift rules in packages where it finds Swift source files (e.g. .swift
).
Due to the way that Swift iOS applications are typically generated, we need to tell the gazelle extension where to create the swift_library
.
Create ios/LoggingClient/App/BUILD.bazel
and add the following comment to the file: # gazelle:swift_default_module_name LoggingClientApp
.
% echo "# gazelle:swift_default_module_name LoggingClientApp" \
> ios/LoggingClient/App/BUILD.bazel
These are used to configure gazelle and its extensions.
Now, run the //:update_build_files
target to generate your BUILD.bazel
files.
% bazel run //:update_build_files
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 refactorings.
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 target(s) 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 unconfigured query command is just bare query
.
Unfortunately the shorter name is taken by the less-useful command.
Here are some things you can try:
- What are all the binary targets in the repo?
- Draw a diagram like the one above for the language you're working with.
- Why does your binary depend on a particular third-party library?
Actions
When Bazel needs to transform inputs to outputs, it does it by spawning an Action, which is just a subprocess invoking some tool.
The Action Graph
In the Analysis phase, the dependency graph is "lowered" to an action graph. In the action graph, each node is a subprocess to spawn (invoking some tool) and the edges are files and Providers which are output by one action and needed as inputs to another.
The graphs are NOT one-to-one!
For example, a ts_project
rule with a custom transpiler produces several actions.
Exercise: bazel analyze
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.
bazel build --nobuild //...
Querying the action graph
This is a valuable skill when debugging a failure of some rule, especially when required inputs aren't declared.
You can run arbitrary starlark programs on the action graph with --output=starlark
which is a powerful tool.
Exercise: bazel aquery
- What are the declared input files to the compile action for a library target you've created?
- What providers are produced by the library target? (You'll need a tiny starlark program)