Skip to main content
aspect gazelle runs Gazelle (or aspect_gazelle_prebuilt, for Starlark-defined extensions) to generate and synchronize your repository’s BUILD files. On CI it detects whether BUILD files are out of sync with the source and fails the step if they are. Run locally, aspect gazelle applies the fixes in place. --check-only defaults to auto: detect-only on CI, apply locally.

Setup: choosing a gazelle binary

aspect gazelle drives a Gazelle binary that you point it at with --gazelle-target (default //tools/gazelle:gazelle). Provide the binary either way below. aspect_gazelle_prebuilt fetches a precompiled Gazelle binary from the aspect-gazelle releases. It needs no Go toolchain and no compile step, and it’s the only binary that can run Starlark/AXL extensions.
MODULE.bazel
tools/gazelle/BUILD.bazel
Advantages:
  • No Go toolchain download and no first-run compile of the gazelle binary: developers fetch a cached prebuilt artifact instead.
  • Runs Starlark/AXL extensions, so you can write generators without the Go extension API.
  • Exposes prebuilt-only flags like -progress and -cache (see Additional Gazelle flags).
  • Avoids the bootstrapping trap where a broken BUILD file referenced by your gazelle binary’s load statements stops gazelle from compiling, and so from fixing that file.

Building from source

Build Gazelle with the upstream gazelle_binary macro from bazel-contrib/bazel-gazelle, composing whatever language extensions you need (including custom first-party Go extensions). This requires a Go toolchain.
tools/gazelle/BUILD.bazel
Advantages:
  • Full control: compose any language extension and write custom generators against Gazelle’s Go Language interface.
  • No dependency on which extensions ship in the prebuilt binary.
The tradeoff is build cost and complexity: a Go (and sometimes cgo) toolchain, a recompile whenever an extension changes, and the bootstrapping trap noted above.

Incremental mode: only touch changed directories

On a large repo, running Gazelle over the whole tree on every change is slow. --scope=changed restricts it to directories that contain changed files:
The changed files are found the same way as for aspect format, with the same --base-ref and --merge-base flags. --changed-file-pattern narrows which changed files count, for example to the languages your Gazelle plugins cover. Escalation: some changes need a whole-repo run, and two things trigger one. By default a change to BUILD.bazel or MODULE.bazel escalates to --scope=all, because those files can affect dependency resolution across the repo. Change detection failing to resolve a base escalates too, with the same --merge-base=<sha> / --base-ref advice aspect format gives. Setting the patterns replaces the defaults, so include them if you want to keep them:
.aspect/config.axl

Watch mode

Pass --watch to keep BUILD files in sync while you edit. After the first run the task stays running, watches the Bazel workspace, and reruns Gazelle after each settled batch of file changes:
A failed run doesn’t stop the task. It waits for the next change. Changes under .git, .jj and Bazel’s convenience symlinks don’t trigger a rerun. Stop the task with Ctrl-C. Without --watch, aspect gazelle runs once and exits.

How drift detection works

aspect gazelle always runs Gazelle with -mode=diff, whatever other flags you pass:
  1. Gazelle runs once with -mode=diff and produces a unified diff without writing any files.
  2. The task reads the diff to decide whether BUILD files are out of sync. It uses the diff output, not Gazelle’s exit code.
  3. With --check-only=true (the auto result on CI), a non-empty diff raises the --severity verdict.
  4. With --check-only=false (the auto result locally), the task applies the diff with git apply -p0.
One Gazelle run gives both the verdict and the patch, and applying the captured diff is faster than running Gazelle a second time.

Configuration

Gazelle target

.aspect/config.axl

Reacting to drift

auto, the default, is fail on a recognized CI host and info off it.

Additional Gazelle flags

Pass extra flags to the Gazelle binary. Any -mode flag is dropped, since the task controls the mode:
Flags specific to aspect_gazelle_prebuilt, such as -progress and -cache, fail against a from-source Gazelle binary. To run a Gazelle subcommand, pass --gazelle-command=update or --gazelle-command=fix. With neither, Gazelle uses its own default. update-repos isn’t supported; run it with bazel run. -index=lazy pairs well with --scope=changed for the best incremental performance: Gazelle only indexes the packages it visits rather than the whole repo.

Patch upload

When BUILD files are out of date on CI, the task uploads the diff as a gazelle.patch artifact so developers can apply it locally. It’s on by default. To turn it off:
.aspect/config.axl

Options

It also takes the Bazel flag options common to every task that runs Bazel (--bazel-flag, --bazel-startup-flag, --announce-bazel-*, --bes-backend, --bes-header); see aspect run.

CI example

.github/workflows/gazelle.yaml
Complete pipeline examples shows the same setup on Buildkite, GitLab CI and CircleCI, and on Aspect Workflows CI runners.