Skip to content

Fix #603: a label-only extension's owner no longer stands in for its repositories - #608

Merged
tinder-maxwellelliott merged 3 commits into
masterfrom
maxwelle/bazel-diff-603-reproducer-ce8875
Nov 3, 2026
Merged

tinder-maxwellelliott merged 3 commits into
masterfrom
maxwelle/bazel-diff-603-reproducer-ce8875

Conversation

@tinder-maxwellelliott

@tinder-maxwellelliott tinder-maxwellelliott commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #603

Reproducer and fix for #603: with an extension listed in --labelOnlyModuleExtensions, a single lockfile bump (go.mod via gazelle's go_deps, pnpm-lock.yaml via rules_js's npm) still impacted every target that uses the owning module's rules and every target of every module with a bazel_dep path to the owner.

Fix

In query mode lower_repositories (src/bazel.rs) makes the module that owns an extension depend on every repository the extension generates, because a toolchain resolved from one of them isn't a query input. #559 added a %-view of the owner without a listed extension's repositories, but only targets inside those repositories used it; a root target naming @ext// and //external:dependent still reached the full owner node.

Now, for a listed extension, the owner gets no stand-in edges at all, the same way --isolatedDependencyRoot members (owner_exemptions) already don't. With the owner node itself reduced, the per-extension view nodes and their per-rule repository lookup in hash.rs are redundant, so they're removed (net −43 lines). The hub-to-spoke edges stay.

Contract change: the flag used to assert "the repositories use each other only through labels". It now asserts they are used only through labels, never by resolving a toolchain from one of them. go_deps, npm and crate repositories satisfy that; a toolchain-generating extension like rules_go's go_sdk must not be listed. The README (template + regenerated) and the --help text are updated accordingly.

Hash shift: for users of the flag, the owner's node and everything depending on it hash differently once. No change without the flag, and no change in cquery mode (which never had the stand-in edges).

Reproducer

Extended after the reporter's feedback (comment). Fixture tests/resources/workspaces/extension_owner_dependent_module/:

  • ext owns an extension generating a hub @crates and version-named spokes (crates__b__1_0 → crates__b__2_0, like npm__user-agents__2.1.229).
  • //:uses_ext uses ext's own rule directly (the npm half).
  • //:uses_dependent uses a rule of dependent, which reaches ext through middle (two bazel_dep hops, the Go half).
  • //:uses_a / //:uses_b consume the hub.

external::label_only_extension_bump_does_not_reach_users_of_the_owner_or_its_dependents, bumping b:

Configuration Expected Before After
no flags (sanity) uses_ext, uses_dependent impacted ✅ ✅
--labelOnlyModuleExtensions (no fine-graining, reporter's setup) {uses_a, uses_b} ❌ + uses_dependent, uses_ext ✅
+ --fineGrainedHashExternalRepos=@crates {uses_b} ❌ + uses_dependent, uses_ext ✅
change to ext/tool.sh, both configs uses_ext, uses_dependent impacted ✅ ✅

The last row keeps the owner's own tree reaching its rule users and dependent modules. The fine-grained run names only the hub, since Bazel rejects a repository that doesn't exist at one of the revisions.

Testing

  • //src:rust_tests, //src:cli_tests: pass. The two lower_repositories unit tests are updated: the views test now pins only apparent names, and the owner-edges test checks both the default stand-in edges and the reduced owner for listed extensions.
  • Full e2e suite locally (Bazel 9.3.1): 68 of 72 pass, including the reproducer and the existing label-only, toolchain and isolated-root cases. The other 4 (bzlmod_transitive_deps_cquery, two cquery_*_platforms, fine_grained_workspace_external_repo) fail only because this machine has no JDK (rules_java local_jdk / coursier); CI covers them.
  • cargo clippy --all-targets -D warnings: clean. regen_check: pass.

🤖 Generated with Claude Code

…ing on the owner

Adds the extension_owner_dependent_module fixture: module `ext` owns an
extension generating a hub and spokes, and module `dependent` only has a
bazel_dep on `ext`. With the extension listed in --labelOnlyModuleExtensions,
bumping one spoke still impacts //:uses_dependent, a target that uses a rule
of `dependent` and names nothing the extension generates, because
//external:dependent depends on the full //external:ext node. The test also
pins that a change to `ext` itself must keep reaching `dependent`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fine-graining

Per the reporter's feedback on #603, the fixture now also covers the npm
half of the issue:

- //:uses_ext uses `ext`'s own rule directly (like a js_binary using
  rules_js), with no dependent module on the path.
- Spokes are named after their version (like npm__user-agents__2.1.229),
  so a bump replaces a spoke instead of changing it in place.
- `dependent` reaches `ext` through `middle`, two module hops.
- The main check runs without --fineGrainedHashExternalRepos, the
  reporter's configuration; a second run fine-grains only the @crates hub
  (a versioned spoke can't be named in a flag shared by both revisions,
  since Bazel rejects an undefined repository in the query).

Today, with the extension listed in --labelOnlyModuleExtensions, the bump
impacts //:uses_ext and //:uses_dependent in both configurations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tinder-maxwellelliott tinder-maxwellelliott changed the title Reproducer for #603: label-only extension bump reaches modules depending on the owner Reproducer for #603: label-only extension bump reaches the owner's rule users and dependent modules Nov 3, 2026
…repositories

In query mode the module that owns an extension depends on every
repository the extension generates. #559 gave targets inside a listed
extension's repositories a view of the owner without them, but every
other reader still reached the full owner node: a target using the
owner's own rule (a js_binary on rules_js) and every module with a
bazel_dep path to the owner (rules_jest -> ... -> gazelle). One go.mod
or pnpm-lock.yaml bump then impacted most of the workspace.

For an extension listed in --labelOnlyModuleExtensions the owner now
gets no stand-in edges at all, the same way --isolatedDependencyRoot
members already don't. The flag's assertion becomes "used only through
labels, never by resolving a toolchain from one of them", which
go_deps/npm/crate repositories satisfy. With the owner node itself
reduced, the per-extension %view nodes and their per-rule lookup in
hash.rs are redundant and are removed. Hub-to-spoke edges stay.

Users of the flag see a one-time hash shift on the owner's node and its
dependents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tinder-maxwellelliott tinder-maxwellelliott changed the title Reproducer for #603: label-only extension bump reaches the owner's rule users and dependent modules Fix #603: a label-only extension's owner no longer stands in for its repositories Nov 3, 2026
@tinder-maxwellelliott
tinder-maxwellelliott merged commit 0ae54f5 into master Nov 3, 2026
19 checks passed
@tinder-maxwellelliott
tinder-maxwellelliott deleted the maxwelle/bazel-diff-603-reproducer-ce8875 branch November 3, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant