Occasionally, I think "it would be nice if everything could be built together in one tool", and I inevitable think of bazel. Well this is my 2023 attempt to try and use it.
We start with not bazel itself, but bazelisk,
a wrapper around bazel
that will handle downloading and running the correct version of bazel
for you.
Obtain bazelisk
through magic (like OS repositories), install it as bazel
,
and we're good to go.
We'll want to choose a version of bazel
though
and record that in .bazelversion
for bazelisk
to pick up:
1$ echo 6.4.0 > .bazelversion
Next we need to define a bazel workspace,
the root of everything that will be built by bazel
.
The previous link used WORKSPACE
or WORKSPACE.bazel
,
which is also where you'd put all the directives to download external dependencies,
but bazel is in the process of moving to modules
where dependencies come from central registries like registry.bazel.build,
so we can replace that with a MODULE.bazel
file.
We declare a module using module:
1# MODULE.bazel
2
3module(name = "example_repo1", version = "0.0.1")
Bazel modules also have to be enabled with a flag right now,
which can be passed to every command via .bazelrc
:
1$ echo "common --enable_bzlmod" > .bazelrc
There are some tools that might be considered almost mandatory:
BUILD
filesBUILD
files from other places like go.mod
From registry.bazel.build we can find
buildifier_prebuilt
and gazelle.
We can add them to MODULE.bazel
using bazel_dep:
1# MODULE.bazel
2
3bazel_dep(name = "gazelle", version = "0.33.0", dev_dependency = True)
4bazel_dep(name = "buildifier_prebuilt", version = "6.3.3", dev_dependency = True)
Finally, we can load extensions and register targets in BUILD.bazel
.
1# BUILD.bazel
2
3load("@gazelle//:def.bzl", "gazelle")
4gazelle(name = "gazelle")
5
6load("@buildifier_prebuilt//:rules.bzl", "buildifier")
7buildifier(name = "buildifier")
This should mean we can run commands like:
1$ bazel run //:buildifier # formats files
Time to write some Go code.
We'll still try to be compatible with vanilla go
(and it's nicer to manage deps this way) so:
1$ go mod init repo1.example
and let's write some very simple code:
1$ mkdir helloworld
2$ cat << EOF > helloworld/main.go
3package main
4
5import "fmt"
6
7func main() {
8 fmt.Println("hello world")
9}
10EOF
We can run it with go
:
1$ go run ./helloworld
But to run it with bazel
we'll need some rules for building go,
specifically rules_go:
1# MODULE.bazel
2
3bazel_dep(name = "rules_go", version = "0.42.0")
Then we can generate the BUILD.bazel
files with gazelle:
1$ bazel run //:gazelle
2
3# results
4$ cat helloworld/BUILD.bazel
5load("@rules_go//go:def.bzl", "go_binary", "go_library")
6
7go_library(
8 name = "helloworld_lib",
9 srcs = ["main.go"],
10 importpath = "repo1.example/helloworld",
11 visibility = ["//visibility:private"],
12)
13
14go_binary(
15 name = "helloworld",
16 embed = [":helloworld_lib"],
17 visibility = ["//visibility:public"],
18)
19
20# build and run
21$ bazel run //helloworld
22
23# or without build logs
24$ bazel run --ui_event_filters=-info,-stdout,-stderr --noshow_progress //helloworld
Just run bazel run //:gazelle
every time new packages are created or imports change.
Now for external dependencies. We start with a simple piece of code borrowed from the Go tutorial
1$ mkdir helloworld2
2$ cat << EOF > helloworld2/main.go
3package main
4
5import (
6 "fmt"
7
8 "rsc.io/quote"
9)
10
11func main() {
12 fmt.Println(quote.Go())
13}
14EOF
We'll want to run go mod tidy
to get the dep in go.mod
:
1$ bazel run @rules_go//go -- mod tidy
We'll also want to use gazelle to sync deps from go.mod
into the bazel module system:
1# MODULE.bazel
2go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
3go_deps.from_file(go_mod = "//:go.mod")
Then we can run gazelle, which should give us a warning about needing to actually register the module
1$ bazel run //:gazelle
2WARNING: /home/arccy/tmp/testrepo0416/MODULE.bazel:10:24: The module extension go_deps defined in @gazelle//:extensions.bzl reported incorrect imports of repositories via use_repo():
3
4Not imported, but reported as direct dependencies by the extension (may cause the build to fail):
5 io_rsc_quote
6
7 ** You can use the following buildozer command to fix these issues:
8
9buildozer 'use_repo_add @gazelle//:extensions.bzl go_deps io_rsc_quote' //MODULE.bazel:all
Run the suggested command to add the dep to MODULE.bazel
which should now have use_repo(go_deps, "io_rsc_quote")
.
And we should finally be able to run:
1$ bazel run //helloworld2
Note that it appears we can't run buildozer
via bazel
itself because we can't escape the sandbox,
and I don't understand enough of bazel
to make it work.
(apparently it's not too hard: aspect blog)