Skip to main content

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.


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.


The java_grpc_library isn't available under bzlmod yet, so we've just stashed the resulting generated code next to the sources.


bazel configure or Gazelle should work 100%.


A gazelle extension is available from rules_python, but a three-line py_binary is enough to make the CLI work.


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
The comment is a gazelle directive.

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.

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 target(s) or target patterns you request.

These targets form a Directed Acyclic Graph (DAG) called the "dependency graph".

Result of querying Java depgraph

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?


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)