The purpose of this workshop is to show Agent developers
how to interact with bazel. We are mainly focusing on
go since other parts, such as dealing with native dependencies,
will be mainly in scope of the Agent Build team. It is not expected
that participants of this workshop have prior bazel experience.
In fact, our intention is to show how easy it actually is to get started
working with it.
Please, take some time to answer some questions about your experience dealing with build systems and this workshop.
- Install
bazelisk. We want to be able to bump bazel's version whenever we need. Major LTE versions are released every year and usually bring UX and performance improvements. Sometimes, it is also necessary upgrade to new patch or minor versions to fix bugs.bazeliskreads the.bazelversionfile and automatically pulls the version that is needed by this particular project.
$> which bazelisk
# If you have no installation then do so
$> brew install bazelisk$> winget install Bazel.Bazeliskor
$> choco install bazeliskor
$> scoop install bazeliskYou can either download an executable from releases or a debian package (if applicable) and run:
$> dpkg -i bazelisk-<arch>.debAs a last resort you can always download executables for your platform from releases
- Create a
.bazelversionfile at the root of the repository. The file is read bybazeliskto pull required bazel's version. This frees users from having to manually manage the version of the build system and avoids versions drifting between developer machines and with CI. It also lets us ensure that going to old branches doesn't break the build process:
# We are using latest stable version
8.3.1
You can confirm that this works as expected by running:
$> bazel --version- Create a
BUILD.bazelfile in the root of the project. BUILD files letbazelknow what is considered a package. The presence of the BUILD file in a directory makes the content of that directory visible tobazel. Add the following to the./BUILD.bazelfile:
load("@gazelle//:def.bzl", "gazelle")
gazelle(name = "gazelle")- Run
gazelle. As you may have noticed in the previous step we added some magical lines mentioninggazelle. It is mentioned inMODULE.bazelas well as inBUILD.bazel.gazelleis a BUILD file generator tool forbazel. Go projects' structure is usually very straight forward and go's build system is modern enough to rely on it instead of re-inventing the wheel again. That means that most of the time we don't need to interact with BUILD files directly and can just letgazelledo its work:
$> bazel run //:gazelle
# Now let's run git to show us generated files
$> git statusYou can take a look at the generated BUILD.bazel files to get a feel for what they look like, even though, as just mentioned, most of the time you won't need to make changes to them.
The contents can be fairly easy to understand most of the time even without knowing much about Bazel.
# Let's try to build and test the project
$> bazel build //...
# Execute unit tests
$> bazel test //...- Run the demo application:
$> bazel run //cmd:cmdThis is a "freestyle" exercise. You can however just follow the presenter.
- Create a new folder within ./pkg of your choice
- Create a new
.gosource file of your choice and implement some logic. Keep it independent from the rest of the packages for now. - Now you need to make bazel aware of your newly created package. To do so just run gazelle.
# This will shrink gazelle's scope to speed up the process and not go
# through the entire project. In our case we could also run gazelle
# without specifying path as this project is very simple and small.
$> bazel run //:gazelle -- /pkg/<new_pkg>- (Optional) Now let's try to import our new package into already existing code. Let's see what happens if we run the same command as above:
$> bazel run //:gazelle -- /pkg/<new_pkg>As you see nothing has changed in the <new_pkg>. So we need to update the package
where we imported our new package:
# Example
$> bazel run //:gazelle -- /pkg/server # In case changes were made in server
# Template command
$> bazel run //:gazelle -- /pkg/<changed_pkg>
# Now let's see if BUILD file was updated accordingly
$> git diffFor external dependency management, bazel uses a built-in system called bzlmod,
which works similarly to other modern dependency managers you may already be familiar with. The MODULE.bazel file at the root of a project
marks it as a Bazel module, and it's where dependencies are defined.
It is possible to import other MODULE files inside the same project as a way to split the contents
across files, but it is important to have at least one MODULE.bazel file at the root as it's what bazel
uses as repository boundary markers.
Let's now check how we can handle external go dependencies. Recommended way is to store the list directly in go.mod.
- Let's add an external dependency to go.mod:
require github.com/shopspring/decimal v1.3.1- Let's add a simple function into a newly created source in
pkg/math/operator.go:
import "github.com/shopspring/decimal"
func (o Operator) DecimalApply(a, b decimal.Decimal) (decimal.Decimal, error) {
switch o {
case Add:
return a.Add(b), nil
case Subtract:
return a.Sub(b), nil
}
return decimal.Decimal{}, fmt.Errorf("invalid operator: %s", o)
}- We need to tell bazel to pull the list of external dependencies from go.mod file. To do that we need to add the following lines to MODULE.bazel
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
)
- Now we should pull the dependency:
$> bazel run @rules_go//go -- mod tidy -v
# Run gazelle to update BUILD files accordingly
$> bazel run //:gazellebazel has a built-in query language that allows to analyze the relations between
targets and even actions. This time we will focus on the former and try out cquery.
For instance, if we want to get the full list of dependencies for a particular target
we can just run:
# Let's check the dependencies of our cmd target
$> bazel cquery "deps(//cmd:cmd)" --notool_deps
# Now let's find all test targets
bazel cquery "kind(go_test, //...)" bazel is not only powerful when it comes to building, but also very helpful running
tests. It allows us:
- to set how many times we should run certain tests or all of them by setting --runs_per_test
- to automatically detect flaky tests by setting --runs_per_test_detects_flakes. When used in combination with the flag above it will not fail the results of
bazel testcommands, but will mark failing tests asFLAKYinstead ofFAILED - to set timeouts and compute resources based on size attribute
- to mark tests known to fail now and then as flaky by setting
flaky = Trueattribute to the*_testtarget.
Now let's actually ask bazel to run tests several times to see if we have any failing tests.
$> bazel test //... --runs_per_test=10Now let's see if failing tests are failing constantly or flaky.
$> bazel test //... --runs_per_test=10 --runs_per_test_detects_flakesAnd now let's mark our known test as flaky in ./pkg/stock/BUILD.bazel:
go_test(
name = "stock_test",
srcs = [
"client_test.go",
"service_test.go",
],
embed = [":stock"],
deps = [
"//internal/testutils",
"//pkg/models",
],
flaky = True,
)- Bazel command line reference - overview of all flags available in bazel. Keep in mind that flags may change based on bazel's version so it is recommended to use versioned docs (see navigation bar)
- Working with Go in bazel - must read for anyone who is planning to use bazel with their codebase.
- Bazel Central Registry - even though
bzlmodis still fresh and a lot of legacy projects are still relying onWORKSPACEapproach more and more modules and tools are added to the Central Registry, so before thinking on "bazelization" of a new tool or a dependency it's worth checking if it isn't already present in BCR. - Reasoning and migration guide for bzlmod - we are starting fresh in the Agent, therefore, we will use
bzlmodright away, so you won't have to deal withWORKSPACE, however it's worth reading this article to understand the background. - Bazel Query Language - official documentation about query language and built-in functions and features.