Skip to content

Commit 5ba63a8

Browse files
siddharthabaignas
andauthored
fix(gazelle): __init__.py in per-file targets (#1582)
As per Python spec, `__init__.py` files are depended upon by every file in the package, so let's make sure that our generated targets also understand this implicit dependency. Note that because Python module dependencies are not a DAG, we can not depend on the Bazel target for `__init__.py` files (to avoid cycles in Bazel), and hence a non-empty `__init__.py` file is added to the `srcs` attribute of every `py_library` target. The language spec also says that each package depends on the parent package, but that is a less commonly used feature, and can make things more complex. From [importlib] docs: > Changed in version 3.3: Parent packages are automatically imported. From [import] language reference: > Importing parent.one will implicitly execute parent/__init__.py and parent/one/__init__.py. [importlib]: https://docs.python.org/3/library/importlib.html#importlib.import_module [import]: https://docs.python.org/3/reference/import.html#regular-packages --------- Co-authored-by: Ignas Anikevicius <240938+aignas@users.noreply.github.com>
1 parent eca368a commit 5ba63a8

File tree

11 files changed

+117
-49
lines changed

11 files changed

+117
-49
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ A brief description of the categories of changes:
5050
### Added
5151

5252
* (docs) bzlmod extensions are now documented on rules-python.readthedocs.io
53+
* (gazelle) `file` generation mode can now also add `__init__.py` to the srcs
54+
attribute for every target in the package. This is enabled through a separate
55+
directive `python_generation_mode_per_file_include_init`.
5356

5457
[0.XX.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.XX.0
5558

gazelle/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ Python-specific directives are as follows:
184184
| Controls whether the Python import statements should be validated. Can be "true" or "false" | |
185185
| `# gazelle:python_generation_mode`| `package` |
186186
| Controls the target generation mode. Can be "file", "package", or "project" | |
187+
| `# gazelle:python_generation_mode_per_file_include_init`| `package` |
188+
| Controls whether `__init__.py` files are included as srcs in each generated target when target generation mode is "file". Can be "true", or "false" | |
187189
| `# gazelle:python_library_naming_convention`| `$package_name$` |
188190
| Controls the `py_library` naming convention. It interpolates \$package_name\$ with the Bazel package name. E.g. if the Bazel package name is `foo`, setting this to `$package_name$_my_lib` would result in a generated target named `foo_my_lib`. | |
189191
| `# gazelle:python_binary_naming_convention` | `$package_name$_bin` |

gazelle/python/configure.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func (py *Configurer) KnownDirectives() []string {
5959
pythonconfig.IgnoreDependenciesDirective,
6060
pythonconfig.ValidateImportStatementsDirective,
6161
pythonconfig.GenerationMode,
62+
pythonconfig.GenerationModePerFileIncludeInit,
6263
pythonconfig.LibraryNamingConvention,
6364
pythonconfig.BinaryNamingConvention,
6465
pythonconfig.TestNamingConvention,
@@ -149,6 +150,12 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
149150
pythonconfig.GenerationMode, d.Value)
150151
log.Fatal(err)
151152
}
153+
case pythonconfig.GenerationModePerFileIncludeInit:
154+
v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
155+
if err != nil {
156+
log.Fatal(err)
157+
}
158+
config.SetPerFileGenerationIncludeInit(v)
152159
case pythonconfig.LibraryNamingConvention:
153160
config.SetLibraryNamingConvention(strings.TrimSpace(d.Value))
154161
case pythonconfig.BinaryNamingConvention:

gazelle/python/generate.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,18 +272,16 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
272272
result.Imports = append(result.Imports, pyLibrary.PrivateAttr(config.GazelleImportsKey))
273273
}
274274
if cfg.PerFileGeneration() {
275+
hasInit, nonEmptyInit := hasLibraryEntrypointFile(args.Dir)
275276
pyLibraryFilenames.Each(func(index int, filename interface{}) {
276-
if filename == pyLibraryEntrypointFilename {
277-
stat, err := os.Stat(filepath.Join(args.Dir, filename.(string)))
278-
if err != nil {
279-
log.Fatalf("ERROR: %v\n", err)
280-
}
281-
if stat.Size() == 0 {
282-
return // ignore empty __init__.py
283-
}
277+
pyLibraryTargetName := strings.TrimSuffix(filepath.Base(filename.(string)), ".py")
278+
if filename == pyLibraryEntrypointFilename && !nonEmptyInit {
279+
return // ignore empty __init__.py.
284280
}
285281
srcs := treeset.NewWith(godsutils.StringComparator, filename)
286-
pyLibraryTargetName := strings.TrimSuffix(filepath.Base(filename.(string)), ".py")
282+
if cfg.PerFileGenerationIncludeInit() && hasInit && nonEmptyInit {
283+
srcs.Add(pyLibraryEntrypointFilename)
284+
}
287285
appendPyLibrary(srcs, pyLibraryTargetName)
288286
})
289287
} else if !pyLibraryFilenames.Empty() {
@@ -468,6 +466,19 @@ func hasEntrypointFile(dir string) bool {
468466
return false
469467
}
470468

469+
// hasLibraryEntrypointFile returns if the given directory has the library
470+
// entrypoint file, and if it is non-empty.
471+
func hasLibraryEntrypointFile(dir string) (bool, bool) {
472+
stat, err := os.Stat(filepath.Join(dir, pyLibraryEntrypointFilename))
473+
if os.IsNotExist(err) {
474+
return false, false
475+
}
476+
if err != nil {
477+
log.Fatalf("ERROR: %v\n", err)
478+
}
479+
return true, stat.Size() != 0
480+
}
481+
471482
// isEntrypointFile returns whether the given path is an entrypoint file. The
472483
// given path can be absolute or relative.
473484
func isEntrypointFile(path string) bool {

gazelle/python/resolve.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,17 @@ func (py *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []reso
6161
provides := make([]resolve.ImportSpec, 0, len(srcs)+1)
6262
for _, src := range srcs {
6363
ext := filepath.Ext(src)
64-
if ext == ".py" {
65-
pythonProjectRoot := cfg.PythonProjectRoot()
66-
provide := importSpecFromSrc(pythonProjectRoot, f.Pkg, src)
67-
provides = append(provides, provide)
64+
if ext != ".py" {
65+
continue
6866
}
67+
if cfg.PerFileGeneration() && len(srcs) > 1 && src == pyLibraryEntrypointFilename {
68+
// Do not provide import spec from __init__.py when it is being included as
69+
// part of another module.
70+
continue
71+
}
72+
pythonProjectRoot := cfg.PythonProjectRoot()
73+
provide := importSpecFromSrc(pythonProjectRoot, f.Pkg, src)
74+
provides = append(provides, provide)
6975
}
7076
if len(provides) == 0 {
7177
return nil
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
load("@rules_python//python:defs.bzl", "py_library")
22

33
# gazelle:python_generation_mode file
4+
# gazelle:python_generation_mode_per_file_include_init true

gazelle/python/testdata/per_file_non_empty_init/BUILD.out

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
load("@rules_python//python:defs.bzl", "py_library")
22

33
# gazelle:python_generation_mode file
4+
# gazelle:python_generation_mode_per_file_include_init true
45

56
py_library(
67
name = "__init__",
@@ -11,6 +12,9 @@ py_library(
1112

1213
py_library(
1314
name = "foo",
14-
srcs = ["foo.py"],
15+
srcs = [
16+
"__init__.py",
17+
"foo.py",
18+
],
1519
visibility = ["//:__subpackages__"],
1620
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# gazelle:python_generation_mode_per_file_include_init true

gazelle/python/testdata/per_file_subdirs/bar/BUILD.out

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
load("@rules_python//python:defs.bzl", "py_library", "py_test")
22

3+
# gazelle:python_generation_mode_per_file_include_init true
4+
35
py_library(
46
name = "__init__",
57
srcs = ["__init__.py"],
68
visibility = ["//:__subpackages__"],
79
)
810

11+
py_library(
12+
name = "bar",
13+
srcs = [
14+
"__init__.py",
15+
"bar.py",
16+
],
17+
visibility = ["//:__subpackages__"],
18+
)
19+
920
py_library(
1021
name = "foo",
11-
srcs = ["foo.py"],
22+
srcs = [
23+
"__init__.py",
24+
"foo.py",
25+
],
1226
visibility = ["//:__subpackages__"],
1327
)
1428

gazelle/python/testdata/per_file_subdirs/bar/bar.py

Whitespace-only changes.

0 commit comments

Comments
 (0)
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