Skip to content

Commit b11a498

Browse files
authored
explain: Distinguish transitive invalidation through tools (#8359)
If a change to a library causes a large number of transitive invalidations, this can be because it affects the output of a tool. Making this explicit in the invalidation summary helps explain the situation.
1 parent bfe0358 commit b11a498

File tree

6 files changed

+112
-10
lines changed

6 files changed

+112
-10
lines changed

cli/explain/compactgraph/compactgraph.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ func Diff(old, new *CompactGraph) (*spawn_diff.DiffResult, error) {
267267
}
268268
resultEntry, _ := diffResults.Load(output)
269269
result := resultEntry.(*diffResult)
270+
if len(result.spawnDiff.GetModified().Diffs) == 0 {
271+
continue
272+
}
270273
spawn := new.spawns[output]
271274
foundTransitiveCause := false
272275
// Get the deduplicated primary outputs for those spawns referenced via invalidatedBy.
@@ -286,13 +289,13 @@ func Diff(old, new *CompactGraph) (*spawn_diff.DiffResult, error) {
286289
invalidatingResult.invalidates = append(invalidatingResult.invalidates, result.invalidates, spawn)
287290
}
288291
}
289-
if len(result.spawnDiff.GetModified().Diffs) > 0 && (result.localChange || !foundTransitiveCause) {
292+
if result.localChange || !foundTransitiveCause {
290293
if len(result.invalidates) > 0 {
291294
// result.invalidates isn't modified after this point as the spawns are visited in topological order.
292295
diffWG.Add(1)
293296
go func() {
294297
defer diffWG.Done()
295-
result.spawnDiff.GetModified().TransitivelyInvalidated = flattenInvalidates(result.invalidates)
298+
result.spawnDiff.GetModified().TransitivelyInvalidated = flattenInvalidates(result.invalidates, isExecOutputPath(output))
296299
}()
297300
}
298301
spawnDiffs = append(spawnDiffs, result.spawnDiff)
@@ -309,7 +312,10 @@ func Diff(old, new *CompactGraph) (*spawn_diff.DiffResult, error) {
309312

310313
// flattenInvalidates flattens a tree of Spawn nodes into a deduplicated map of mnemonic to count of transitively
311314
// invalidated spawns.
312-
func flattenInvalidates(invalidates []any) map[string]uint32 {
315+
// Mnemonics of spawns are suffixed with " (as tool)" if the invalidating spawn is a tool and the invalidated spawn is
316+
// not, that is, if the dependency path crosses an edge with an "exec" transition (ignoring "exec" transitions on
317+
// targets that are already in the "exec" configuration).
318+
func flattenInvalidates(invalidates []any, isTool bool) map[string]uint32 {
313319
transitivelyInvalidated := make(map[string]uint32)
314320
spawnsSeen := make(map[*Spawn]struct{})
315321
toVisit := invalidates
@@ -320,7 +326,11 @@ func flattenInvalidates(invalidates []any) map[string]uint32 {
320326
case *Spawn:
321327
if _, seen := spawnsSeen[n]; !seen {
322328
spawnsSeen[n] = struct{}{}
323-
transitivelyInvalidated[n.Mnemonic]++
329+
suffix := ""
330+
if isTool && !isExecOutputPath(n.PrimaryOutputPath()) {
331+
suffix = " (as tool)"
332+
}
333+
transitivelyInvalidated[n.Mnemonic+suffix]++
324334
}
325335
default:
326336
// If n is not a Spawn, it must be a slice of Spawns or slices.

cli/explain/compactgraph/compactgraph_test.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,33 @@ func TestTransitiveInvalidation(t *testing.T) {
315315
}
316316
}
317317

318+
func TestTransitiveInvalidationForTool(t *testing.T) {
319+
spawnDiffs := diffLogs(t, "transitive_invalidation_for_tool", "8.0.0")
320+
require.Len(t, spawnDiffs, 2)
321+
322+
{
323+
sd := spawnDiffs[0]
324+
assert.Regexp(t, "^bazel-out/[^/]+-fastbuild/bin/pkg/lib.out$", sd.PrimaryOutput)
325+
assert.Equal(t, "//pkg:library", sd.TargetLabel)
326+
assert.Equal(t, "Genrule", sd.Mnemonic)
327+
assert.Equal(t, map[string]uint32{"Genrule": 1}, sd.GetModified().GetTransitivelyInvalidated())
328+
require.Len(t, sd.GetModified().GetDiffs(), 1)
329+
assert.Len(t, sd.GetModified().GetDiffs()[0].GetInputContents().GetFileDiffs(), 1)
330+
}
331+
{
332+
sd := spawnDiffs[1]
333+
assert.Regexp(t, "^bazel-out/[^/]+-exec-[^/]+/bin/pkg/lib.out$", sd.PrimaryOutput)
334+
assert.Equal(t, "//pkg:library", sd.TargetLabel)
335+
assert.Equal(t, "Genrule", sd.Mnemonic)
336+
assert.Equal(t, map[string]uint32{
337+
"Genrule": 1,
338+
"Genrule (as tool)": 3,
339+
}, sd.GetModified().GetTransitivelyInvalidated())
340+
require.Len(t, sd.GetModified().GetDiffs(), 1)
341+
assert.Len(t, sd.GetModified().GetDiffs()[0].GetInputContents().GetFileDiffs(), 1)
342+
}
343+
}
344+
318345
func TestNonHermetic(t *testing.T) {
319346
spawnDiffs := diffLogs(t, "non_hermetic", "7.3.1")
320347
require.Len(t, spawnDiffs, 1)
@@ -512,7 +539,7 @@ func TestToolRunfilesPaths(t *testing.T) {
512539
assert.Regexp(t, "^bazel-out/[^/]+/bin/pkg/tool.runfiles", sd.PrimaryOutput)
513540
assert.Equal(t, "//tools:tool_sh", sd.TargetLabel)
514541
assert.Equal(t, "Runfiles directory", sd.Mnemonic)
515-
assert.Equal(t, map[string]uint32{"Genrule": 2}, sd.GetModified().GetTransitivelyInvalidated())
542+
assert.Equal(t, map[string]uint32{"Genrule (as tool)": 2}, sd.GetModified().GetTransitivelyInvalidated())
516543
require.Len(t, sd.GetModified().GetDiffs(), 1)
517544
d := sd.GetModified().Diffs[0]
518545
require.IsType(t, &spawn_diff.Diff_InputPaths{}, d.Diff)
@@ -528,7 +555,7 @@ func TestToolRunfilesContents(t *testing.T) {
528555
assert.Regexp(t, "^bazel-out/[^/]+/bin/pkg/tool.runfiles", sd.PrimaryOutput)
529556
assert.Equal(t, "//tools:tool_sh", sd.TargetLabel)
530557
assert.Equal(t, "Runfiles directory", sd.Mnemonic)
531-
assert.Equal(t, map[string]uint32{"Genrule": 2}, sd.GetModified().GetTransitivelyInvalidated())
558+
assert.Equal(t, map[string]uint32{"Genrule (as tool)": 2}, sd.GetModified().GetTransitivelyInvalidated())
532559
require.Len(t, sd.GetModified().GetDiffs(), 1)
533560
d := sd.GetModified().Diffs[0]
534561
require.IsType(t, &spawn_diff.Diff_InputContents{}, d.Diff)
@@ -549,7 +576,7 @@ func TestToolRunfilesContentsTransitive(t *testing.T) {
549576
assert.Regexp(t, "^bazel-out/[^/]+/bin/tools/tool.sh", sd.PrimaryOutput)
550577
assert.Equal(t, "//tools:tool_sh", sd.TargetLabel)
551578
assert.Equal(t, "Genrule", sd.Mnemonic)
552-
assert.Equal(t, map[string]uint32{"Genrule": 2, "Runfiles directory": 1}, sd.GetModified().GetTransitivelyInvalidated())
579+
assert.Equal(t, map[string]uint32{"Genrule (as tool)": 2, "Runfiles directory": 1}, sd.GetModified().GetTransitivelyInvalidated())
553580
require.Len(t, sd.GetModified().GetDiffs(), 1)
554581
d := sd.GetModified().Diffs[0]
555582
require.IsType(t, &spawn_diff.Diff_Args{}, d.Diff)
@@ -571,7 +598,7 @@ func TestToolRunfilesSymlinksPaths(t *testing.T) {
571598
assert.Regexp(t, "^bazel-out/[^/]+/bin/tools/tool.runfiles$", sd.PrimaryOutput)
572599
assert.Empty(t, sd.TargetLabel)
573600
assert.Equal(t, "Runfiles directory", sd.Mnemonic)
574-
assert.Equal(t, map[string]uint32{"Genrule": 1}, sd.GetModified().GetTransitivelyInvalidated())
601+
assert.Equal(t, map[string]uint32{"Genrule (as tool)": 1}, sd.GetModified().GetTransitivelyInvalidated())
575602
require.Len(t, sd.GetModified().GetDiffs(), 1)
576603
d := sd.GetModified().Diffs[0]
577604
require.IsType(t, &spawn_diff.Diff_InputPaths{}, d.Diff)
@@ -588,7 +615,7 @@ func TestToolRunfilesSymlinksContents(t *testing.T) {
588615
assert.Regexp(t, "^bazel-out/[^/]+/bin/tools/tool.runfiles$", sd.PrimaryOutput)
589616
assert.Empty(t, sd.TargetLabel)
590617
assert.Equal(t, "Runfiles directory", sd.Mnemonic)
591-
assert.Equal(t, map[string]uint32{"Genrule": 1}, sd.GetModified().GetTransitivelyInvalidated())
618+
assert.Equal(t, map[string]uint32{"Genrule (as tool)": 1}, sd.GetModified().GetTransitivelyInvalidated())
592619
require.Len(t, sd.GetModified().GetDiffs(), 1)
593620
d := sd.GetModified().Diffs[0]
594621
require.IsType(t, &spawn_diff.Diff_InputContents{}, d.Diff)
@@ -620,7 +647,7 @@ func TestToolRunfilesSymlinksContentsTransitive(t *testing.T) {
620647
assert.Equal(t, "//gen:gen", sd.TargetLabel)
621648
assert.Equal(t, "Genrule", sd.Mnemonic)
622649
assert.Equal(t, map[string]uint32{
623-
"Genrule": 1,
650+
"Genrule (as tool)": 1,
624651
"Runfiles directory": 1,
625652
}, sd.GetModified().GetTransitivelyInvalidated())
626653
require.Len(t, sd.GetModified().GetDiffs(), 1)

cli/explain/compactgraph/input.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,16 @@ func isSourcePath(path string) bool {
678678
return !strings.HasPrefix(path, "bazel-out/")
679679
}
680680

681+
// Exec-configured output paths look like:
682+
// bazel-out/darwin_arm64-opt-exec-ST-d57f47055a04/bin/pkg/foo
683+
// bazel-out/my_platform-opt-exec/bin/pkg/foo
684+
var execOutputRegexp = regexp.MustCompile("bazel-out/[^/]+-exec[-/].*")
685+
686+
// Whether the given path is an output path of an artifact built in the exec configuration.
687+
func isExecOutputPath(path string) bool {
688+
return execOutputRegexp.MatchString(path)
689+
}
690+
681691
func computeRunfilesPath(input Input) string {
682692
p := input.Path()
683693
// For a path such as "bazel-out/k8-fastbuild/bin/pkg/foo", trim to "pkg/foo".
Binary file not shown.
Binary file not shown.

cli/explain/compactgraph/testdata/generate.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,61 @@ old
862862
new
863863
-- pkg/in2.txt --
864864
new
865+
`,
866+
bazelVersions: []string{"8.0.0"},
867+
},
868+
{
869+
name: "transitive_invalidation_for_tool",
870+
baseline: `
871+
-- MODULE.bazel --
872+
-- pkg/BUILD --
873+
genrule(
874+
name = "library",
875+
outs = ["lib.out"],
876+
srcs = ["lib.in"],
877+
cmd = "cp $< $@",
878+
)
879+
880+
genrule(
881+
name = "tool",
882+
outs = ["tool.out"],
883+
srcs = [":library"],
884+
cmd = "cp $< $@",
885+
executable = True,
886+
tags = ["manual"],
887+
)
888+
889+
genrule(
890+
name = "tool_via_tool",
891+
outs = ["tool_via_tool.out"],
892+
tools = [":tool"],
893+
cmd = "cp $(location :tool) $@",
894+
executable = True,
895+
)
896+
897+
genrule(
898+
name = "gen",
899+
outs = ["gen.out"],
900+
tools = [":tool"],
901+
cmd = "cp $(location :tool) $@",
902+
)
903+
904+
genrule(
905+
name = "toplevel",
906+
outs = ["toplevel.out"],
907+
srcs = [
908+
":library",
909+
":gen",
910+
":tool_via_tool",
911+
],
912+
cmd = "cat $(SRCS) > $@",
913+
)
914+
-- pkg/lib.in --
915+
old
916+
`,
917+
changes: `
918+
-- pkg/lib.in --
919+
new
865920
`,
866921
bazelVersions: []string{"8.0.0"},
867922
},

0 commit comments

Comments
 (0)