diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac2a3f0c3..817761dd47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,9 @@ A brief description of the categories of changes: `__test__.py` exists in the same package. Previously in these cases there would only be one test target made. +* (gazelle) If a non-test Python file contains `if __name__ == "__main__":`, + then a `py_binary` target is made for it instead of a `py_library` target. + Breaking changes: * (pip) `pip_install` repository rule in this release has been disabled and diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go index 25fb194370..2fc04fa4c1 100644 --- a/gazelle/python/generate.go +++ b/gazelle/python/generate.go @@ -20,6 +20,7 @@ import ( "log" "os" "path/filepath" + "regexp" "strings" "github.com/bazelbuild/bazel-gazelle/config" @@ -85,14 +86,11 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes packageName := filepath.Base(args.Dir) + pyBinaryFilenames := treeset.NewWith(godsutils.StringComparator) pyLibraryFilenames := treeset.NewWith(godsutils.StringComparator) pyTestFilenames := treeset.NewWith(godsutils.StringComparator) pyFileNames := treeset.NewWith(godsutils.StringComparator) - // hasPyBinary controls whether a py_binary target should be generated for - // this package or not. - hasPyBinary := false - // hasPyTestEntryPointFile and hasPyTestEntryPointTarget control whether a py_test target should // be generated for this package or not. hasPyTestEntryPointFile := false @@ -106,14 +104,14 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes ext := filepath.Ext(f) if ext == ".py" { pyFileNames.Add(f) - if !hasPyBinary && f == pyBinaryEntrypointFilename { - hasPyBinary = true - } else if !hasPyTestEntryPointFile && f == pyTestEntrypointFilename { + if !hasPyTestEntryPointFile && f == pyTestEntrypointFilename { hasPyTestEntryPointFile = true } else if f == conftestFilename { hasConftestFile = true } else if strings.HasSuffix(f, "_test.py") || strings.HasPrefix(f, "test_") { pyTestFilenames.Add(f) + } else if f == pyBinaryEntrypointFilename || hasNameEqualsMain(filepath.Join(args.Config.RepoRoot, args.Rel, f)) { + pyBinaryFilenames.Add(f) } else { pyLibraryFilenames.Add(f) } @@ -270,13 +268,19 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes appendPyLibrary(pyLibraryFilenames, cfg.RenderLibraryName(packageName)) } - if hasPyBinary { - deps, err := parser.parseSingle(pyBinaryEntrypointFilename) + pyBinaryFilenames.Each(func(index int, filename interface{}) { + entrypointFilename := filename.(string) + deps, err := parser.parseSingle(entrypointFilename) if err != nil { log.Fatalf("ERROR: %v\n", err) } - pyBinaryTargetName := cfg.RenderBinaryName(packageName) + var pyBinaryTargetName string + if entrypointFilename == pyBinaryEntrypointFilename { + pyBinaryTargetName = cfg.RenderBinaryName(packageName) + } else { + pyBinaryTargetName = strings.TrimSuffix(filepath.Base(filename.(string)), ".py") + } // Check if a target with the same name we are generating already // exists, and if it is of a different kind from the one we are @@ -296,17 +300,20 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes } pyBinaryTarget := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames). - setMain(pyBinaryEntrypointFilename). addVisibility(visibility). - addSrc(pyBinaryEntrypointFilename). + addSrc(entrypointFilename). addModuleDependencies(deps). generateImportsAttribute() + if entrypointFilename == pyBinaryEntrypointFilename { + pyBinaryTarget.setMain(pyBinaryEntrypointFilename) + } + pyBinary := pyBinaryTarget.build() result.Gen = append(result.Gen, pyBinary) result.Imports = append(result.Imports, pyBinary.PrivateAttr(config.GazelleImportsKey)) - } + }) var conftest *rule.Rule if hasConftestFile { @@ -463,6 +470,20 @@ func hasEntrypointFile(dir string) bool { return false } +// hasNameEqualsMain determines if the file contains 'if __name__ == "__main__"'. +func hasNameEqualsMain(path string) bool { + searchString := `if __name__ == ['"]__main__['"]:` + bytesContents, err := os.ReadFile(path) + if err != nil { + return false + } + match, err := regexp.Match(searchString, bytesContents) + if err == nil { + return match + } + return false +} + // isEntrypointFile returns whether the given path is an entrypoint file. The // given path can be absolute or relative. func isEntrypointFile(path string) bool { diff --git a/gazelle/python/testdata/binary_targets/BUILD.in b/gazelle/python/testdata/binary_targets/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/binary_targets/BUILD.out b/gazelle/python/testdata/binary_targets/BUILD.out new file mode 100644 index 0000000000..09a69799ba --- /dev/null +++ b/gazelle/python/testdata/binary_targets/BUILD.out @@ -0,0 +1,35 @@ +load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test") + +py_library( + name = "binary_targets", + srcs = [ + "bar.py", + "baz.py", + ], + visibility = ["//:__subpackages__"], +) + +py_binary( + name = "bar_binary", + srcs = ["bar_binary.py"], + visibility = ["//:__subpackages__"], + deps = [":binary_targets"], +) + +py_binary( + name = "single_quote_main", + srcs = ["single_quote_main.py"], + visibility = ["//:__subpackages__"], + deps = [":bar_test"], +) + +py_test( + name = "bar_test", + srcs = ["bar_test.py"], + deps = [":bar_binary"], +) + +py_test( + name = "name_main_test", + srcs = ["name_main_test.py"], +) diff --git a/gazelle/python/testdata/binary_targets/README.md b/gazelle/python/testdata/binary_targets/README.md new file mode 100644 index 0000000000..76ac4df7b1 --- /dev/null +++ b/gazelle/python/testdata/binary_targets/README.md @@ -0,0 +1,4 @@ +# Binary targets + +This test case generates `py_binary` targets for files containing +`if __name__ == "__main__"`. diff --git a/gazelle/python/testdata/binary_targets/WORKSPACE b/gazelle/python/testdata/binary_targets/WORKSPACE new file mode 100644 index 0000000000..faff6af87a --- /dev/null +++ b/gazelle/python/testdata/binary_targets/WORKSPACE @@ -0,0 +1 @@ +# This is a Bazel workspace for the Gazelle test data. diff --git a/gazelle/python/testdata/binary_targets/bar.py b/gazelle/python/testdata/binary_targets/bar.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/binary_targets/bar_binary.py b/gazelle/python/testdata/binary_targets/bar_binary.py new file mode 100644 index 0000000000..1623c29a64 --- /dev/null +++ b/gazelle/python/testdata/binary_targets/bar_binary.py @@ -0,0 +1,4 @@ +import bar + +if __name__ == "__main__": + pass diff --git a/gazelle/python/testdata/binary_targets/bar_test.py b/gazelle/python/testdata/binary_targets/bar_test.py new file mode 100644 index 0000000000..11de95a1b3 --- /dev/null +++ b/gazelle/python/testdata/binary_targets/bar_test.py @@ -0,0 +1 @@ +import bar_binary diff --git a/gazelle/python/testdata/binary_targets/baz.py b/gazelle/python/testdata/binary_targets/baz.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/binary_targets/name_main_test.py b/gazelle/python/testdata/binary_targets/name_main_test.py new file mode 100644 index 0000000000..b1c81fcc03 --- /dev/null +++ b/gazelle/python/testdata/binary_targets/name_main_test.py @@ -0,0 +1,3 @@ +# This should make a py_test target because of the filename +if __name__ == "__main__": + pass diff --git a/gazelle/python/testdata/binary_targets/single_quote_main.py b/gazelle/python/testdata/binary_targets/single_quote_main.py new file mode 100644 index 0000000000..367012ae92 --- /dev/null +++ b/gazelle/python/testdata/binary_targets/single_quote_main.py @@ -0,0 +1,4 @@ +import bar_test + +if __name__ == '__main__': + pass diff --git a/gazelle/python/testdata/binary_targets/test.yaml b/gazelle/python/testdata/binary_targets/test.yaml new file mode 100644 index 0000000000..ed97d539c0 --- /dev/null +++ b/gazelle/python/testdata/binary_targets/test.yaml @@ -0,0 +1 @@ +--- pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy