> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# aspect gazelle

> Keep Bazel BUILD files in sync with aspect gazelle, covering incremental scope, smart escalation, diff-mode detection, and Aspect CLI CI examples.

`aspect gazelle` runs [Gazelle](https://registry.bazel.build/modules/gazelle) (or [`aspect_gazelle_prebuilt`](https://registry.bazel.build/modules/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`](#gazelle-target) (default `//tools/gazelle:gazelle`). There are two ways to provide that binary, and `aspect gazelle` supports both.

### Prebuilt binary (recommended)

[`aspect_gazelle_prebuilt`](https://registry.bazel.build/modules/aspect_gazelle_prebuilt) fetches a precompiled Gazelle binary from the [aspect-gazelle](https://github.com/aspect-build/aspect-gazelle) releases. No Go toolchain and no compile step are required, and it is the only binary that can run [Starlark/AXL extensions](/docs/cli/guides/gazelle).

```python title="MODULE.bazel" theme={null}
# See https://github.com/aspect-build/aspect-gazelle/releases for the latest version.
bazel_dep(name = "aspect_gazelle_prebuilt", version = "0.0.21")
```

```python title="tools/gazelle/BUILD.bazel" theme={null}
load("@aspect_gazelle_prebuilt//:def.bzl", "aspect_gazelle")

aspect_gazelle(
    name = "gazelle",
    languages = ["go", "proto", "python"],  # whichever fit your repo
    extensions = ["//tools/gazelle:shell.axl"],
)
```

**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](/docs/cli/guides/gazelle), so you can write generators without the Go extension API.
* Exposes prebuilt-only flags like `-progress` and `-cache` (see [Additional Gazelle flags](#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`](https://github.com/bazel-contrib/bazel-gazelle), composing whatever language extensions you need (including custom first-party Go extensions). This requires a Go toolchain.

```python title="tools/gazelle/BUILD.bazel" theme={null}
load("@bazel_gazelle//:def.bzl", "gazelle", "gazelle_binary")

gazelle_binary(
    name = "gazelle_binary",
    languages = [
        "@bazel_gazelle//language/proto",
        "@bazel_gazelle//language/go",
        "@rules_python//gazelle",
        "//tools/gazelle/my_extension",  # a custom first-party go_library
    ],
)

gazelle(
    name = "gazelle",
    gazelle = ":gazelle_binary",
)
```

**Advantages:**

* Full control: compose any language extension and write custom generators against Gazelle's Go [`Language` interface](https://pkg.go.dev/github.com/bazel-contrib/bazel-gazelle/language#Language).
* 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:

```shell theme={null}
aspect gazelle --scope=changed   # only dirs with PR-changed files
aspect gazelle --scope=all       # full workspace (default)
```

**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:

```python title=".aspect/config.axl" theme={null}
def config(ctx: ConfigContext):
    ctx.tasks["gazelle"].args.scope_all_on_change = [
        "BUILD.bazel",
        "MODULE.bazel",
        "go.sum",           # Go module changes require a full re-index
    ]
```

## 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

```shell theme={null}
aspect gazelle --gazelle-target=//tools/gazelle:gazelle
```

```python title=".aspect/config.axl" theme={null}
def config(ctx: ConfigContext):
    ctx.tasks["gazelle"].args.gazelle_target = "//tools/gazelle:gazelle"
```

Default: `//tools/gazelle:gazelle`.

### Reacting to drift

```shell theme={null}
aspect gazelle --on-change=fail    # default on CI
aspect gazelle --on-change=warn    # warn but don't block
aspect gazelle --on-change=silent  # detect-only, no output
```

### Additional Gazelle flags

Pass extra flags to the Gazelle binary (any `-mode` flag is stripped internally since the task controls that):

```shell theme={null}
aspect gazelle --gazelle-flag=-index=lazy //...
```

`-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

```python title=".aspect/config.axl" theme={null}
def config(ctx: ConfigContext):
    ctx.tasks["gazelle"].args.upload_gazelle_diff = True
```

Uploads the BUILD diff as `gazelle.patch` when drift is detected, so developers can apply it locally.

## CI examples

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  on:
    pull_request:
      branches: [main]

  jobs:
    gazelle:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        id-token: write
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
          with:
            aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
        - run: aspect gazelle --task:name gazelle
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":bazel: Gazelle"
      plugins:
        - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
      command: aspect gazelle --task:name gazelle
      agents:
        queue: aspect-default
  ```

  ```yaml GitLab theme={null}
  include:
    - component: $CI_SERVER_FQDN/aspect-build/setup-aspect-gitlab-component/setup@2026.26.8

  gazelle:
    extends: .setup-aspect
    tags: [aspect-workflows, aspect-default]
    rules:
      - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    script: aspect gazelle --task:name gazelle
  # Set ASPECT_API_TOKEN as a masked CI/CD variable.
  ```

  ```yaml CircleCI theme={null}
  version: 2.1

  orbs:
    setup-aspect: aspect-build/setup-aspect@2026.26.10

  jobs:
    gazelle:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run: aspect gazelle --task:name gazelle
  ```
</CodeGroup>
