Skip to content

fix(gazelle) Delete python targets with invalid srcs #3046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ END_UNRELEASED_TEMPLATE
* (toolchains) use "command -v" to find interpreter in `$PATH`
([#3150](https://github.com/bazel-contrib/rules_python/pull/3150)).
* (pypi) `bazel vendor` now works in `bzlmod` ({gh-issue}`3079`).
* (gazelle) Remove py_binary targets with invalid srcs. This includes files
that are not generated or regular files.

{#v0-0-0-added}
### Added
Expand Down
47 changes: 46 additions & 1 deletion gazelle/python/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
result.Gen = append(result.Gen, pyTest)
result.Imports = append(result.Imports, pyTest.PrivateAttr(config.GazelleImportsKey))
}

if !cfg.CoarseGrainedGeneration() {
emptyRules := py.getRulesWithInvalidSrcs(cfg, args)
result.Empty = append(result.Empty, emptyRules...)
}
if !collisionErrors.Empty() {
it := collisionErrors.Iterator()
for it.Next() {
Expand All @@ -502,6 +505,48 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
return result
}

// getRulesWithInvalidSrcs checks existing Python rules in the BUILD file and return the rules with invalid source files.
// Invalid source files are files that do not exist or not a target.
func (py *Python) getRulesWithInvalidSrcs(cfg *pythonconfig.Config, args language.GenerateArgs) (invalidRules []*rule.Rule) {
if args.File == nil {
return
}
filesMap := make(map[string]struct{})
for _, file := range args.RegularFiles {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about those python files in subdirs? Thoughts on my previous comment about reading mainModules instead of args.RegularFiles?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we can just scope it for file/package mode, so we can potentially generalize it to also clean up py_library rules.

if cfg.IgnoresFile(filepath.Base(file)) {
continue
}
filesMap[file] = struct{}{}
}
for _, file := range args.GenFiles {
filesMap[file] = struct{}{}
}

isTarget := func(src string) bool {
return strings.HasPrefix(src, "@") || strings.HasPrefix(src, "//") || strings.HasPrefix(src, ":")
}
for _, existingRule := range args.File.Rules {
if existingRule.Kind() != pyBinaryKind {
continue
}
var hasValidSrcs bool
for _, src := range existingRule.AttrStrings("srcs") {
if isTarget(src) {
hasValidSrcs = true
break
}
if _, ok := filesMap[src]; ok {
hasValidSrcs = true
break
}
}
if !hasValidSrcs {
invalidRules = append(invalidRules, newTargetBuilder(existingRule.Kind(), existingRule.Name(), args.Config.RepoRoot, args.Rel, nil).build())
}
}
return invalidRules
}

// isBazelPackage determines if the directory is a Bazel package by probing for
// the existence of a known BUILD file name.
func isBazelPackage(dir string) bool {
Expand Down
11 changes: 11 additions & 0 deletions gazelle/python/testdata/remove_invalid_binary/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load("@rules_python//python:defs.bzl", "py_binary")

py_library(
name = "keep_library",
deps = ["//keep_binary:foo"],
)
py_binary(
name = "remove_invalid_binary",
srcs = ["__main__.py"],
visibility = ["//:__subpackages__"],
)
6 changes: 6 additions & 0 deletions gazelle/python/testdata/remove_invalid_binary/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
load("@rules_python//python:defs.bzl", "py_library")

py_library(
name = "keep_library",
deps = ["//keep_binary:foo"],
)
3 changes: 3 additions & 0 deletions gazelle/python/testdata/remove_invalid_binary/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Remove invalid binary

This test case asserts that `py_binary` should be deleted if invalid (no source files).
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@rules_python//python:defs.bzl", "py_binary", "py_library")

py_binary(
name = "foo",
srcs = ["foo.py"],
visibility = ["//:__subpackages__"],
)

py_library(
name = "keep_binary",
srcs = ["foo.py"],
visibility = ["//:__subpackages__"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@rules_python//python:defs.bzl", "py_binary", "py_library")

py_binary(
name = "foo",
srcs = ["foo.py"],
visibility = ["//:__subpackages__"],
)

py_library(
name = "keep_binary",
srcs = ["foo.py"],
visibility = ["//:__subpackages__"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
if __name__ == "__main__":
print("foo")
Empty file.