4.3.3 Buildifier — Formatting & Linting

Buildifier is the standard mechanical layer for BUILD, WORKSPACE, and .bzl files: it parses Bazel's Starlark-like files and emits one canonical style, so formatting stops being a code-review topic and tools get files they can reliably read, edit, and generate.1 For rule and macro authors, that matters more than whitespace. A stable file shape makes the abstractions from 4.1 Macro Design & Rule-Authoring Starlark and 4.2 Custom Rules, Providers & Actions easier for humans to inspect and easier for automated maintenance tools to change.

Formatter, Linter, Contract

At the local command line, the formatter shape is intentionally boring:

buildifier path/to/BUILD.bazel
buildifier path/to/defs.bzl
buildifier -r path/to/package_tree

That command rewrites files in place. Use it before review, wire it into editors, and make CI check that the checked-in files already match the tool's output. BUILD file formatting must match Buildifier output.1 The same operational advice applies to Starlark extension files: use Buildifier as both formatter and linter.2

The linter mode is the second half of the contract:

buildifier --lint=warn path/to/file
buildifier --lint=fix path/to/file

Formatting answers "is this file in the canonical shape?" Linting answers "does this Starlark look suspicious or non-idiomatic?" Common findings include missing public function docstrings, load statements that are not at the top of the file, unused variables, and dictionary items whose order should be normalized. The exact warning set evolves with bazelbuild/buildtools, so treat category names as tool-version facts, not prose you hand-maintain forever.

Why Full-File Formatting Wins

Buildifier's design deliberately avoids personal style knobs. The original large-scale rollout at Google enforced one strict style for everyone: no exceptions, no settings, no personal preferences.3 The point was not that one indentation style is morally better. The point was to remove an entire class of review churn and make build-file edits scalable.

That design shows up in the formatter mechanics. Buildifier attaches comments to nearby syntax-tree nodes, has a limited set of hard-coded line-splitting cases, preserves some existing line breaks, and reformats the whole file rather than only the edited lines.3 Full-file formatting sounds heavy, but it prevents a worse outcome: every later edit to an old file producing a noisy mixed formatting diff.

The rollout lesson is useful for smaller repositories too. If you introduce Buildifier after years of hand formatting, put the one-time formatting commit in .git-blame-ignore-revs so future archaeology does not stop at a mechanical rewrite.4 Then keep the rule simple: new diffs should already be formatted.

Put It Where Developers Already Work

Buildifier is most effective before CI. The effective setup uses prebuilt binaries rather than making every developer compile the Go tool on a cache miss, then puts buildifier on PATH through a workspace-specific environment such as direnv plus bazel_env.bzl.4 That connects this item directly back to 3.5 Developer Experience & Local Tooling: Bazel can manage the version, but editors and shells still need a stable executable path.

For VS Code, the usual shape is to point the Bazel extension at that stable path and enable fix-on-format:

{
  "bazel.buildifierExecutable": "./bazel-out/bazel_env-opt/bin/tools/bazel_env/bin/buildifier",
  "bazel.buildifierFixOnFormat": true
}

The exact path depends on where the bazel_env target lives, but the principle is portable: the editor should call the same Buildifier binary that CI expects.4 A pre-commit hook is the fallback for developers whose editor does not format on save, and a CI format.check target is the final enforcement layer.5 The broader formatter/linter integration pattern belongs in 3.4.4 Code Quality Integration. Here the rule-authoring lesson is narrower: keep Starlark files mechanically clean so the interesting review is about the rule API, provider contract, and generated actions.

Adopt Lints Gradually

Buildifier has about 100 checks, and many are specific to Bazel's standard library or Starlark conventions.6 Turning every warning on as a required check can create a giant cleanup PR that blocks real work and is hard to rebase. The reliable approach is the ratchet: enable one check, fix or suppress the existing findings, then add the next check.6

Buildifier linting also has a different shape from graph-aware language linting. rules_lint can run many linters over existing dependency-graph targets, but BUILD files themselves are not usually modeled as ordinary dependencies of those targets. Buildifier linting therefore works best as a standalone repository-wide step. It is useful, but not incremental in the same sense as an aspect-driven linter over *_library targets.6

That distinction avoids a common mistake: do not force every BUILD.bazel file into a filegroup just so a linter target can see it. For formatting, file-tree tools and pre-commit hooks are a better fit. For semantic checks over rule targets, use Bazel-native linting or aspects later in 4.8 Aspects.

What Buildifier Does Not Replace

Buildifier is not a BUILD-file generator. Gazelle, code generators, and repository-specific tools may create or update BUILD files, but they should delegate final formatting to Buildifier rather than trying to clone its style decisions.3 It is also not a substitute for clear Starlark design. If a macro emits confusing generated targets, Buildifier can make the file neat. It cannot make the abstraction good. Use 4.3.2 Macro Expansion Inspection and query output when you need to inspect what the macro actually created.

The most useful mental model is "mechanical first reviewer." Buildifier handles deterministic formatting and cheap Starlark hygiene. Humans can then review the parts that require judgment: whether a macro should exist, whether a rule publishes the right providers, whether an action contract is stable, and whether a public .bzl API is documented.

key takeaway

Use Buildifier everywhere Starlark is written: editor save, pre-commit fallback, and CI enforcement. Formatting should be automatic. Linting should be ratcheted in deliberately.

For rule authors, the payoff is not prettier files. It is a repository where BUILD and .bzl files stay tool-friendly enough for query, Buildozer, Gazelle, IDEs, and future large-scale refactors to keep working.

Check your understanding · 3 questions

1.What is the primary reason to enforce Buildifier output in CI?

Select one answer

2.Which practices match a healthy Buildifier rollout?

Select all that apply

3.True or false: Buildifier's role in Starlark development.

Choose True or False for each sentence

Buildifier is both a formatter and a linter for Bazel Starlark files.
Buildifier replaces macro-expansion inspection when a macro emits confusing targets.
Buildifier should be the final formatting step for tools that generate BUILD files.
Buildifier linting is best adopted as a single all-or-nothing cleanup.
0 of 3 answered

Footnotes

  1. BUILD Style Guide - Buildifier parses and emits standard BUILD-file style, making formatting a non-issue in reviews and helping tools understand, edit, and generate files. 1 2

  2. .bzl style guide - general Starlark advice to use Buildifier as formatter and linter.

  3. The Story of Reformatting 100k Files at Google in 2012 - Buildifier design choices, strict no-settings rollout, editor integration, full-file formatting, and large-scale maintenance effect. 1 2 3

  4. Starlark linter: Buildifier - prebuilt binary recommendation, direnv plus bazel_env.bzl PATH setup, stable editor path, and VS Code settings. 1 2 3

  5. Starlark linter: Buildifier - pre-commit fallback, format.check CI enforcement, and .git-blame-ignore-revs recommendation for the initial formatting commit.

  6. Starlark linter: Buildifier - about 100 Buildifier checks, standalone repository-wide linting recommendation, and ratchet adoption strategy. 1 2 3