From 26bcb8a87328a23810c451452dfcaa8225447eff Mon Sep 17 00:00:00 2001 From: Tao Wang Date: Tue, 9 Jun 2026 10:11:44 -0700 Subject: [PATCH 1/4] refactor(gazelle): rename remove_invalid_library test to remove_invalid_library_package_mode Clarifies that this test covers package-mode py_library removal (where __init__.py is missing), distinct from per-file mode stale target removal. Co-Authored-By: Tao Wang --- .../BUILD.in | 0 .../BUILD.out | 0 .../README.md | 0 .../WORKSPACE | 0 .../my_test.py | 0 .../others/BUILD.in | 0 .../others/BUILD.out | 0 .../test.yaml | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/BUILD.in (100%) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/BUILD.out (100%) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/README.md (100%) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/WORKSPACE (100%) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/my_test.py (100%) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/others/BUILD.in (100%) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/others/BUILD.out (100%) rename gazelle/python/testdata/{remove_invalid_library => remove_invalid_library_package_mode}/test.yaml (100%) diff --git a/gazelle/python/testdata/remove_invalid_library/BUILD.in b/gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.in similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/BUILD.in rename to gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.in diff --git a/gazelle/python/testdata/remove_invalid_library/BUILD.out b/gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.out similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/BUILD.out rename to gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.out diff --git a/gazelle/python/testdata/remove_invalid_library/README.md b/gazelle/python/testdata/remove_invalid_library_package_mode/README.md similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/README.md rename to gazelle/python/testdata/remove_invalid_library_package_mode/README.md diff --git a/gazelle/python/testdata/remove_invalid_library/WORKSPACE b/gazelle/python/testdata/remove_invalid_library_package_mode/WORKSPACE similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/WORKSPACE rename to gazelle/python/testdata/remove_invalid_library_package_mode/WORKSPACE diff --git a/gazelle/python/testdata/remove_invalid_library/my_test.py b/gazelle/python/testdata/remove_invalid_library_package_mode/my_test.py similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/my_test.py rename to gazelle/python/testdata/remove_invalid_library_package_mode/my_test.py diff --git a/gazelle/python/testdata/remove_invalid_library/others/BUILD.in b/gazelle/python/testdata/remove_invalid_library_package_mode/others/BUILD.in similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/others/BUILD.in rename to gazelle/python/testdata/remove_invalid_library_package_mode/others/BUILD.in diff --git a/gazelle/python/testdata/remove_invalid_library/others/BUILD.out b/gazelle/python/testdata/remove_invalid_library_package_mode/others/BUILD.out similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/others/BUILD.out rename to gazelle/python/testdata/remove_invalid_library_package_mode/others/BUILD.out diff --git a/gazelle/python/testdata/remove_invalid_library/test.yaml b/gazelle/python/testdata/remove_invalid_library_package_mode/test.yaml similarity index 100% rename from gazelle/python/testdata/remove_invalid_library/test.yaml rename to gazelle/python/testdata/remove_invalid_library_package_mode/test.yaml From ad0e48ce0849ef9a0e89fb75a51345afb1947eb6 Mon Sep 17 00:00:00 2001 From: Tao Wang Date: Tue, 9 Jun 2026 10:12:01 -0700 Subject: [PATCH 2/4] test(gazelle): add test cases for stale per-file py_library and py_test removal Add remove_invalid_library_file_mode: verifies that a py_library target whose source file was deleted is removed in per-file generation mode. Add remove_invalid_test: verifies that a py_test target whose source file was deleted is removed. Both tests currently fail without the corresponding fix to getRulesWithInvalidSrcs. Co-Authored-By: Tao Wang --- .../testdata/remove_invalid_per_file/BUILD.in | 36 +++++++++++++++++++ .../remove_invalid_per_file/BUILD.out | 21 +++++++++++ .../remove_invalid_per_file/WORKSPACE | 1 + .../remove_invalid_per_file/__main__.py | 0 .../testdata/remove_invalid_per_file/bar.py | 0 .../remove_invalid_per_file/bar_test.py | 0 .../remove_invalid_per_file/test.yaml | 1 + 7 files changed, 59 insertions(+) create mode 100644 gazelle/python/testdata/remove_invalid_per_file/BUILD.in create mode 100644 gazelle/python/testdata/remove_invalid_per_file/BUILD.out create mode 100644 gazelle/python/testdata/remove_invalid_per_file/WORKSPACE create mode 100644 gazelle/python/testdata/remove_invalid_per_file/__main__.py create mode 100644 gazelle/python/testdata/remove_invalid_per_file/bar.py create mode 100644 gazelle/python/testdata/remove_invalid_per_file/bar_test.py create mode 100644 gazelle/python/testdata/remove_invalid_per_file/test.yaml diff --git a/gazelle/python/testdata/remove_invalid_per_file/BUILD.in b/gazelle/python/testdata/remove_invalid_per_file/BUILD.in new file mode 100644 index 0000000000..7f387ab6f9 --- /dev/null +++ b/gazelle/python/testdata/remove_invalid_per_file/BUILD.in @@ -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( + name = "deleted_test", + srcs = ["deleted_test.py"], +) diff --git a/gazelle/python/testdata/remove_invalid_per_file/BUILD.out b/gazelle/python/testdata/remove_invalid_per_file/BUILD.out new file mode 100644 index 0000000000..db62ff2df4 --- /dev/null +++ b/gazelle/python/testdata/remove_invalid_per_file/BUILD.out @@ -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__"], +) diff --git a/gazelle/python/testdata/remove_invalid_per_file/WORKSPACE b/gazelle/python/testdata/remove_invalid_per_file/WORKSPACE new file mode 100644 index 0000000000..faff6af87a --- /dev/null +++ b/gazelle/python/testdata/remove_invalid_per_file/WORKSPACE @@ -0,0 +1 @@ +# This is a Bazel workspace for the Gazelle test data. diff --git a/gazelle/python/testdata/remove_invalid_per_file/__main__.py b/gazelle/python/testdata/remove_invalid_per_file/__main__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/remove_invalid_per_file/bar.py b/gazelle/python/testdata/remove_invalid_per_file/bar.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/remove_invalid_per_file/bar_test.py b/gazelle/python/testdata/remove_invalid_per_file/bar_test.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/remove_invalid_per_file/test.yaml b/gazelle/python/testdata/remove_invalid_per_file/test.yaml new file mode 100644 index 0000000000..ed97d539c0 --- /dev/null +++ b/gazelle/python/testdata/remove_invalid_per_file/test.yaml @@ -0,0 +1 @@ +--- From d32fb7bb70a996e2d1215944a17941c5c4a708ed Mon Sep 17 00:00:00 2001 From: Tao Wang Date: Tue, 9 Jun 2026 10:12:19 -0700 Subject: [PATCH 3/4] fix(gazelle): remove stale py_library and py_test targets with missing srcs getRulesWithInvalidSrcs only checked py_binary targets for stale source files. In per-file generation mode, deleting a .py file left orphaned py_library and py_test targets in BUILD files, causing build failures. Extend the function to also check py_library and py_test rules. Uses allFilesMap (all regular files on disk) for these kinds, while py_binary continues using validFilesMap (main modules only). Rules with no srcs attr (e.g. deps-only libraries) are left untouched. Co-Authored-By: Tao Wang --- gazelle/python/generate.go | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go index 6fa6252ebb..90e06a1546 100644 --- a/gazelle/python/generate.go +++ b/gazelle/python/generate.go @@ -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{}{} + } + 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 From c954bf37a1d89475703d67824918253c3b4e5e13 Mon Sep 17 00:00:00 2001 From: Tao Wang Date: Wed, 10 Jun 2026 10:05:47 -0700 Subject: [PATCH 4/4] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e05b6dd0..a5334c3d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,9 @@ END_UNRELEASED_TEMPLATE {#v0-0-0-fixed} ### Fixed +* (gazelle) `py_library` and `py_test` targets with missing source files can now be + removed by Gazelle + ([#3375](https://github.com/bazel-contrib/rules_python/issues/3375)). * (bootstrap) Fixed a potential race condition with symlink creation during startup. * (gazelle) Fixed handling of auto-included `__init__.py` files when generating `py_binary`