Skip to content
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
35 changes: 31 additions & 4 deletions gazelle/python/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,26 +593,53 @@ func (py *Python) getRulesWithInvalidSrcs(args language.GenerateArgs, validFiles
validFilesMap[file] = struct{}{}
}

// allFilesMap extends validFilesMap with all regular files on disk.
// py_binary uses validFilesMap (main modules + generated files), while py_library
// and py_test use allFilesMap since any file is a valid src for them.
allFilesMap := make(map[string]struct{}, len(validFilesMap)+len(args.RegularFiles))
for file := range validFilesMap {
allFilesMap[file] = struct{}{}
}
for _, file := range args.RegularFiles {
allFilesMap[file] = struct{}{}
}
Comment on lines +596 to +605

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If __main__.py is present in the package, it is the standard entry point for py_binary targets. However, since __main__.py is not added to validFilesMap (which only contains main modules detected in appendPyLibrary and generated files), any existing py_binary target (including custom ones) that uses __main__.py as a source will be incorrectly marked as invalid and deleted by Gazelle.

We should ensure __main__.py is added to validFilesMap if it exists in args.RegularFiles.

	// allFilesMap extends validFilesMap with all regular files on disk.
	// py_binary uses validFilesMap (main modules + generated files), while py_library
	// and py_test use allFilesMap since any file is a valid src for them.
	allFilesMap := make(map[string]struct{}, len(validFilesMap)+len(args.RegularFiles))
	for file := range validFilesMap {
		allFilesMap[file] = struct{}{}
	}
	for _, file := range args.RegularFiles {
		allFilesMap[file] = struct{}{}
		if file == pyBinaryEntrypointFilename {
			validFilesMap[pyBinaryEntrypointFilename] = struct{}{}
		}
	}

@taowang487 taowang487 Jun 9, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't think this comment is correct. Also added a test case for __main__.py in commit ad0e48c and the test can pass.

This target is not deleted by Gazelle

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


isTarget := func(src string) bool {
return strings.HasPrefix(src, "@") || strings.HasPrefix(src, "//") || strings.HasPrefix(src, ":")
}
for _, existingRule := range args.File.Rules {
if !kindMatches(args.Config, existingRule, pyBinaryKind) {
var matchedKind string
var filesMap map[string]struct{}
if kindMatches(args.Config, existingRule, pyBinaryKind) {
matchedKind = pyBinaryKind
filesMap = validFilesMap
} else if kindMatches(args.Config, existingRule, pyLibraryKind) {
matchedKind = pyLibraryKind
filesMap = allFilesMap
} else if kindMatches(args.Config, existingRule, pyTestKind) {
matchedKind = pyTestKind
filesMap = allFilesMap
} else {
continue
}

srcs := existingRule.AttrStrings("srcs")
if len(srcs) == 0 {
continue
}
var hasValidSrcs bool
for _, src := range existingRule.AttrStrings("srcs") {
for _, src := range srcs {
if isTarget(src) {
hasValidSrcs = true
break
}
if _, ok := validFilesMap[src]; ok {
if _, ok := filesMap[src]; ok {
hasValidSrcs = true
break
}
}
if !hasValidSrcs {
invalidRules = append(invalidRules, newTargetBuilder(pyBinaryKind, existingRule.Name(), "", "", nil, false).build())
invalidRules = append(invalidRules, newTargetBuilder(matchedKind, existingRule.Name(), "", "", nil, false).build())
}
}
return invalidRules
Expand Down
36 changes: 36 additions & 0 deletions gazelle/python/testdata/remove_invalid_per_file/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

# gazelle:python_generation_mode file

py_library(
name = "bar",
srcs = ["bar.py"],
visibility = ["//:__subpackages__"],
)

py_library(
name = "deleted_lib",
srcs = ["deleted.py"],
visibility = ["//:__subpackages__"],
)

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

py_binary(
name = "deleted_bin",
srcs = ["deleted_bin.py"],
)

py_test(
name = "bar_test",
srcs = ["bar_test.py"],
)

py_test(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add some test cases for aliased and mapped rules. For example, someone who has # gazelle:alias_kind my_py_test py_test or # gazelle:map_kind py_binary my_py_binary //bazel_wrappers:python.

It should be OK because you're using kindMatches, but I'd prefer the explicit test.

name = "deleted_test",
srcs = ["deleted_test.py"],
)
21 changes: 21 additions & 0 deletions gazelle/python/testdata/remove_invalid_per_file/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

# gazelle:python_generation_mode file

py_library(
name = "bar",
srcs = ["bar.py"],
visibility = ["//:__subpackages__"],
)

py_test(
name = "bar_test",
srcs = ["bar_test.py"],
)

py_binary(
name = "remove_invalid_per_file_bin",
srcs = ["__main__.py"],
main = "__main__.py",
visibility = ["//:__subpackages__"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
---