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. In CI it detects whether BUILD files are out of sync with source code and fails the step if so — preventing drift before it accumulates into a bigger cleanup. Running aspect gazelle locally applies fixes in place. Same command, different behavior based on context — --check-only defaults to true on CI and false locally.

Setup: choosing a gazelle binary

aspect gazelle drives a Gazelle binary that you point it at with --gazelle-target (default //tools/gazelle:gazelle). There are two ways to provide that binary, and aspect gazelle supports both. aspect_gazelle_prebuilt fetches a precompiled Gazelle binary from the aspect-gazelle releases. No Go toolchain and no compile step are required, and it is 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 prevents gazelle from compiling — and therefore 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. For most repos the prebuilt binary is the better default.

Incremental mode: only touch changed directories

For large repos, running Gazelle over the entire tree on every PR is slow. --scope=changed (available when --scope-all-on-change patterns aren’t matched) restricts Gazelle to directories containing changed files:
Smart escalation: certain file changes require a full-repo re-run. By default, changes to BUILD.bazel or MODULE.bazel escalate to --scope=all because those files can affect dependency resolution across the entire repo. Configure which patterns trigger escalation:
.aspect/config.axl

How drift detection works

aspect gazelle runs Gazelle with -mode=diff internally regardless of other flags. This is the key:
  1. Gazelle runs once with -mode=diff — it produces a unified diff without writing any files
  2. The task parses the diff to determine if BUILD files are out of sync (exit code from Gazelle isn’t reliable — the task uses stdout content)
  3. On CI (--check-only=true): if the diff is non-empty, the task exits 1
  4. Locally (--check-only=false): the task applies the diff via git apply -p0
Why single-run? Running Gazelle twice (once to detect, once to apply) would be expensive on a large monorepo. The diff-mode approach gets both the verdict and the patch from one Gazelle invocation. Applying the captured diff is much faster than a second Gazelle run.

Configuration

Gazelle target

.aspect/config.axl
Default: //tools/gazelle:gazelle.

Reacting to drift

Additional Gazelle flags

Pass extra flags to the Gazelle binary (any -mode flag is stripped internally since the task controls that):
-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

.aspect/config.axl
Uploads the BUILD diff as gazelle.patch when drift is detected, so developers can apply it locally.

CI examples