Skip to content

Commit 39bd4d8

Browse files
yushan26yushan8dougthor42
authored
fix(gazelle) Delete python targets with invalid srcs (#3046)
When running Gazelle, it generated the following target: ``` py_binary( name = "remove_py_binary", srcs = ["__main__.py"], main = "__main__.py", visibility = ["//visibility:public"], ) ``` After `__main__.py` was deleted and the change committed, re-running Gazelle did not remove the file from the srcs list. This change introduces logic to check whether all entries in a Python target’s srcs attribute correspond to valid files. If none of them exist, the target is added to result.Empty to signal that it should be cleaned up. This cleanup behavior applies to when python_generation mode is package or file, as all `srcs` are expected to reside directly within the current directory. --------- Co-authored-by: yushan <[email protected]> Co-authored-by: Douglas Thor <[email protected]>
1 parent cf594f7 commit 39bd4d8

File tree

13 files changed

+109
-2
lines changed

13 files changed

+109
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ BEGIN_UNRELEASED_TEMPLATE
3838
3939
{#v0-0-0-fixed}
4040
### Fixed
41-
* Nothing fixed.
41+
* (gazelle) Remove {obj}`py_binary` targets with invalid `srcs`. This includes files
42+
that are not generated or regular files.
4243
4344
{#v0-0-0-added}
4445
### Added

gazelle/python/generate.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,14 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
231231
}
232232

233233
collisionErrors := singlylinkedlist.New()
234+
// Create a validFilesMap of mainModules to validate if python macros have valid srcs.
235+
validFilesMap := make(map[string]struct{})
234236

235237
appendPyLibrary := func(srcs *treeset.Set, pyLibraryTargetName string) {
236238
allDeps, mainModules, annotations, err := parser.parse(srcs)
239+
for name := range mainModules {
240+
validFilesMap[name] = struct{}{}
241+
}
237242
if err != nil {
238243
log.Fatalf("ERROR: %v\n", err)
239244
}
@@ -363,6 +368,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
363368
setAnnotations(*annotations).
364369
generateImportsAttribute()
365370

371+
366372
pyBinary := pyBinaryTarget.build()
367373

368374
result.Gen = append(result.Gen, pyBinary)
@@ -490,7 +496,8 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
490496
result.Gen = append(result.Gen, pyTest)
491497
result.Imports = append(result.Imports, pyTest.PrivateAttr(config.GazelleImportsKey))
492498
}
493-
499+
emptyRules := py.getRulesWithInvalidSrcs(args, validFilesMap)
500+
result.Empty = append(result.Empty, emptyRules...)
494501
if !collisionErrors.Empty() {
495502
it := collisionErrors.Iterator()
496503
for it.Next() {
@@ -502,6 +509,42 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
502509
return result
503510
}
504511

512+
// getRulesWithInvalidSrcs checks existing Python rules in the BUILD file and return the rules with invalid source files.
513+
// Invalid source files are files that do not exist or not a target.
514+
func (py *Python) getRulesWithInvalidSrcs(args language.GenerateArgs, validFilesMap map[string]struct{}) (invalidRules []*rule.Rule) {
515+
if args.File == nil {
516+
return
517+
}
518+
for _, file := range args.GenFiles {
519+
validFilesMap[file] = struct{}{}
520+
}
521+
522+
isTarget := func(src string) bool {
523+
return strings.HasPrefix(src, "@") || strings.HasPrefix(src, "//") || strings.HasPrefix(src, ":")
524+
}
525+
for _, existingRule := range args.File.Rules {
526+
actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
527+
if existingRule.Kind() != actualPyBinaryKind {
528+
continue
529+
}
530+
var hasValidSrcs bool
531+
for _, src := range existingRule.AttrStrings("srcs") {
532+
if isTarget(src) {
533+
hasValidSrcs = true
534+
break
535+
}
536+
if _, ok := validFilesMap[src]; ok {
537+
hasValidSrcs = true
538+
break
539+
}
540+
}
541+
if !hasValidSrcs {
542+
invalidRules = append(invalidRules, newTargetBuilder(pyBinaryKind, existingRule.Name(), "", "", nil, false).build())
543+
}
544+
}
545+
return invalidRules
546+
}
547+
505548
// isBazelPackage determines if the directory is a Bazel package by probing for
506549
// the existence of a known BUILD file name.
507550
func isBazelPackage(dir string) bool {

gazelle/python/kinds.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ var pyKinds = map[string]rule.KindInfo{
4646
SubstituteAttrs: map[string]bool{},
4747
MergeableAttrs: map[string]bool{
4848
"srcs": true,
49+
"imports": true,
4950
},
5051
ResolveAttrs: map[string]bool{
5152
"deps": true,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
load("@rules_python//python:defs.bzl", "py_binary", "py_library")
2+
3+
py_library(
4+
name = "keep_library",
5+
deps = ["//keep_binary:foo"],
6+
)
7+
py_binary(
8+
name = "remove_invalid_binary",
9+
srcs = ["__main__.py"],
10+
data = ["testdata/test.txt"],
11+
visibility = ["//:__subpackages__"],
12+
)
13+
14+
py_binary(
15+
name = "another_removed_binary",
16+
srcs = ["foo.py"], # eg a now-deleted file that used to have `if __name__` block
17+
imports = ["."],
18+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
load("@rules_python//python:defs.bzl", "py_library")
2+
3+
py_library(
4+
name = "keep_library",
5+
deps = ["//keep_binary:foo"],
6+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Remove invalid binary
2+
3+
This test case asserts that `py_binary` should be deleted if invalid (no source files).

gazelle/python/testdata/remove_invalid_binary/WORKSPACE

Whitespace-only changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
load("@rules_python//python:defs.bzl", "py_binary", "py_library")
2+
3+
py_binary(
4+
name = "foo",
5+
srcs = ["foo.py"],
6+
visibility = ["//:__subpackages__"],
7+
)
8+
9+
py_library(
10+
name = "keep_binary",
11+
srcs = ["foo.py"],
12+
visibility = ["//:__subpackages__"],
13+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
load("@rules_python//python:defs.bzl", "py_binary", "py_library")
2+
3+
py_binary(
4+
name = "foo",
5+
srcs = ["foo.py"],
6+
visibility = ["//:__subpackages__"],
7+
)
8+
9+
py_library(
10+
name = "keep_binary",
11+
srcs = ["foo.py"],
12+
visibility = ["//:__subpackages__"],
13+
)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
if __name__ == "__main__":
2+
print("foo")

0 commit comments

Comments
 (0)