How to Use Bazel for Your Build Process

yanir manor
4 min readJan 4, 2024

Bazel is a powerful tool that helps you build, test, and deploy software faster and more reliably. It supports multiple languages, platforms, and frameworks, and can scale to handle large and complex projects.

But how do you use Bazel effectively for your own build process? In this blog post, I will answer some common questions about using Bazel and share some tips and tricks to make the most of it.

Photo by Andrik Langfield on Unsplash

What is the output of a Bazel command execution?

When you run a Bazel command, such as bazel build or bazel test, Bazel will generate four folders in your workspace directory:

  • bazel-bin: This folder contains the built executables and scripts that you can run or deploy. For example, if you build a Java binary named hello, you will find it in bazel-bin/hello.jar.
  • bazel-genfiles: This folder stores the generated source files that are not executable, such as protocol buffers, Java sources, or HTML files. For example, if you generate a Java file named Hello.java from a proto file named hello.proto, you will find it in bazel-genfiles/Hello.java.
  • bazel-out: This folder houses the intermediate build outputs, such as object files, libraries, or archives. These files are usually not useful for the end user, but they are needed for the build process. For example, if you compile a C++ file named hello.cc, you will find the object file in bazel-out/hello.o.
  • bazel-testlogs: This folder contains the test logs and results for the tests that you run with Bazel. For example, if you run a Python test named hello_test.py, you will find the test log in bazel-testlogs/hello_test.log and the test output in bazel-testlogs/hello_test/test.outputs.

You can use the bazel clean command to delete these folders and start a fresh build.

How can you control Bazel task execution orders?

One of the key features of Bazel is that it handles the dependency management for you. This means that Bazel will automatically determine the correct build order and execution order for your tasks, based on the dependencies that you declare between them.

To declare dependencies between tasks, you need to use the deps attribute in your Bazel rules. For example, if you have a Java binary that depends on a Java library, you can write something like this in your BUILD file:

java_binary(
name = "hello",
srcs = ["Hello.java"],
main_class = "Hello",
deps = [":hello_lib"],
)
java_library(
name = "hello_lib",
srcs = ["HelloLib.java"],
)

The deps attribute tells Bazel that the hello binary depends on the hello_lib library, and that the hello_lib library should be built before the hello binary. Bazel will also ensure that the hello_lib library is available on the classpath when compiling or running the hello binary.

You can use the bazel query command to inspect the dependency graph of your tasks. For example, you can run the following command to see all the dependencies of the hello binary:

$ bazel query 'deps(//:hello)'
//:hello
//:hello_lib
@bazel_tools//tools/jdk:current_java_runtime
@bazel_tools//tools/jdk:current_host_java_runtime
@bazel_tools//tools/jdk:java
@bazel_tools//tools/jdk:jdk
@bazel_tools//tools/jdk:jar
@bazel_tools//tools/jdk:java_toolchain
@bazel_tools//tools/jdk:toolchain_type
@bazel_tools//tools/jdk:remote_jdk11
@bazel_tools//tools/jdk:remote_jdk
@bazel_tools//tools/jdk:remotejdk11
@bazel_tools

How can I visualize the dependency graph?

Bazel provides a query language that allows you to inspect the dependency graph of your targets. You can use the bazel query command to run queries and get various outputs.

For example, if you want to see all the dependencies of the //:main target and format the output as a graph, you can run this command:

$ bazel query 'deps(//:main)' --output graph > graph.in

This creates a file called graph.in, which is a text representation of the build graph. You can use dot (install with sudo brew install graphviz) to create a png from this:

$ dot -Tpng < graph.in > graph.png

This will generate a file called graph.png, which is a visual representation of the build graph. You can open it with any image viewer and see the dependency structure of your target.

What is bazel genrule? When to use it?

A Bazel genrule is a versatile tool used to generate one or more files as part of your build process. Unlike other built-in Bazel rules which handle specific tasks like compiling code or copying resources, genrule offers flexible execution through user-defined Bash commands. This makes it suitable for a wide range of scenarios where a dedicated rule doesn’t exist or wouldn’t be efficient.

Here’s a breakdown of what a genrule can do:

  1. Generate Files: Use it to create files like configuration files, data files, or pre-processed source code based on templates or scripts. Define the output files (outs) your command produces.
  2. Run Custom Commands: Execute arbitrary Bash commands within the genrule, allowing you to leverage any shell tools or external programs. Specify the command (cmd) to run and the inputs (srcs) it needs.

For example, if you want to generate a header file that contains the current date and time, you can use a genrule like this:

genrule(
name = "date_header",
outs = ["date.h"],
cmd = "echo '#define DATE \"$(date)\"' > $@",
)

This tells Bazel to create a file called date.h that contains a macro definition of the current date and time.
The $@ variable refers to the output file.

Conclusion

Bazel is a great tool for managing your build process. It can handle complex dependencies, parallel execution, caching, and more. In this blog post, I answered some common questions about using Bazel and showed you how to get started with it. I hope you found it useful and learned something new.

--

--