feat: scope .bzl edits to macro-produced rules via instantiation stack#436
Merged
tinder-maxwellelliott merged 2 commits intoJul 17, 2026
Merged
Conversation
packageBzlSeeds (Tinder#365) attributes a loaded .bzl's digest to every target in every package that load()s it. So editing one widely-loaded rule .bzl re-hashes every unrelated target in each loading package -- native rules, filegroups, source files -- not just the targets that use the rule. Attribute each .bzl to the specific rules a macro produced instead. RuleHasher.ruleBzlSeed builds a per-rule seed from the .bzl files in the rule's macro instantiation stack (--proto:instantiation_stack). $rule_implementation_hash (already hashed via BazelRule.digest) covers a rule class's own definition .bzl and its transitive loads, so editing a rule file still invalidates its consumers even across files. Source files drop the seed. The package-wide seed remains as a fallback when instantiation stacks are unavailable (older Bazel), preserving the Tinder#365 fix there. --proto:instantiation_stack adds ~2-7% to query-proto size; the stack is already computed by Bazel, so there is no meaningful time cost. Tests: editing a rule .bzl (co-located, or a cross-file macro/rule pair) impacts the targets that use it but not unrelated same-package targets; editing either the macro or the rule invalidates the consumer. The existing Tinder#259/Tinder#227 (macro_invalidation) and Tinder#365 (bzl_seed_overtrigger) tests still pass.
tinder-maxwellelliott
self-requested a review
July 16, 2026 21:45
tinder-maxwellelliott
approved these changes
Jul 16, 2026
tinder-maxwellelliott
left a comment
Collaborator
There was a problem hiding this comment.
Great fix! Thanks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
packageBzlSeeds(added in #365) attributes a loaded.bzl's content digest to everytarget in every package that
load()s it. That fixed the #259/#227 under-invalidation(a macro edit that changed no rule attribute invalidated nothing), but it over-corrected:
editing one widely-
load()ed rule.bzlnow re-hashes every unrelated target in eachloading package — native rules, filegroups, source files — not just the targets that use
the rule.
In a repo where a single rule
.bzlis loaded by hundreds of packages (each for one target),a one-line edit to that
.bzlreported ~130k impacted targets. In our case this was acopybaratarget in each vendored directory, but would also have a similar effect forany other lightweight config macro whose target nothing else in the package depends on
(e.g., a license target). With this fix our impacted set for that scenario is down to ~1k.
Change
Attribute each
.bzlto the specific rules a macro produced, not the whole package.RuleHasher.ruleBzlSeedbuilds a per-rule seed from the.bzlfiles in that rule's macroinstantiation stack (
--proto:instantiation_stack), so a macro edit re-hashes only therules that macro created. Two signals combine to cover every edit:
macros.bzl -> ... -> rule(...)). Coversmacro edits, including a macro that wraps a native rule (which has no rule-definition
digest), preserving Bazel Macro issue #259/:bazel-diff -- generate-hashes is not giving correct targets when changed something in build attribute .bzl file which imported in many BUILD files #227.
$rule_implementation_hash— already emitted by Bazel as a synthetic rule attributeand already folded into the rule digest by
BazelRule.digest. It is the transitive digestof the
.bzlwhere the rule class is defined, so editing a rule's definition file (evenin a different file than the macro that instantiates it) still invalidates its consumers.
Source-file target hashes drop the seed entirely (a source file's hash is its content).
When instantiation stacks are unavailable (older Bazel / the flag off),
BuildGraphHasherfalls back to the existing per-package seed, so the #365 behavior is preserved there.
Coverage
$rule_implementation_hashcc_libraryfrom@rules_cc)Unrelated targets that merely share a package with a macro-produced target are no longer
swept in.
Cost
--proto:instantiation_stackadds ~2–7% to query-proto size on a macro-heavy graph. Thestack is already computed by Bazel (it backs macro-aware error messages), so there is no
meaningful query-time cost. It is on by default; the package-seed fallback keeps older
Bazel working.
Tests
New e2e fixtures + tests:
rule_bzl_overtrigger— editing an existing widely-loaded rule.bzlimpacts the targetthat uses it but not unrelated native targets in the same package.
cross_file_macro_rule— a macro in one file loading a rule from another; editingeither file impacts the consumer, and a sibling native target stays out.
The existing regression tests still pass:
macro_invalidation(#259/#227) andbzl_seed_overtrigger(#365).