Hello Bazel
October 8th, 2022
Bazel is a powerful tool that becomes a joy to use. Unfortunately this is only after suffering through a painful learning curve. For me I did a lot of hair pulling before beginning to grok how to work with Bazel effectively. The purpose of this post is to share some thoughts on getting started with Bazel as well as some useful introductory concepts. The first thing you will need to do is install Bazel on your machine. To do this you can visit the Bazel download page here.
To confirm your installation you can run: bazel --version
Next we will create a new directory and chage our current working directory to be the newly created directory.
mkdir hello-bazel && cd hello-bazel
The final step before we can build bazel targets is to define our
MODULE.bazel and root BUILD.bazel files.
touch MODULE.bazel BUILD.bazel
This will create empty configuration files that Bazel will use.
Finally we can build all Bazel targets.
bazel build ...
The ... in this command is bazel shorthand for recursively
match all targets. It is common to prefix this with a path to a parent
directory to build subtrees of your project. Ex:
bazel build a/b/c/...
If everything goes according to plan you should see some output like this:
bazel build ...
INFO: Analyzed 0 targets (1 packages loaded, 0 targets configured).
INFO: Found 0 targets...
INFO: Elapsed time: 0.511s, Critical Path: 0.01s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
Congratulations! 🎉 you just ran your first successful Bazel build. Now let's dig in a bit more.
List the files in your current working directory with ls
ls
BUILD.bazel bazel-hello-bazel
MODULE.bazel bazel-out
bazel-bin bazel-testlogs
You will see the original BUILD.bazel and
MODULE.bazel files that we created above. Additionally there
are the bazel- prefixed directories that Bazel will use for
various purposes. For now you should just be aware that they exist and are
managed by Bazel.
Example: Bazel Executable Rule
Next let's build something a bit closer to useful. How about a simple bash
executable to print "Hello World!". For this we will
write a Bazel executable rule with the target name
"hello-world".
First we will create a new file called index.bzl with the
following content:
def _hello(ctx):
executable = ctx.actions.declare_file("hello-world.sh")
ctx.actions.write(
executable,
"""
#!/bin/bash
echo Hello World!
""",
)
return [DefaultInfo(executable=executable)]
hello = rule(
executable=True,
implementation=_hello,
)
In the above snippet we are defining an executable rule named
hello with an implementation _hello. In the
implementation we are declaring the output file
hello-world.sh with the literal file content as shown.
Next we need to load the hello rule and define a target. Update the
BUILD.bazel file with the following content:
load("index.bzl", "hello")
hello(name = "hello-world")
Now we are ready to execute.
bazel run :hello-world
You should see output like:
bazel run :hello-world
INFO: Analyzed target //:hello-world (4 packages loaded, 6 targets configured).
INFO: Found 1 target...
Target //:hello-world up-to-date:
bazel-bin/hello-world.sh
INFO: Elapsed time: 0.583s, Critical Path: 0.04s
INFO: 4 processes: 4 internal.
INFO: Build completed successfully, 4 total actions
INFO: Build completed successfully, 4 total actions
Hello World!
Fantastic! ⭐️ I think you're starting to get it. Now let's take a quick look under the hood.
Your bazel-bin directory should now have a copy of
hello-world.sh. As you might have expected you can execute this
file directly with bazel-bin/hello-world.sh.
bazel-bin/hello-world.sh
Hello World!
Let's continue on with a more practical example of building file archives
Example: Archive Bazel Rules
It is very common to deal with archives when working with build systems. The
following code snippets will demonstrate how to create zip and
tar archives using Bazel rules.
First we will create the archive.bzl rules file with the
following content:
def _zip(ctx):
zip_out = ctx.actions.declare_file("{name}.zip".format(name=ctx.label.name))
ctx.actions.run_shell(
outputs=[zip_out],
inputs=ctx.files.srcs,
arguments=[f.path for f in ctx.files.srcs],
command="zip -r {out} $@".format(out=zip_out.path),
)
return [DefaultInfo(files = depset([zip_out]))]
zip = rule(
implementation=_zip,
attrs={
"srcs": attr.label_list(
allow_empty=False,
allow_files=True,
mandatory=True,
)
},
)
def _tar(ctx):
tar_out = ctx.actions.declare_file("{name}.tar.xz".format(name=ctx.label.name))
ctx.actions.run_shell(
outputs=[tar_out],
inputs=ctx.files.srcs,
arguments=[f.path for f in ctx.files.srcs],
command="tar cfJ {out} $@".format(out=tar_out.path),
)
return [DefaultInfo(files = depset([tar_out]))]
tar = rule(
implementation=_tar,
attrs={
"srcs": attr.label_list(
allow_empty=False,
allow_files=True,
mandatory=True,
)
},
)
In both implementations we simply execute the system binary for tar or zip
passing in some default options and the specified source files as arguments.
The important Bazel bits to take away here is that it is crucial to define
the inputs and outputs for the run_shell action because
only the files defined here will be available in the execution context.
Consider that the filepath f.path that is used above will not
be the path to the file in your working directory but rather a Bazel build
directory where the rule will be executed.
Now we can write targets to invoke the tar and zip archive rules. Update
your BUILD.bazel with the following content:
load("archive.bzl", "zip", "tar")
zip(
name = "zip_example",
srcs = [
":BUILD.bazel",
":hello-world",
],
)
tar(
name = "tar_example",
srcs = [
":BUILD.bazel",
":hello-world",
],
)
In the above snippet we defined two Bazel targets
zip_example and tar_example which will take as
sources the BUILD.bazel file and the output file from the
previously written hello-world rule.
Finally, we can build these targets with:
bazel build :zip_example
bazel build :tar_example
Hooray! 🥳 Everything worked just as expected the first time!
This will produce the output files
bazel-bin/zip_example.zip and
bazel-bin/tar_example.tar.xz. You can un-archive those files in
the typical way to inspect the content.
I hope this short post is enough for you to get started working with Bazel and to get the gears in your head turning as to how Bazel may be useful in your future projects.
Access the code content used in this post here: