Skip to content

feat(bazel): enforce that BUILD files match their sources - #491

Merged
balajinvda merged 3 commits into
mainfrom
feat/bazel-gazelle-enforcement
Jul 28, 2026
Merged

feat(bazel): enforce that BUILD files match their sources#491
balajinvda merged 3 commits into
mainfrom
feat/bazel-gazelle-enforcement

Conversation

@balajinvda

@balajinvda balajinvda commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Why

Phase 2 of docs/dev/bazel-consolidation.md (#445).

Nothing in CI notices when a BUILD file falls behind its sources, and the consequence is not theoretical. Running Gazelle from the root surfaced four test files that Bazel was never building:

File Package State
apikey_test.go //src/clis/nvcf-cli/cmd missing from srcs
deploy_test.go //src/clis/nvcf-cli/cmd missing from srcs
root_test.go //src/clis/nvcf-cli/cmd missing from srcs
client_test.go //src/clis/nvcf-cli/internal/apikeys no go_test target at all

These exist in the repository and have never run in CI. Both targets pass once declared, so this is coverage that was silently absent, not tests that were failing.

That is the same failure mode as nvsnap, whose internal/agent/BUILD.bazel was last regenerated 2026-07-02 while checkpoint_v2.go and restore_v2.go landed 2026-07-14 — twelve days broken with nothing reporting it.

What changed

  • tools/ci/check-gazelle runs Gazelle from the repository root in --mode=diff, so it reports rather than rewrites and never leaves a dirty tree. The failure output is the patch to apply.
  • It distinguishes "there is a diff" from "gazelle failed to run" — a fetch or Starlark error must not read as tidy BUILD files.
  • The BUILD files Gazelle flagged are regenerated.
  • Wired into build-test, not bazel.yml.

Scope

Go packages only. Gazelle also wants to reorder attributes in the src/libraries/java BUILD files:

 java_library(
     name = "optional_compile_deps",
-    exports = CORE_OPTIONAL_COMPILE_DEPS,
     neverlink = True,
     visibility = ["//visibility:private"],
+    exports = CORE_OPTIONAL_COMPILE_DEPS,
 )

That is cosmetic and would churn 8 files the Java modules own for no functional gain. Those modules move into the root module in Phase 6; widening the check belongs in that change rather than ahead of it.

Customer Release Notes

Not customer visible.

Plan Summary

Not applicable.

Usage

tools/ci/check-gazelle     # verify
bazel run //:gazelle       # regenerate when it fails

Testing

  • bazel test //src/clis/nvcf-cli/cmd:cmd_test //src/clis/nvcf-cli/internal/apikeys:apikeys_test — both pass
  • tools/ci/check-gazelle is idempotent: clean after the regeneration, including after the manual comment fix below

Notes

One manual fix on top of Gazelle's output. It hoists the rules_shell load in src/libraries/go/lib/BUILD.bazel to the top of the file and carried a comment along with it — a comment that describes the sh_test target, not the load. The comment is reattached to sh_test, and the check stays clean with it there.

Two known gaps, deliberately left and not blocking this check:

  • Gazelle cannot merge src/compute-plane-services/nvsnap/ui/BUILD.bazel or src/libraries/go/worker/test/testutils/BUILD.bazel (could not merge expression)
  • Three imports under tests/bdd are not valid Go module paths: nvcf-bdd/dsl, nvcf-bdd/harness, nvcf-bdd/steps

References

None

Related Merge Requests/Pull Requests

Phase 2 companion: #484 (nested-module guard). Plan: #445.

Dependencies

None

Summary by CodeRabbit

  • Build & Quality
    • Added CI validation to ensure BUILD files match their Go sources, with clearer reporting when updates are needed.
    • Introduced Gazelle diff checking helpers plus a new CI test suite covering “up to date”, “out of date”, and failure behavior.
  • Developer Tools
    • Expanded Bazel Go test coverage for the CLI and internal components with new/updated test targets.
    • Added Bazel build support for the administrative mock command.
    • Added a Gazelle verification test target for the Go library workspace.

@balajinvda
balajinvda requested review from a team as code owners July 28, 2026 03:47
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 77d1fb4a-2e32-475c-ac71-153a24b00459

📥 Commits

Reviewing files that changed from the base of the PR and between dda92d7 and 82b4ae2.

📒 Files selected for processing (8)
  • .github/workflows/build-test.yml
  • src/clis/nvcf-cli/cmd/BUILD.bazel
  • src/clis/nvcf-cli/internal/apikeys/BUILD.bazel
  • src/clis/nvcf-cli/internal/client/BUILD.bazel
  • src/clis/nvcf-cli/scripts/admin-mock/BUILD.bazel
  • src/libraries/go/lib/BUILD.bazel
  • tools/ci/check-gazelle
  • tools/ci/test-check-gazelle
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/clis/nvcf-cli/scripts/admin-mock/BUILD.bazel
  • src/clis/nvcf-cli/internal/client/BUILD.bazel
  • src/clis/nvcf-cli/internal/apikeys/BUILD.bazel
  • tools/ci/check-gazelle
  • src/clis/nvcf-cli/cmd/BUILD.bazel
  • src/libraries/go/lib/BUILD.bazel
  • tools/ci/test-check-gazelle

📝 Walkthrough

Walkthrough

Bazel definitions now cover additional CLI tests, API key tests, client dependencies, and the admin-mock binary. A Gazelle test and CI helper validate BUILD files, with the build-test workflow invoking both helper scripts.

Changes

Bazel target coverage

Layer / File(s) Summary
CLI and internal test targets
src/clis/nvcf-cli/cmd/BUILD.bazel, src/clis/nvcf-cli/internal/apikeys/BUILD.bazel, src/clis/nvcf-cli/internal/client/BUILD.bazel
Additional CLI tests and dependencies are declared, and a dedicated API keys test target is added.
Admin-mock build targets
src/clis/nvcf-cli/scripts/admin-mock/BUILD.bazel
Private library and public binary targets are added for main.go.

Gazelle test configuration

Layer / File(s) Summary
Gazelle and shell-test BUILD rules
src/libraries/go/lib/BUILD.bazel
A gazelle_test target is added, and the shell-test load is moved before the existing lint target.

CI Gazelle enforcement

Layer / File(s) Summary
Gazelle checker, behavioral tests, and workflow integration
tools/ci/check-gazelle, tools/ci/test-check-gazelle, .github/workflows/build-test.yml
The checker runs Gazelle in diff mode, distinguishes execution failures from BUILD drift, tests clean and failure paths with a Bazel stub, and is invoked by the build-test workflow.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant build-test workflow
  participant test-check-gazelle
  participant check-gazelle
  participant Bazel/Gazelle
  build-test workflow->>test-check-gazelle: Run behavioral checks
  test-check-gazelle->>check-gazelle: Invoke with stubbed BAZEL
  check-gazelle->>Bazel/Gazelle: Run //:gazelle --mode=diff
  Bazel/Gazelle-->>check-gazelle: Return status and output
  check-gazelle-->>test-check-gazelle: Report clean, stale, or execution failure
  build-test workflow->>check-gazelle: Run repository BUILD check
Loading

Suggested reviewers: kristinapathak

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is well-formed Conventional Commits, but feat mislabels a CI/build/test infrastructure change rather than a product feature. Use ci, build, or test instead, e.g. ci(bazel): enforce that BUILD files match their sources.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bazel-gazelle-enforcement

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/ci/check-gazelle`:
- Around line 47-64: Update the Gazelle result handling after the command in the
check-gazelle script to inspect the captured status before the no-diff success
path. For any non-zero status, report the failure and exit nonzero; only use the
diff-specific exit code when unified-diff markers are present, while preserving
exit 0 for successful runs without diffs.
- Around line 31-77: add a focused shell test named test-check-gazelle that
stubs BAZEL and exercises check-gazelle’s three outcomes: exit 0 when no unified
diff is produced, exit 1 when Gazelle emits a generated diff, and exit 2 for
invocation failures such as ERROR/FATAL output. Assert each status and keep the
test isolated from the real Bazel/Gazelle commands.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 25675416-2de6-48b2-a7b5-a35074dde5c6

📥 Commits

Reviewing files that changed from the base of the PR and between 4760e37 and 6de5b7e.

📒 Files selected for processing (7)
  • .github/workflows/build-test.yml
  • src/clis/nvcf-cli/cmd/BUILD.bazel
  • src/clis/nvcf-cli/internal/apikeys/BUILD.bazel
  • src/clis/nvcf-cli/internal/client/BUILD.bazel
  • src/clis/nvcf-cli/scripts/admin-mock/BUILD.bazel
  • src/libraries/go/lib/BUILD.bazel
  • tools/ci/check-gazelle

Comment thread tools/ci/check-gazelle
Comment thread tools/ci/check-gazelle
@balajinvda

Copy link
Copy Markdown
Contributor Author

Both findings fixed in the latest push.

Unchecked exit status. This was a real false green and worth stating plainly: the script captured gazelle's status and never looked at it, so any failure printing neither an ERROR/FATAL prefix nor diff markers fell through to the success path and reported "BUILD files are up to date" for a run that verified nothing. A crashed server or a killed process would have read as tidy BUILD files — the exact silent pass this check exists to eliminate.

Non-zero with diff markers stays exit 1, since gazelle exits non-zero whenever it emits a diff and that is the normal out-of-date signal. Non-zero without them is now exit 2.

Missing test. tools/ci/test-check-gazelle stubs BAZEL so nothing touches a real Bazel or the network, and covers all three outcomes:

Case Expected
clean, zero exit 0
diff, non-zero exit 1
diff, zero exit 1
ERROR: output 2
FATAL: output 2
non-zero, no diff, no error prefix 2
empty output, zero exit 0

I checked the test catches the regression rather than merely passing today: reverting the status check makes it fail with want exit 2, got 0, which is exactly the symptom reported.

Both this and check-gazelle now run from the build-test job.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/ci/test-check-gazelle`:
- Around line 41-50: Update the substring check in expect() to match substr as a
literal fixed string and avoid the printf-to-grep pipeline; use here-string
input with grep’s fixed-string and quiet options while preserving the existing
failure reporting behavior.
- Around line 21-30: Update make_bazel to record its received arguments and
current working directory before producing the fixed payload, then add focused
assertions that check-gazelle invokes Bazel with --mode=diff, //:gazelle, the
expected Go roots, and the correct working directory. Keep the existing output
and exit-status classification assertions intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b61a8751-eefb-4113-8b1e-15a899a3fa61

📥 Commits

Reviewing files that changed from the base of the PR and between 6de5b7e and 63af067.

📒 Files selected for processing (3)
  • .github/workflows/build-test.yml
  • tools/ci/check-gazelle
  • tools/ci/test-check-gazelle
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/build-test.yml
  • tools/ci/check-gazelle

Comment thread tools/ci/test-check-gazelle
Comment thread tools/ci/test-check-gazelle
@balajinvda
balajinvda removed the request for review from FamousDirector July 28, 2026 14:53
@balajinvda

Copy link
Copy Markdown
Contributor Author

Both fixed in the latest push.

Asserting the invocation. Good catch, and the scope check was the part that most needed it. Go-only is a deliberate decision — Gazelle also wants to reorder attributes in the src/libraries/java BUILD files, which is cosmetic churn in files the Java modules own — but nothing recorded that intent in a way a change could trip over.

The stub now records its working directory and argv. The tests assert //:gazelle, --mode=diff, the repository root as cwd, each of the five Go roots, and that src/libraries/java is absent.

Literal matching. Switched to grep -Fq -- "${substr}" <<<"${out}". Both points were right: the needle was being treated as a regex, so new_test.go would have accepted newXtest.go, and the pipeline could SIGPIPE the producer under pipefail.

Verified the new assertions bite rather than merely passing today:

Mutation Result
add src/libraries/java to the scope FAIL java is in scope; it is deliberately excluded
drop --mode=diff FAIL asks for a diff, does not rewrite

balajinvda and others added 3 commits July 28, 2026 14:24
Phase 2 of docs/dev/bazel-consolidation.md. Nothing in CI noticed when a
BUILD file fell behind its sources, and the consequences were not
theoretical.

Running Gazelle from the root found four test files that Bazel was never
building. apikey_test.go, deploy_test.go and root_test.go were missing
from //src/clis/nvcf-cli/cmd, and internal/apikeys had a client_test.go
with no go_test target at all. They exist in the repository and have
never run in CI. Both targets pass once declared, so this is coverage
that was silently absent rather than tests that were failing.

That is the same failure mode as nvsnap, whose BUILD file was last
regenerated twelve days before the sources it was meant to build.

tools/ci/check-gazelle runs Gazelle from the root in --mode=diff, so it
reports rather than rewrites and leaves no dirty tree. It distinguishes
"there is a diff" from "gazelle failed", because a fetch or Starlark
error must not read as tidy BUILD files.

Scope is Go packages only. Gazelle also wants to reorder attributes in
the src/libraries/java BUILD files, which is cosmetic and would churn
files the Java modules own for no functional gain. Those modules move
into the root in Phase 6; widen this then, as part of that move.

One manual fix on top of the generated output: Gazelle hoists the
rules_shell load in src/libraries/go/lib and carried a comment with it
that describes the sh_test target, not the load. The comment is back on
sh_test, and the check is idempotent with it there.

Known gaps, deliberately left: two BUILD files Gazelle cannot merge
(nvsnap/ui, go/worker/test/testutils) and three imports under tests/bdd
that are not valid module paths. Neither blocks this check.

Runs in build-test, not bazel.yml.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review caught a false green. The script captured gazelle's exit status
and never checked it, so any failure that printed neither an ERROR/FATAL
prefix nor unified-diff markers fell through to the success path and
reported "BUILD files are up to date" for a run that verified nothing.

A crashed server, a killed process, an unrecognised failure mode: all of
them read as tidy BUILD files. That is precisely the class of silent pass
this check was added to eliminate, so it mattered more here than it would
in most scripts.

Non-zero with diff markers stays exit 1, since gazelle exits non-zero
whenever it produces a diff and that is the normal "out of date" signal.
Non-zero without them is now exit 2.

tools/ci/test-check-gazelle covers all three outcomes with a stubbed
BAZEL, so nothing touches a real Bazel or the network: clean exits 0,
diff exits 1 whether gazelle reports zero or non-zero, and ERROR, FATAL
and silent non-zero exits all fail closed with 2.

Verified the test catches the regression rather than only passing today.
Reverting the status check makes it fail with "want exit 2, got 0",
which is the exact symptom reported.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stub ignored its arguments, so the suite verified only how
check-gazelle classified gazelle's output. It would still have passed if
the script dropped --mode=diff and started rewriting the tree, renamed
the target, ran from the wrong directory, or changed which roots it
scopes. Everything about the invocation was untested.

The scope is the part that most needed a guard. Go-only is a deliberate
decision: Gazelle also wants to reorder attributes in the
src/libraries/java BUILD files, which is cosmetic and would churn files
the Java modules own. Nothing recorded that intent in a way a change
could trip over.

The stub now records its working directory and argv, and the tests assert
the target, --mode=diff, the repository root as cwd, each of the five Go
roots, and that src/libraries/java is absent.

Also switches assertions to fixed-string matching. The helper passed its
needle to grep as a regex, so "new_test.go" would have accepted
"newXtest.go", and it used a pipeline where grep exiting early could
SIGPIPE the producer and trip pipefail on large output.

Verified the new assertions fail when the behaviour changes rather than
only passing today: adding src/libraries/java to the scope fails the
exclusion check, and removing --mode=diff fails the diff-mode check.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@balajinvda
balajinvda force-pushed the feat/bazel-gazelle-enforcement branch from dda92d7 to 82b4ae2 Compare July 28, 2026 21:24
@balajinvda
balajinvda enabled auto-merge July 28, 2026 21:25
@balajinvda
balajinvda added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit bbd9d85 Jul 28, 2026
16 checks passed
@balajinvda
balajinvda deleted the feat/bazel-gazelle-enforcement branch July 28, 2026 22:08
@balajinvda

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version nvcf-cli-v1.13.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

balajinvda added a commit that referenced this pull request Jul 29, 2026
CI's BUILD-file check (#491) failed: pathsafe_write_test.go was a new file
absent from internal/agent/BUILD.bazel srcs.

Adding it to BUILD would work, but the package already pairs one test file per
source file and pathsafe_test.go was the obvious home. Same tests, no BUILD
churn, and one fewer place to look for coverage of pathsafe.go.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
balajinvda added a commit that referenced this pull request Jul 29, 2026
CI's BUILD-file check (#491) failed: pathsafe_write_test.go was a new file
absent from internal/agent/BUILD.bazel srcs.

Adding it to BUILD would work, but the package already pairs one test file per
source file and pathsafe_test.go was the obvious home. Same tests, no BUILD
churn, and one fewer place to look for coverage of pathsafe.go.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
balajinvda added a commit that referenced this pull request Aug 3, 2026
CI's "Check BUILD files match their sources" failed: internal/manifests and
internal/manifests/gen had no BUILD.bazel at all. Both packages were added on
this branch before that check landed on main (#491), so the merge is what
surfaced it rather than anything new here.

Generated with `bazel run //:gazelle` as the check instructs, not hand-written,
so the result is byte-for-byte what CI regenerates and compares against.

Gazelle also rewrote eight unrelated BUILD files under
src/libraries/java/nv-boot-parent/. Those are outside this PR and outside the
check's scope (check-gazelle passes only the Go roots, and its own test asserts
"leaves src/libraries/java out of scope"), so they were reverted rather than
swept in.

Verified by running the failing job's steps locally: check-gazelle now reports
"BUILD files are up to date", and test-check-gazelle, check-nested-modules and
test-check-nested-modules all pass.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants