$ bazel aquery //foo/bar:baz
Working with Bazel Last updated May 14, 2022
Quickstart: the quickest way to get started is to clone and run some of the demos.
Overview
Developing software with Bazel is pretty much like developing software with any other build system: lather, rinse, repeat. You edit your sources, execute a build command of some kind, run a test driver or executable to verify results, and repeat.
The major differences are of course related to the build program and the build engine. Bazel build programs are written in the Starlark language, and Bazel is also the name of the build engine (of which there is only one.)
Bazel provides a great deal of information about your build program, and it provides fine-grained control over build actions. You can build any target in your project, and only it and its dependencies will be built. You can parameterize your builds at any level of granularity. If you include appropriate test targets, you can modularize your development even if your source code is not organized in a modular manner.
Furthermore, Bazel includes powerful query facilities that make it possible to explore the dependency structures of your code. You can easily list the dependency chain between two targets, or list the targets that have a certain parameter, etc. You can generate SVG graphs showing dependency graphs of your code.
Bazel also makes it easy to develop multiple, mutually-dependent projects simultaneously. If your main project depends on an external repository, you can easily configure your build to use a local copy of the dependency without altering your build code. One benefit of this is that it makes it easy to eliminate embedded git submodules.
Bazel
OBazl recommends using Bazelisk (Installing Bazel using Bazelisk). |
A great deal of Bazel documentation is available, but it is not always easy to find what you need. If you do find it, you probably want to bookmark it. |
If you use Bazelisk, you can pin the Bazel version by putting file
.bazelversion
in the root directory of your project, containing the
required version string, e.g. latest
or 5.1.1
.
To work effectively with OBazl you must master the following material at minimum:
-
Labels (See also:
$ bazel help target-syntax
)
If you are just getting started with Bazel, you should work through one of the Tutorials
OBazl
OBazl deviates from standard Bazel conventions in a few minor ways:
-
Rules that build executable binaries are named
*_executable
, not*_binary
:ocaml_executable
,ppx_executable
-
The library rules
ocaml_library
does not build a "separately compiled module". Instead it provides a simple aggregation mechanism, so that you can depend on a collection of resources under a single name. In other words, OBazl takes the term "library" to mean "collection of resources"; the resources will almost always be OCaml compiled modules, but may include e.g. runtime data dependencies. The "library" produced byocaml_library
is a Starlark structure, which has no OCaml counterpart.
Prerequisites
-
Platform: Linux or MacOS. Both Bazel and OCaml support windows, so OBazl may work, but it has not been tested and there are no plans to do so. However, it should not be terribly difficult to port it to Windows. If you have the time and energy to work on this please reach out on the OBazl Discord server and I will be happy to help.
-
Build tools. The OCaml toolchain depends on a C/C++ compiler and tools.
-
OPAM installation. The rules have been used with version 2.0.7. Earlier versions may work; if not, please file an issue.
-
An OPAM switch containing the OCaml compiler and OPAM packages your project needs.
-
Currently the OBazl rules only support an OPAM toolchain installed on the local host outside of Bazel’s control; they do not support fully hermetic builds, where all build inputs including toolchains are exclusively controlled by Bazel. A future version of the OBazl rules will automatically download and install the entire OCaml toolchain and required packages into a private, Bazel-controlled cache.
-
Instruction for configuring an OPAM switch is beyond the scope of this guide, but here are some common useful commands:
-
$ opam switch show
- prints the name of the current switch -
$ opam switch
- shows all installed switches -
$ opam list
- shows all packages installed in the current switch -
$ opam pin list
- shows all "pinned" packages -
$ opam config list
- lists all general configuration settings -
$ opam config list ounit2
- lists all configuration settings for packageounit2
-
$ opam var
- same asopam config list
-
$ opam var bin
- print the value of thebin
var of the current switch -
$ opam var --package <pkg>
- prints all variables for package<pkg>
as<pkg>:<var>
-
$ opam var <pkg>:<var>
- prints value of variable for package, e.g.-
$ opam var ounit2:version
- prints version string of packageounit2
-
-
-
"opam var --package …" may be very, very slow. |
-
OPAM switch names commonly match the version string of the installed compiler, but this is not required; you can name your switches however you wish. In that case, here are some useful commands, using
--switch <s>
to pick out a switch:-
$ opam list --switch myswitch
- list packages installed for switchmyswitch
-
$ opam config list --switch myswitch
- prints variables for switchmyswitch
. -
$ opam var --package ocaml --switch myswitch
- prints variables for theocaml
package of switchmyswitch
-
$ opam var ocaml:version --switch myswitch
- printsversion
variable of theocaml
package of switchmyswitch
-
$ opam var ocaml:depends --switch myswitch
- printsdepends
variable (i.e. compiler build options) of theocaml
package of switchmyswitch
-
-
$ opam var --package ocaml-base-compiler --switch myswitch
- prints variables for theocaml-base-compiler
package of switchmyswitch
-
Compiler versions are treated as (pseudo) packages. When you run
$ opam switch you will see that the "compiler" column lists strings of
form <pkgname>.<varname> ; for example, ocaml-base-compiler.4.12.0 .
If you have installed a compiler with +options the string will look
like ocaml-variants.4.14.0+options . Use the <pkgname> part to obtain information about the compiler, e.g. $ opam var --package ocaml-base-compiler:version --switch myswitch .
|
The ocaml-base-compiler variable is unreliable! Don’t use it unless it is listed in the output of $ opam config list --switch myswitch .
|
-
If you use
emacs
, you probably want to installmerlin
. -
Locally installed (system) libraries. Some OPAM packages depend on locally installed resources. For example, package
bignum
depends on packagezarith
, which depends on a local installation oflibgmp
(usually in/usr/local
).
Setup
To get the most out of OBazl and Bazel, you need to decide on some conventions and do a little configuration. See OBazl Conventions for a list.
Inspecting the Bazel environment, logs, build actions, etc.
bazel info
The bazel info
command will print a dictionary listing the
parameters, file locations, etc. that Bazel uses internally. It
supports a large number of options; run $ bazel help info
to see them
all; to see just the keys for the dictionary, run $ bazel help info-keys
.
Most of entries in the dictionary, most of the time, can be safely
ignored; but if you run into trouble, two of them can be helpful with
debugging: command_log
and output_base
.
command_log
Bazel writes logs to a command_log
file each time it executes a
command; it overwrites the file. You can discover the location of the
file by running $ bazel info command_log
. Since the output of this
command will overwrite the log file, you must use an alias or shell
script to enable easy browsing. See the aliases
recommendation in OBazl Conventions for an example.
output_base
The output_base
directory contains a subdirectory, external
, that
contains the external repositories your project has configured. You
can browse the BUILD.bazel
files of an external repo, for example,
to verify that you are using the correct target labels.
targets
Bazel can print a source text representation of each target :
$ bazel query '@ppx_tools//:*' --output build
This shows what the target looks like to Bazel after it has been processed (e.g. variables expanded, etc.).
actions
A single build target may generate multiple build actions. For
example, if an ocaml_module
rule is parameterized with a ppx
argument, it will generate two actions: one to transform the source
file with the PPX, and one to compile the result. Each action will
have a command line string.
Normally there is no need to pay these actions any mind, but if
something goes wrong with your build it may be useful to see exactly
what a build rule is doing - what the actions are, what commands and
arguments are used to run the actions, and what the inputs and outputs
are. Fortunately this is easy to do. You can use the [action query]()
facility to print all the actions generated by a rule without actually
running the rule (so it does not trigger any compilation). For
example, the following will print all the actions (and much additional
information) generated by the //foo/bar:baz
target:
See Transparency for more information.
packages
To list all the files in the directory containing associated with a package:
$ bazel query 'kind("source file", @<repo>//<pkg>:*)' --output label_kind
To include all subpackages (subdirectories containing BUILD.bazel
file), use @<repo>//<pkg>/...:*
instead of @<repo>//<pkg>:*
.
E.g.
$ bazel query 'kind("source file", @ppx_tools//:*)' --output label_kind
$ bazel query 'kind("source file", @ppx_tools//metaquot:*)' --output label_kind
Compile/link commands
todo…
Useful tips
-
The
clean
command "[r]emoves bazel-created output, including all object files, and bazel metadata." It will not refresh repository dependencies. Adding the--expunge
option will delete everything; it will also stop the server, so that then next build command will start from scratch. You almost never need to do this. -
You should rarely need to run
$ bazel clean
. Bazel caches a complete description of the build, so it always knows what needs to be rebuilt. However, if you change the build structure - especially if you remove build targets - you may need this command to rebuild the cache. -
Do spend some time learning to use the query facilities. On a project of any size you’ll be glad you did.
-
To experiment with build rules etc. you can avoid cluttering the source tree by creating a
BUILD.bazel
in a work directory likedev
and putting the rules there. Since dependencies are expressed as target labels, you can reach into the tree anywhere you like, although you may need to adjust thevisibility
attribute of targets. -
Use Bazelisk to make sure you’re always using the latest version of Bazel. You can pin the version you want by using a
.bazelversion
file. -
You can enable command-line completion (also known as tab-completion) in Bash and Zsh. This lets you tab-complete command names, flags names and flag values, and target names. Caveat: tab-completion may be an issue for Bazelisk; see Support bash autocomplete #29.)
-
If you need to make some kind of global change, e.g. renaming a target or adding a dependencie to multiple rules, do not search-and-replace. Use buildozer instead. (See Batch Editing for more information.)
Working with external repositories
Note in particular: Transitive dependencies
To coordinate development of a main directory and external dependencies, you can override the declared repositories. See Overriding repositories from the command line.
Put your --override
directives in your user.bazelrc
file (by convention, dev/user.bazelrc
), and load it from .bazelrc
with the following line: try-import dev/user.bazelrc