Formatting and linting are common tasks that involve the build system. We can run these with Bazel. We’ll use Aspect’s Bazel Central Registry | aspect_rules_lint to make more ergonomic.
Formatting
The go
CLI comes with the fmt
command, applying an opinionated formatting to all Go code. There’s also a stricter alternative, https://pkg.go.dev/mvdan.cc/gofumpt which we’ll use in this lesson.
There’s an executable format
target in the root BUILD
file, so we can simply format all files from the repository root by running it. First make sure there’s a git
repository (you may need to run git init
) since the formatter only looks at version-controlled files by default.
% bazel run format
...
Formatted Go in 0m0.326s
However developers won’t remember to run this command, so we have pre-commit setup to automatically format code when you git commit
. Note that each developer must do a one-time setup in the repository to enable the git pre-commit hook. If you setup a CI check that requires code be formatted, you can point to the pre-commit setup instructions from the failure message you print, making this easily discoverable.
Curious how the formatter knows which tool to run? You can use bazel query
commands to inspect the setup:
% bazel query --output=build format
alias(
name = "format",
actual = "//tools/format:format",
)
# Follow the indirection...
% bazel print //tools/format:format
format_multirun(
name = "format",
go = "@aspect_rules_lint//format:gofumpt",
starlark = "@buildifier_prebuilt//:buildifier",
)
Linting
There are a variety of popular Go linting tools, such as nogo, govet, revive, and many more. There are so many there’s an “aggregator” linter to combine them: https://golangci-lint.run/usage/linters/
As of January 2025, rules_lint doesn’t have any Go linters included. So our repository follows the setup from rules_go instead: https://github.com/bazel-contrib/rules_go/blob/master/docs/go/core/bzlmod.md#configuring-nogo
This integrates as a Bazel “validation action”. This special type of build action is spawned even though it doesn’t produce any needed outputs, and there is one for each go_library
target. Thus we can check that linting is working just by introducing a violation, for example by shadowing a variable.
Try changing the body of our HelloServer
function in cmd/hello/main.go
to have this code:
think := true
if think {
think := false // Shadowing violation
cowsay.Cow{}.Write(w, []byte("Thinking"), think)
}
cowsay.Cow{}.Write(w, []byte("Hello world!"), think)
Now if we try to build the library, we should get an error:
% bazel build cmd/hello:hello_lib
...
nogo: errors found by nogo during build-time code analysis:
cmd/hello/main.go:17:9: declaration of "think" shadows declaration at line 15 (shadow)
Target //cmd/hello:hello_lib failed to build
It’s possible to skip these validation actions with --norun_validations