This set of exercises is a practical introduction to building polyglot projects with Bazel, supported by Nix for reproducible, hermetic build environments. It is self-contained and works on Linux and macOS.
Please open an issue if you have questions or encounter problems running the examples.
Please make a pull request if you found and fixed an issue.
This codelab is based on Google's Bazel Codelab. It has been adapted to
- Use more recent versions of the required Bazel rule sets.
- Follow best practices such as formatting with
buildifier
or usingBUILD.bazel
files instead ofBUILD
files. - Use Nix to provide Bazel and other developer tools in a Nix shell.
- Use
rules_nixpkgs
to import Nix provided toolchains fromnixpkgs
.
direnv
used to automatically set up shell environment, see.envrc
.- Nix shell used to provide developer tools like Bazel itself, see
shell.nix
- Nix dependencies pinned under
nix/
. - Bazel layout
MODULE.bazel
defines root of Bazel project and defines external module dependencies- Uses
bazel_dep
to import Bazel dependencies from the Bazel Central Registry (BCR) - Uses
register_toolchains
to register toolchains from nixpkgs - Uses
use_repo
to bring repositories into scope - Uses other rule sets' dedicated module extensions to import their dependencies, e.g. Go or NodeJS.
- Uses
non_module_dependencies.bzl
contains legacy WORKSPACE rules that are not available as native module extensions- Uses
nixpkgs_*_configure
to create toolchains - Can use
http_archive
to import Bazel dependencies - Can use
nixpkgs_package
to import Nix dependencies into Bazel - Can use other rule sets' dedicated repository rules to import their dependencies
- Uses
.bazelrc
configures BazelBUILD.bazel
files define Bazel packages and targets in that package
The repository contains bazel build files that are known to work, they are shipped in the solutions
subdirectory of the project.
A script named lab
is added to your environment with nix-shell
, it helps using the solutions files.
lab
can be used in multiple ways:
lab help
to get help and examples of uselab compare $file
to view a difference between your file and the solution if existslab display $file
to view its solution if anylab install $file
to copy the solution if any (this will replace your work!)lab install_all
install all solutions file, the project should compile fine after running this command
Read up on Bazel Concepts and Terminology.
Install the Nix package manager with
sh <(curl -L https://nixos.org/nix/install) --daemon
All commands from the instructions are expected to run in the environment created by entering
nix-shell
If you want to use your favorite shell (e.g. fish
) installed on your system:
nix-shell --run fish
Use direnv
to automatically set up the environment when changing to this project's directory.
This project contains missing pieces that you will have to write, with some guidance, to make it compile again.
In the files that require editing, you will find comments such as # section 1 code to add here
helping you ensure that you are looking at the right place for the current exercise.
The lab
script can be used if you need to compare your changes with the solution.
Feel free to open an issue to ask questions, or if you think something is wrong in the codelab.
Build an example Java application that prints
Welcome to the Bootcamp! Let's get going :)
to the console.
- Edit
java/src/main/java/bazel/bootcamp/BUILD.bazel
- Add a binary target for
HelloBazelBootcamp.java
- use
java_binary()
- name it
HelloBazelBootcamp
- use
- Run the binary using
bazel run //java/src/main/java/bazel/bootcamp:HelloBazelBootcamp
- WARNING: The first time you run this command, it may take quite a long time. Bazel is downloading and installing all the dependencies required to build a Java project.
Build a Go server that receives log messages in the format defined in proto/logger/logger.proto
.
- Edit
proto/logger/BUILD.bazel
- Add a target for the protocol description
- use
proto_library()
- name it
logger_proto
- ignore the deprecation warning in the documentation, the API is still valid
- use
- Add a library target for a Go
protobuf
library based onlogger.proto
- use
go_proto_library()
- name it
logger_go_proto
importpath
- allows importing the library from a known package path in
server.go
- allows importing the library from a known package path in
compilers
- setting to
["@rules_go//proto:go_grpc"]
builds functions implementing theLogger
service forgRPC
- setting to
- name it
- use
- Add a target for the protocol description
- Edit
go/cmd/server/BUILD.bazel
- The
go_library
targetserver_lib
is already written for you - Add a binary target from
server.go
- use
go_binary()
- name it
go-server
- it should be compiled together with the sources of
server_lib
- use
- The
- Run the go binary using
bazel run //go/cmd/server:go-server
run
implies thebuild
step
- Check
http://localhost:8081
- it should display
"No log messages received yet."
- it should display
Build a Java client which sends log messages to the server, in the format defined in proto/logger/logger.proto
.
-
Edit
proto/logger/BUILD.bazel
- Add a Java library for data structures in
logger.proto
- use
java_proto_library()
- name it
logger_java_proto
- use
- Add a Java library implementing the
gRPC
serviceLogger
defined inlogger.proto
- use
java_grpc_library()
- name it
logger_java_grpc
- use
- Add a Java library for data structures in
-
Edit
java/src/main/java/bazel/bootcamp/BUILD.bazel
- Add a Java library implementing the logging client
- use
java_library()
- name it
JavaLoggingClientLibrary
- use
JavaLoggingClientLibrary.java
as source - declare depencies on
*_proto
and*_grpc
targets created in previous steps - since
JavaLoggingClientLibrary
uses theio.grpc
package, you will also need to declare a dependency on@grpc-java//core
, as well as a transport impementation such as@grpc-java//netty
.
- use
- Add a Java binary for the client
- use
java_binary()
- name it
JavaLoggingClient
- use
JavaLoggingClient.java
as source - declare a dependency on
JavaLoggingClientLibrary
- use
- Add a Java library implementing the logging client
-
Run the Java binary with
bazel run //java/src/main/java/bazel/bootcamp:JavaLoggingClient
- workaround for an open
nixpkgs
issue on macOSNote that it will not work by fixingenv CC=gcc bazel run //java/src/main/java/bazel/bootcamp:JavaLoggingClient
CC
to eitherclang
orclang++
, as bothC
andC++
dependencies are present!
- workaround for an open
-
Run the Go binary from Section 2 from another
nix-shell
-
Type messages to send from the client to the server and view them on
http://localhost:8081
- Edit
java/src/main/java/bazel/bootcamp/BUILD.bazel
- Add a test target for
JavaLoggingClientLibrary
java_test()
- name it
JavaLoggingClientLibraryTest
- names matter for tests!
- target name, test class, and file name must be identical
- use
JavaLoggingClientLibraryTest.java
as source - add
JavaLoggingClientLibrary
target from Section 3 as dependency
- Repeat the same for the client binary
JavaLoggingClient
- Add a test target for
- Run tests
- NOTE: this is not a unit test, it depends on the server to make any sense.
- If you skipped section 2 exercise, you can simply run
lab install go/cmd/server/BUILD.bazel
to get the solution of this exercise andbazel run //go/cmd/server:go-server
to run it.
- If you skipped section 2 exercise, you can simply run
- start Go server in another
nix-shell
, as in Section 2 - run Java client tests
bazel test //java/src/main/java/bazel/bootcamp:JavaLoggingClientLibraryTest
- NOTE: If you simply turn off the server and rerun the test,
you will most likely see that the test passed, with the information that it is "cached".
Indeed, Bazel does not rerun the tests if none of its dependencies changed.
If you really want to rerun it to acknowledge that it fails when the server is off,
you have to specify
--cache_test_results=no
in the command line.
- NOTE: If you simply turn off the server and rerun the test,
you will most likely see that the test passed, with the information that it is "cached".
Indeed, Bazel does not rerun the tests if none of its dependencies changed.
If you really want to rerun it to acknowledge that it fails when the server is off,
you have to specify
- Do not forget to run the tests of
JavaLoggingClient
- NOTE: Those tests do not use the server.
- NOTE: this is not a unit test, it depends on the server to make any sense.
- Edit
proto/logger/BUILD.bazel
- Add a TypeScript
protobuf
library implementinglogger.proto
- use
ts_proto_library()
- name it
logger_ts_proto
- use
- Add a TypeScript
- Edit
typescript/BUILD.bazel
- Add a target for the TypeScript app
app.ts
- use
ts_project()
- add
logger_ts_proto
as a dependency - set
transpiler = "tsc"
- you need to reference the local tsconfig.json
- use
- Bundle the JavaScript modules using esbuild
- use esbuild()
- name it
bundle
- set a valid entrypoint (ts_project generates a file with the ending
.js
)
- Add a target for a development server
- use
js_run_devserver()
- name it
devserver
- add the
esbuild
target andindex.html
asdata
dependencies - set
args = ["typescript"]
to serve files from thetypescript
directory - set
tool = ":http_server"
to make it work correctly
- use
- Add a target for the TypeScript app
- Run the webserver using
bazel run //typescript:devserver
- it will print a link which you can click on
- if the link does not work, try
http://localhost:5432
- Run the Go server and Java client from the previous steps.
- send messages from the Java client to the Go server
- click the button on the web front end to see them in your web browser
- Edit
tests/BUILD.bazel
- add a shell test target for
integrationtest.sh
- use
sh_test
- add the Go server and Java client targets from previous steps as
data
dependencies
- add a shell test target for
- Run the test using
bazel test <target>
and make sure that it passes- NOTE: You may have to modify your solution of Section 2 and 3 to make those tests pass. In particular, you most likely did not set the visibility of the binaries.
- Run the test multiple times using
bazel test <target> --runs_per_test=10
- QUESTION: Does it pass? If not, can you figure out why?
- HINT: the section on test run behaviour in the
tags
documentation may prove useful.
- Edit
BUILD.bazel
- Add a
load
instruction to import the rulebuildifier
.- NOTE: the documentation linked above assumes the use of the upstream buildifier rules. This project currently depends on a fork that ships precompiled binaries and is already available as a Bazel module.
- Create a target called
buildifier-print
which warns about lint error and only prints the changesbuildifier
would make.- NOTE: Adding such a rule will provide a warning that using
mode = "diff"
is deprecated, and suggesting to use abuildifier_test
rule instead. There is a known issue preventing abuildifier_test
from being run on every folders of a project, hence, for now, let's simply ignore this deprecation warning.
- NOTE: Adding such a rule will provide a warning that using
- Now create a target called
buildifier-fix
which fixes all the formatting errors (including linting ones). - Run the 2 targets to format your solution to the exercises.
- Add a
- Read the Bazel Query How-To and try the examples to display the dependency graph of the packages in this exercise.
- SOLUTION: This exercise is solved here.