From df9b83a38f6f6c0751eeed2262eef7addc8b2d47 Mon Sep 17 00:00:00 2001 From: yushan Date: Wed, 7 May 2025 22:20:05 +0000 Subject: [PATCH 1/4] Resolve dependencies when imports are relative to the package path --- gazelle/python/file_parser.go | 4 +- gazelle/python/resolve.go | 48 ++++++++++++++++++- .../testdata/relative_imports/README.md | 4 -- .../relative_imports_package_mode/BUILD.in | 1 + .../relative_imports_package_mode/BUILD.out | 14 ++++++ .../relative_imports_package_mode/README.md | 3 ++ .../WORKSPACE | 0 .../relative_imports_package_mode/__main__.py | 5 ++ .../package1}/BUILD.in | 0 .../package1/BUILD.out | 10 ++++ .../package1/module1.py | 0 .../package1/module2.py | 0 .../package2/BUILD.in | 0 .../package2/BUILD.out | 12 +++++ .../package2/__init__.py | 20 ++++++++ .../package2/library/BUILD.in | 0 .../package2/library/BUILD.out | 7 +++ .../package2/library/__init__.py | 14 ++++++ .../package2/module3.py | 5 ++ .../package2/module4.py | 2 + .../test.yaml | 0 .../BUILD.in | 1 + .../BUILD.out | 7 +-- .../relative_imports_project_mode/README.md | 4 ++ .../relative_imports_project_mode/WORKSPACE | 1 + .../__main__.py | 0 .../package1/module1.py | 19 ++++++++ .../package1/module2.py | 17 +++++++ .../package2/BUILD.in | 0 .../package2/BUILD.out | 0 .../package2/__init__.py | 0 .../package2/module3.py | 0 .../package2/module4.py | 0 .../package2/subpackage1/module5.py | 0 .../relative_imports_project_mode/test.yaml | 15 ++++++ 35 files changed, 203 insertions(+), 10 deletions(-) delete mode 100644 gazelle/python/testdata/relative_imports/README.md create mode 100644 gazelle/python/testdata/relative_imports_package_mode/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/README.md rename gazelle/python/testdata/{relative_imports => relative_imports_package_mode}/WORKSPACE (100%) create mode 100644 gazelle/python/testdata/relative_imports_package_mode/__main__.py rename gazelle/python/testdata/{relative_imports/package2 => relative_imports_package_mode/package1}/BUILD.in (100%) create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out rename gazelle/python/testdata/{relative_imports => relative_imports_package_mode}/package1/module1.py (100%) rename gazelle/python/testdata/{relative_imports => relative_imports_package_mode}/package1/module2.py (100%) create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/__init__.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/library/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/library/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/library/__init__.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/module3.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package2/module4.py rename gazelle/python/testdata/{relative_imports => relative_imports_package_mode}/test.yaml (100%) rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/BUILD.in (61%) rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/BUILD.out (70%) create mode 100644 gazelle/python/testdata/relative_imports_project_mode/README.md create mode 100644 gazelle/python/testdata/relative_imports_project_mode/WORKSPACE rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/__main__.py (100%) create mode 100644 gazelle/python/testdata/relative_imports_project_mode/package1/module1.py create mode 100644 gazelle/python/testdata/relative_imports_project_mode/package1/module2.py create mode 100644 gazelle/python/testdata/relative_imports_project_mode/package2/BUILD.in rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/package2/BUILD.out (100%) rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/package2/__init__.py (100%) rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/package2/module3.py (100%) rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/package2/module4.py (100%) rename gazelle/python/testdata/{relative_imports => relative_imports_project_mode}/package2/subpackage1/module5.py (100%) create mode 100644 gazelle/python/testdata/relative_imports_project_mode/test.yaml diff --git a/gazelle/python/file_parser.go b/gazelle/python/file_parser.go index c147984fc3..06b63be989 100644 --- a/gazelle/python/file_parser.go +++ b/gazelle/python/file_parser.go @@ -165,7 +165,9 @@ func (p *FileParser) parseImportStatements(node *sitter.Node) bool { } } else if node.Type() == sitterNodeTypeImportFromStatement { from := node.Child(1).Content(p.code) - if strings.HasPrefix(from, ".") { + // If the import is from the current package, we don't need to add it to the modules i.e. from . import Class1. + // If the import is from a different relative package i.e. from .package1 import foo, we need to add it to the modules. + if from == "." { return true } for j := 3; j < int(node.ChildCount()); j++ { diff --git a/gazelle/python/resolve.go b/gazelle/python/resolve.go index 7a2ec3d68a..9c6da483a9 100644 --- a/gazelle/python/resolve.go +++ b/gazelle/python/resolve.go @@ -148,12 +148,56 @@ func (py *Resolver) Resolve( modules := modulesRaw.(*treeset.Set) it := modules.Iterator() explainDependency := os.Getenv("EXPLAIN_DEPENDENCY") + // Resolve relative paths for package generation + isPackageGeneration := !cfg.PerFileGeneration() && !cfg.CoarseGrainedGeneration() hasFatalError := false MODULES_LOOP: for it.Next() { mod := it.Value().(module) - moduleParts := strings.Split(mod.Name, ".") - possibleModules := []string{mod.Name} + moduleName := mod.Name + // Transform relative imports `.` or `..foo.bar` into the package path from root. + if strings.HasPrefix(moduleName, ".") { + // If not package generation mode, skip relative imports + if !isPackageGeneration { + continue MODULES_LOOP + } + relativeDepth := 0 + for i := 0; i < len(moduleName); i++ { + if moduleName[i] == '.' { + relativeDepth++ + } else { + break + } + } + + // Extract suffix after leading dots + relativeSuffix := moduleName[relativeDepth:] + var relativeSuffixParts []string + if relativeSuffix != "" { + relativeSuffixParts = strings.Split(relativeSuffix, ".") + } + + // Split current package label into parts + pkgParts := strings.Split(from.Pkg, "/") + + if relativeDepth- 1 > len(pkgParts) { + // Trying to go above the root + log.Printf("ERROR: Invalid relative import %q in %q: exceeds package root.", moduleName, mod.Filepath) + continue MODULES_LOOP + } + + // Go up `relativeDepth - 1` levels + baseParts := pkgParts + if relativeDepth > 1 { + baseParts = pkgParts[:len(pkgParts)-(relativeDepth-1)] + } + + absParts := append(baseParts, relativeSuffixParts...) + moduleName = strings.Join(absParts, ".") + } + + moduleParts := strings.Split(moduleName, ".") + possibleModules := []string{moduleName} for len(moduleParts) > 1 { // Iterate back through the possible imports until // a match is found. diff --git a/gazelle/python/testdata/relative_imports/README.md b/gazelle/python/testdata/relative_imports/README.md deleted file mode 100644 index 1937cbcf4a..0000000000 --- a/gazelle/python/testdata/relative_imports/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Relative imports - -This test case asserts that the generated targets handle relative imports in -Python correctly. diff --git a/gazelle/python/testdata/relative_imports_package_mode/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/BUILD.in new file mode 100644 index 0000000000..421b48688a --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/BUILD.in @@ -0,0 +1 @@ +# gazelle:python_generation_mode package diff --git a/gazelle/python/testdata/relative_imports_package_mode/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/BUILD.out new file mode 100644 index 0000000000..4c790b8046 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/BUILD.out @@ -0,0 +1,14 @@ +load("@rules_python//python:defs.bzl", "py_binary") + +# gazelle:python_generation_mode package + +py_binary( + name = "relative_imports_package_mode_bin", + srcs = ["__main__.py"], + main = "__main__.py", + visibility = ["//:__subpackages__"], + deps = [ + "//package1", + "//package2", + ], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/README.md b/gazelle/python/testdata/relative_imports_package_mode/README.md new file mode 100644 index 0000000000..01c3fe79c6 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/README.md @@ -0,0 +1,3 @@ +# Resolve deps for relative imports + +This test case verifies that the generated targets correctly handle relative imports in Python. Specifically, when the Python generation mode is set to "package," it ensures that relative import statements such as from .foo import X are properly resolved to their corresponding modules. diff --git a/gazelle/python/testdata/relative_imports/WORKSPACE b/gazelle/python/testdata/relative_imports_package_mode/WORKSPACE similarity index 100% rename from gazelle/python/testdata/relative_imports/WORKSPACE rename to gazelle/python/testdata/relative_imports_package_mode/WORKSPACE diff --git a/gazelle/python/testdata/relative_imports_package_mode/__main__.py b/gazelle/python/testdata/relative_imports_package_mode/__main__.py new file mode 100644 index 0000000000..4fb887a803 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/__main__.py @@ -0,0 +1,5 @@ +from package1.module1 import function1 +from package2.module3 import function3 + +print(function1()) +print(function3()) diff --git a/gazelle/python/testdata/relative_imports/package2/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.in similarity index 100% rename from gazelle/python/testdata/relative_imports/package2/BUILD.in rename to gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.in diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out new file mode 100644 index 0000000000..6667417598 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out @@ -0,0 +1,10 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "package1", + srcs = [ + "module1.py", + "module2.py", + ], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports/package1/module1.py b/gazelle/python/testdata/relative_imports_package_mode/package1/module1.py similarity index 100% rename from gazelle/python/testdata/relative_imports/package1/module1.py rename to gazelle/python/testdata/relative_imports_package_mode/package1/module1.py diff --git a/gazelle/python/testdata/relative_imports/package1/module2.py b/gazelle/python/testdata/relative_imports_package_mode/package1/module2.py similarity index 100% rename from gazelle/python/testdata/relative_imports/package1/module2.py rename to gazelle/python/testdata/relative_imports_package_mode/package1/module2.py diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package2/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package2/BUILD.out new file mode 100644 index 0000000000..bd78108159 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package2/BUILD.out @@ -0,0 +1,12 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "package2", + srcs = [ + "__init__.py", + "module3.py", + "module4.py", + ], + visibility = ["//:__subpackages__"], + deps = ["//package2/library"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/__init__.py b/gazelle/python/testdata/relative_imports_package_mode/package2/__init__.py new file mode 100644 index 0000000000..3d19d80e21 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package2/__init__.py @@ -0,0 +1,20 @@ +from .library import add as _add +from .library import divide as _divide +from .library import multiply as _multiply +from .library import subtract as _subtract + + +def add(a, b): + return _add(a, b) + + +def divide(a, b): + return _divide(a, b) + + +def multiply(a, b): + return _multiply(a, b) + + +def subtract(a, b): + return _subtract(a, b) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/library/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package2/library/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/library/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package2/library/BUILD.out new file mode 100644 index 0000000000..d704b7fe93 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package2/library/BUILD.out @@ -0,0 +1,7 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "library", + srcs = ["__init__.py"], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/library/__init__.py b/gazelle/python/testdata/relative_imports_package_mode/package2/library/__init__.py new file mode 100644 index 0000000000..5f8fc62492 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package2/library/__init__.py @@ -0,0 +1,14 @@ +def add(a, b): + return a + b + + +def divide(a, b): + return a / b + + +def multiply(a, b): + return a * b + + +def subtract(a, b): + return a - b diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/module3.py b/gazelle/python/testdata/relative_imports_package_mode/package2/module3.py new file mode 100644 index 0000000000..6b955cfda6 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package2/module3.py @@ -0,0 +1,5 @@ +from .library import function5 + + +def function3(): + return "function3 " + function5() diff --git a/gazelle/python/testdata/relative_imports_package_mode/package2/module4.py b/gazelle/python/testdata/relative_imports_package_mode/package2/module4.py new file mode 100644 index 0000000000..6e69699985 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package2/module4.py @@ -0,0 +1,2 @@ +def function4(): + return "function4" diff --git a/gazelle/python/testdata/relative_imports/test.yaml b/gazelle/python/testdata/relative_imports_package_mode/test.yaml similarity index 100% rename from gazelle/python/testdata/relative_imports/test.yaml rename to gazelle/python/testdata/relative_imports_package_mode/test.yaml diff --git a/gazelle/python/testdata/relative_imports/BUILD.in b/gazelle/python/testdata/relative_imports_project_mode/BUILD.in similarity index 61% rename from gazelle/python/testdata/relative_imports/BUILD.in rename to gazelle/python/testdata/relative_imports_project_mode/BUILD.in index c04b5e5434..1059942bfb 100644 --- a/gazelle/python/testdata/relative_imports/BUILD.in +++ b/gazelle/python/testdata/relative_imports_project_mode/BUILD.in @@ -1 +1,2 @@ # gazelle:resolve py resolved_package //package2:resolved_package +# gazelle:python_generation_mode project diff --git a/gazelle/python/testdata/relative_imports/BUILD.out b/gazelle/python/testdata/relative_imports_project_mode/BUILD.out similarity index 70% rename from gazelle/python/testdata/relative_imports/BUILD.out rename to gazelle/python/testdata/relative_imports_project_mode/BUILD.out index bf9524480a..acdc914541 100644 --- a/gazelle/python/testdata/relative_imports/BUILD.out +++ b/gazelle/python/testdata/relative_imports_project_mode/BUILD.out @@ -1,9 +1,10 @@ load("@rules_python//python:defs.bzl", "py_binary", "py_library") # gazelle:resolve py resolved_package //package2:resolved_package +# gazelle:python_generation_mode project py_library( - name = "relative_imports", + name = "relative_imports_project_mode", srcs = [ "package1/module1.py", "package1/module2.py", @@ -12,12 +13,12 @@ py_library( ) py_binary( - name = "relative_imports_bin", + name = "relative_imports_project_mode_bin", srcs = ["__main__.py"], main = "__main__.py", visibility = ["//:__subpackages__"], deps = [ - ":relative_imports", + ":relative_imports_project_mode", "//package2", ], ) diff --git a/gazelle/python/testdata/relative_imports_project_mode/README.md b/gazelle/python/testdata/relative_imports_project_mode/README.md new file mode 100644 index 0000000000..ec5342389d --- /dev/null +++ b/gazelle/python/testdata/relative_imports_project_mode/README.md @@ -0,0 +1,4 @@ +# Relative imports + +This test case asserts that the generated targets handle relative imports in +Python correctly. This tests that if python generation mode is project, the relative paths are included in the subdirectories. diff --git a/gazelle/python/testdata/relative_imports_project_mode/WORKSPACE b/gazelle/python/testdata/relative_imports_project_mode/WORKSPACE new file mode 100644 index 0000000000..4959898cdd --- /dev/null +++ b/gazelle/python/testdata/relative_imports_project_mode/WORKSPACE @@ -0,0 +1 @@ +# This is a test data Bazel workspace. diff --git a/gazelle/python/testdata/relative_imports/__main__.py b/gazelle/python/testdata/relative_imports_project_mode/__main__.py similarity index 100% rename from gazelle/python/testdata/relative_imports/__main__.py rename to gazelle/python/testdata/relative_imports_project_mode/__main__.py diff --git a/gazelle/python/testdata/relative_imports_project_mode/package1/module1.py b/gazelle/python/testdata/relative_imports_project_mode/package1/module1.py new file mode 100644 index 0000000000..28502f1f84 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_project_mode/package1/module1.py @@ -0,0 +1,19 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .module2 import function2 + + +def function1(): + return "function1 " + function2() diff --git a/gazelle/python/testdata/relative_imports_project_mode/package1/module2.py b/gazelle/python/testdata/relative_imports_project_mode/package1/module2.py new file mode 100644 index 0000000000..0cbc5f0be0 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_project_mode/package1/module2.py @@ -0,0 +1,17 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def function2(): + return "function2" diff --git a/gazelle/python/testdata/relative_imports_project_mode/package2/BUILD.in b/gazelle/python/testdata/relative_imports_project_mode/package2/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports/package2/BUILD.out b/gazelle/python/testdata/relative_imports_project_mode/package2/BUILD.out similarity index 100% rename from gazelle/python/testdata/relative_imports/package2/BUILD.out rename to gazelle/python/testdata/relative_imports_project_mode/package2/BUILD.out diff --git a/gazelle/python/testdata/relative_imports/package2/__init__.py b/gazelle/python/testdata/relative_imports_project_mode/package2/__init__.py similarity index 100% rename from gazelle/python/testdata/relative_imports/package2/__init__.py rename to gazelle/python/testdata/relative_imports_project_mode/package2/__init__.py diff --git a/gazelle/python/testdata/relative_imports/package2/module3.py b/gazelle/python/testdata/relative_imports_project_mode/package2/module3.py similarity index 100% rename from gazelle/python/testdata/relative_imports/package2/module3.py rename to gazelle/python/testdata/relative_imports_project_mode/package2/module3.py diff --git a/gazelle/python/testdata/relative_imports/package2/module4.py b/gazelle/python/testdata/relative_imports_project_mode/package2/module4.py similarity index 100% rename from gazelle/python/testdata/relative_imports/package2/module4.py rename to gazelle/python/testdata/relative_imports_project_mode/package2/module4.py diff --git a/gazelle/python/testdata/relative_imports/package2/subpackage1/module5.py b/gazelle/python/testdata/relative_imports_project_mode/package2/subpackage1/module5.py similarity index 100% rename from gazelle/python/testdata/relative_imports/package2/subpackage1/module5.py rename to gazelle/python/testdata/relative_imports_project_mode/package2/subpackage1/module5.py diff --git a/gazelle/python/testdata/relative_imports_project_mode/test.yaml b/gazelle/python/testdata/relative_imports_project_mode/test.yaml new file mode 100644 index 0000000000..fcea77710f --- /dev/null +++ b/gazelle/python/testdata/relative_imports_project_mode/test.yaml @@ -0,0 +1,15 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- From 38103efa8dfe3bdc09c771c0e8f1af30bb7ad06d Mon Sep 17 00:00:00 2001 From: yushan Date: Fri, 23 May 2025 22:54:45 +0000 Subject: [PATCH 2/4] update --- gazelle/python/resolve.go | 41 ++++++++++++------- .../package1/BUILD.out | 1 + .../package1/__init__.py | 2 + .../package1/my_library/BUILD.in | 7 ++++ .../package1/my_library/BUILD.out | 7 ++++ .../package1/my_library/__init__.py | 2 + .../package1/my_library/foo/BUILD.in | 0 .../package1/my_library/foo/BUILD.out | 7 ++++ .../package1/my_library/foo/__init__.py | 2 + .../package1/subpackage1/BUILD.in | 10 +++++ .../package1/subpackage1/BUILD.out | 10 +++++ .../package1/subpackage1/__init__.py | 3 ++ .../package1/subpackage1/some_module.py | 3 ++ .../package1/subpackage1/subpackage2/BUILD.in | 0 .../subpackage1/subpackage2/BUILD.out | 16 ++++++++ .../subpackage1/subpackage2/__init__.py | 0 .../subpackage1/subpackage2/library/BUILD.in | 0 .../subpackage1/subpackage2/library/BUILD.out | 7 ++++ .../subpackage2/library/other_module.py | 0 .../subpackage1/subpackage2/script.py | 6 +++ 20 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/__init__.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/my_library/__init__.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/__init__.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/__init__.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/some_module.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/__init__.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/BUILD.in create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/BUILD.out create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/other_module.py create mode 100644 gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py diff --git a/gazelle/python/resolve.go b/gazelle/python/resolve.go index 9c6da483a9..f47b1b5957 100644 --- a/gazelle/python/resolve.go +++ b/gazelle/python/resolve.go @@ -71,6 +71,7 @@ func (py *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []reso } pythonProjectRoot := cfg.PythonProjectRoot() provide := importSpecFromSrc(pythonProjectRoot, f.Pkg, src) + // log.Printf("import to index: %v", provide.Imp) provides = append(provides, provide) } if len(provides) == 0 { @@ -156,46 +157,56 @@ func (py *Resolver) Resolve( mod := it.Value().(module) moduleName := mod.Name // Transform relative imports `.` or `..foo.bar` into the package path from root. - if strings.HasPrefix(moduleName, ".") { - // If not package generation mode, skip relative imports + if strings.HasPrefix(mod.From, ".") { if !isPackageGeneration { continue MODULES_LOOP } + + // Count number of leading dots in mod.From (e.g., ".." = 2, "...foo.bar" = 3) relativeDepth := 0 - for i := 0; i < len(moduleName); i++ { - if moduleName[i] == '.' { + for i := 0; i < len(mod.From); i++ { + if mod.From[i] == '.' { relativeDepth++ } else { break } } - // Extract suffix after leading dots - relativeSuffix := moduleName[relativeDepth:] - var relativeSuffixParts []string - if relativeSuffix != "" { - relativeSuffixParts = strings.Split(relativeSuffix, ".") + // Extract final symbol (e.g., "some_function") from mod.Name + imported := mod.Name + if idx := strings.LastIndex(mod.Name, "."); idx >= 0 { + imported = mod.Name[idx+1:] + } + + // Optional subpath in 'from' clause, e.g. "from ...my_library.foo import x" + fromPath := strings.TrimLeft(mod.From, ".") + var fromParts []string + if fromPath != "" { + fromParts = strings.Split(fromPath, ".") } - // Split current package label into parts + // Current Bazel package as path segments pkgParts := strings.Split(from.Pkg, "/") - if relativeDepth- 1 > len(pkgParts) { - // Trying to go above the root - log.Printf("ERROR: Invalid relative import %q in %q: exceeds package root.", moduleName, mod.Filepath) + if relativeDepth-1 > len(pkgParts) { + log.Printf("ERROR: Invalid relative import %q in %q: exceeds package root.", mod.Name, mod.Filepath) continue MODULES_LOOP } - // Go up `relativeDepth - 1` levels + // Go up relativeDepth - 1 levels baseParts := pkgParts if relativeDepth > 1 { baseParts = pkgParts[:len(pkgParts)-(relativeDepth-1)] } + // Build absolute module path + absParts := append([]string{}, baseParts...) // base path + absParts = append(absParts, fromParts...) // subpath from 'from' + absParts = append(absParts, imported) // actual imported symbol - absParts := append(baseParts, relativeSuffixParts...) moduleName = strings.Join(absParts, ".") } + moduleParts := strings.Split(moduleName, ".") possibleModules := []string{moduleName} for len(moduleParts) > 1 { diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out index 6667417598..c562ff07de 100644 --- a/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/BUILD.out @@ -3,6 +3,7 @@ load("@rules_python//python:defs.bzl", "py_library") py_library( name = "package1", srcs = [ + "__init__.py", "module1.py", "module2.py", ], diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/__init__.py b/gazelle/python/testdata/relative_imports_package_mode/package1/__init__.py new file mode 100644 index 0000000000..11ffb98647 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/__init__.py @@ -0,0 +1,2 @@ +def some_function(): + pass diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.in new file mode 100644 index 0000000000..80a4a22348 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.in @@ -0,0 +1,7 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "my_library", + srcs = ["__init__.py"], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.out new file mode 100644 index 0000000000..80a4a22348 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/BUILD.out @@ -0,0 +1,7 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "my_library", + srcs = ["__init__.py"], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/__init__.py b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/__init__.py new file mode 100644 index 0000000000..aaa161cd59 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/__init__.py @@ -0,0 +1,2 @@ +def some_function(): + return "some_function" diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/BUILD.out new file mode 100644 index 0000000000..58498ee3b3 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/BUILD.out @@ -0,0 +1,7 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "foo", + srcs = ["__init__.py"], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/__init__.py b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/__init__.py new file mode 100644 index 0000000000..aaa161cd59 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/my_library/foo/__init__.py @@ -0,0 +1,2 @@ +def some_function(): + return "some_function" diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.in new file mode 100644 index 0000000000..0a5b665c8d --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.in @@ -0,0 +1,10 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "subpackage1", + srcs = [ + "__init__.py", + "some_module.py", + ], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.out new file mode 100644 index 0000000000..0a5b665c8d --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/BUILD.out @@ -0,0 +1,10 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "subpackage1", + srcs = [ + "__init__.py", + "some_module.py", + ], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/__init__.py b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/__init__.py new file mode 100644 index 0000000000..02feaeb848 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/__init__.py @@ -0,0 +1,3 @@ + +def some_init(): + return "some_init" diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/some_module.py b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/some_module.py new file mode 100644 index 0000000000..3cae706242 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/some_module.py @@ -0,0 +1,3 @@ + +def some_function(): + return "some_function" diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/BUILD.out new file mode 100644 index 0000000000..8c34081210 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/BUILD.out @@ -0,0 +1,16 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "subpackage2", + srcs = [ + "__init__.py", + "script.py", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//package1/my_library", + "//package1/my_library/foo", + "//package1/subpackage1", + "//package1/subpackage1/subpackage2/library", + ], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/__init__.py b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/BUILD.out new file mode 100644 index 0000000000..9fe2e3d1d7 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/BUILD.out @@ -0,0 +1,7 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "library", + srcs = ["other_module.py"], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/other_module.py b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/library/other_module.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py new file mode 100644 index 0000000000..2f9ca7d732 --- /dev/null +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py @@ -0,0 +1,6 @@ + +from ...my_library import some_function # Import path should be package1.my_library.some_function +from ...my_library.foo import some_function # Import path should be package1.my_library.foo.some_function +from .library import other_module # Import path should be package1.subpackage1.subpackage2.library.other_module +from .. import some_module # Import path should be package1.subpackage1.some_module +from .. import some_function # Import path should be package1.subpackage1.some_function From 847fdae06c4243a83f4792400cd159b66b5be58f Mon Sep 17 00:00:00 2001 From: yushan Date: Tue, 27 May 2025 20:19:04 +0000 Subject: [PATCH 3/4] Address comments and add directive Update --- CHANGELOG.md | 4 ++ gazelle/README.md | 48 +++++++++++++++++-- gazelle/python/configure.go | 8 ++++ gazelle/python/resolve.go | 13 ++--- .../relative_imports_package_mode/BUILD.in | 1 + .../relative_imports_package_mode/BUILD.out | 1 + .../relative_imports_package_mode/README.md | 5 +- .../subpackage1/subpackage2/script.py | 17 ++++--- .../relative_imports_project_mode/README.md | 3 +- gazelle/pythonconfig/pythonconfig.go | 16 +++++++ 10 files changed, 95 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e48e3d4f3d..13340322af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,10 @@ END_UNRELEASED_TEMPLATE * (py_wheel) py_wheel always creates zip64-capable wheel zips * (providers) (experimental) {obj}`PyInfo.venv_symlinks` replaces `PyInfo.site_packages_symlinks` +* (gazelle) For package mode, resolve dependencies when imports are relative + to the package path. This is enabled via the + `# gazelle:experimental_allow_relative_imports` true directive. + (https://github.com/bazel-contrib/rules_python/issues/2203) {#v0-0-0-fixed} ### Fixed diff --git a/gazelle/README.md b/gazelle/README.md index 89ebaef4cd..7145217fa2 100644 --- a/gazelle/README.md +++ b/gazelle/README.md @@ -121,12 +121,12 @@ gazelle_python_manifest( requirements = "//:requirements_lock.txt", # include_stub_packages: bool (default: False) # If set to True, this flag automatically includes any corresponding type stub packages - # for the third-party libraries that are present and used. For example, if you have + # for the third-party libraries that are present and used. For example, if you have # `boto3` as a dependency, and this flag is enabled, the corresponding `boto3-stubs` # package will be automatically included in the BUILD file. # - # Enabling this feature helps ensure that type hints and stubs are readily available - # for tools like type checkers and IDEs, improving the development experience and + # Enabling this feature helps ensure that type hints and stubs are readily available + # for tools like type checkers and IDEs, improving the development experience and # reducing manual overhead in managing separate stub packages. include_stub_packages = True ) @@ -220,6 +220,8 @@ Python-specific directives are as follows: | Defines the format of the distribution name in labels to third-party deps. Useful for using Gazelle plugin with other rules with different repository conventions (e.g. `rules_pycross`). Full label is always prepended with (pip) repository name, e.g. `@pip//numpy`. | | `# gazelle:python_label_normalization` | `snake_case` | | Controls how distribution names in labels to third-party deps are normalized. Useful for using Gazelle plugin with other rules with different label conventions (e.g. `rules_pycross` uses PEP-503). Can be "snake_case", "none", or "pep503". | +| `# gazelle:experimental_allow_relative_imports` | `false` | +| Controls whether Gazelle resolves dependencies for import statements that use paths relative to the current package. Can be "true" or "false".| #### Directive: `python_root`: @@ -468,7 +470,7 @@ def py_test(name, main=None, **kwargs): name = "__test__", deps = ["@pip_pytest//:pkg"], # change this to the pytest target in your repo. ) - + deps.append(":__test__") main = ":__test__.py" @@ -581,6 +583,44 @@ deps = [ ] ``` +#### Annotation: `experimental_allow_relative_imports` +Enables experimental support for resolving relative imports in +`python_generation_mode package`. + +By default, when `# gazelle:python_generation_mode package` is enabled, +relative imports (e.g., from .library import foo) are not added to the +deps field of the generated target. This results in incomplete py_library +rules that lack required dependencies on sibling packages. + +Example: +Given this Python file import: +```python +from .library import add as _add +from .library import subtract as _subtract +``` + +Expected BUILD file output: +```starlark +py_library( + name = "py_default_library", + srcs = ["__init__.py"], + deps = [ + "//example/library:py_default_library", + ], + visibility = ["//visibility:public"], +) +``` + +Actual output without this annotation: +```starlark +py_library( + name = "py_default_library", + srcs = ["__init__.py"], + visibility = ["//visibility:public"], +) +``` +If the directive is set to `true`, gazelle will resolve imports +that are relative to the current package. ### Libraries diff --git a/gazelle/python/configure.go b/gazelle/python/configure.go index a00b0ba0ba..ae0f7ee1d1 100644 --- a/gazelle/python/configure.go +++ b/gazelle/python/configure.go @@ -68,6 +68,7 @@ func (py *Configurer) KnownDirectives() []string { pythonconfig.TestFilePattern, pythonconfig.LabelConvention, pythonconfig.LabelNormalization, + pythonconfig.ExperimentalAllowRelativeImports, } } @@ -222,6 +223,13 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) { default: config.SetLabelNormalization(pythonconfig.DefaultLabelNormalizationType) } + case pythonconfig.ExperimentalAllowRelativeImports: + v, err := strconv.ParseBool(strings.TrimSpace(d.Value)) + if err != nil { + log.Printf("invalid value for gazelle:%s in %q: %q", + pythonconfig.ExperimentalAllowRelativeImports, rel, d.Value) + } + config.SetExperimentalAllowRelativeImports(v) } } diff --git a/gazelle/python/resolve.go b/gazelle/python/resolve.go index f47b1b5957..ebc7d13b45 100644 --- a/gazelle/python/resolve.go +++ b/gazelle/python/resolve.go @@ -71,7 +71,6 @@ func (py *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []reso } pythonProjectRoot := cfg.PythonProjectRoot() provide := importSpecFromSrc(pythonProjectRoot, f.Pkg, src) - // log.Printf("import to index: %v", provide.Imp) provides = append(provides, provide) } if len(provides) == 0 { @@ -158,18 +157,14 @@ func (py *Resolver) Resolve( moduleName := mod.Name // Transform relative imports `.` or `..foo.bar` into the package path from root. if strings.HasPrefix(mod.From, ".") { - if !isPackageGeneration { + if !cfg.ExperimentalAllowRelativeImports() || !isPackageGeneration { continue MODULES_LOOP } // Count number of leading dots in mod.From (e.g., ".." = 2, "...foo.bar" = 3) - relativeDepth := 0 - for i := 0; i < len(mod.From); i++ { - if mod.From[i] == '.' { - relativeDepth++ - } else { - break - } + relativeDepth := strings.IndexFunc(mod.From, func(r rune) bool { return r != '.' }) + if relativeDepth == -1 { + relativeDepth = len(mod.From) } // Extract final symbol (e.g., "some_function") from mod.Name diff --git a/gazelle/python/testdata/relative_imports_package_mode/BUILD.in b/gazelle/python/testdata/relative_imports_package_mode/BUILD.in index 421b48688a..78ef0a7863 100644 --- a/gazelle/python/testdata/relative_imports_package_mode/BUILD.in +++ b/gazelle/python/testdata/relative_imports_package_mode/BUILD.in @@ -1 +1,2 @@ # gazelle:python_generation_mode package +# gazelle:experimental_allow_relative_imports true diff --git a/gazelle/python/testdata/relative_imports_package_mode/BUILD.out b/gazelle/python/testdata/relative_imports_package_mode/BUILD.out index 4c790b8046..f51b516cab 100644 --- a/gazelle/python/testdata/relative_imports_package_mode/BUILD.out +++ b/gazelle/python/testdata/relative_imports_package_mode/BUILD.out @@ -1,6 +1,7 @@ load("@rules_python//python:defs.bzl", "py_binary") # gazelle:python_generation_mode package +# gazelle:experimental_allow_relative_imports true py_binary( name = "relative_imports_package_mode_bin", diff --git a/gazelle/python/testdata/relative_imports_package_mode/README.md b/gazelle/python/testdata/relative_imports_package_mode/README.md index 01c3fe79c6..eb9f8c096c 100644 --- a/gazelle/python/testdata/relative_imports_package_mode/README.md +++ b/gazelle/python/testdata/relative_imports_package_mode/README.md @@ -1,3 +1,6 @@ # Resolve deps for relative imports -This test case verifies that the generated targets correctly handle relative imports in Python. Specifically, when the Python generation mode is set to "package," it ensures that relative import statements such as from .foo import X are properly resolved to their corresponding modules. +This test case verifies that the generated targets correctly handle relative imports in +Python. Specifically, when the Python generation mode is set to "package," it ensures +that relative import statements such as from .foo import X are properly resolved to +their corresponding modules. diff --git a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py index 2f9ca7d732..e93f07719a 100644 --- a/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py +++ b/gazelle/python/testdata/relative_imports_package_mode/package1/subpackage1/subpackage2/script.py @@ -1,6 +1,11 @@ - -from ...my_library import some_function # Import path should be package1.my_library.some_function -from ...my_library.foo import some_function # Import path should be package1.my_library.foo.some_function -from .library import other_module # Import path should be package1.subpackage1.subpackage2.library.other_module -from .. import some_module # Import path should be package1.subpackage1.some_module -from .. import some_function # Import path should be package1.subpackage1.some_function +from ...my_library import ( + some_function, +) # Import path should be package1.my_library.some_function +from ...my_library.foo import ( + some_function, +) # Import path should be package1.my_library.foo.some_function +from .library import ( + other_module, +) # Import path should be package1.subpackage1.subpackage2.library.other_module +from .. import some_module # Import path should be package1.subpackage1.some_module +from .. import some_function # Import path should be package1.subpackage1.some_function diff --git a/gazelle/python/testdata/relative_imports_project_mode/README.md b/gazelle/python/testdata/relative_imports_project_mode/README.md index ec5342389d..3c95a36e62 100644 --- a/gazelle/python/testdata/relative_imports_project_mode/README.md +++ b/gazelle/python/testdata/relative_imports_project_mode/README.md @@ -1,4 +1,5 @@ # Relative imports This test case asserts that the generated targets handle relative imports in -Python correctly. This tests that if python generation mode is project, the relative paths are included in the subdirectories. +Python correctly. This tests that if python generation mode is project, +the relative paths are included in the subdirectories. diff --git a/gazelle/pythonconfig/pythonconfig.go b/gazelle/pythonconfig/pythonconfig.go index 866339d449..e0a2b8a469 100644 --- a/gazelle/pythonconfig/pythonconfig.go +++ b/gazelle/pythonconfig/pythonconfig.go @@ -91,6 +91,9 @@ const ( // names of labels to third-party dependencies are normalized. Supported values // are 'none', 'pep503' and 'snake_case' (default). See LabelNormalizationType. LabelNormalization = "python_label_normalization" + // ExperimentalAllowRelativeImports represents the directive that controls + // whether relative imports are allowed. + ExperimentalAllowRelativeImports = "experimental_allow_relative_imports" ) // GenerationModeType represents one of the generation modes for the Python @@ -177,6 +180,7 @@ type Config struct { testFilePattern []string labelConvention string labelNormalization LabelNormalizationType + experimentalAllowRelativeImports bool } type LabelNormalizationType int @@ -212,6 +216,7 @@ func New( testFilePattern: strings.Split(DefaultTestFilePatternString, ","), labelConvention: DefaultLabelConvention, labelNormalization: DefaultLabelNormalizationType, + experimentalAllowRelativeImports: false, } } @@ -244,6 +249,7 @@ func (c *Config) NewChild() *Config { testFilePattern: c.testFilePattern, labelConvention: c.labelConvention, labelNormalization: c.labelNormalization, + experimentalAllowRelativeImports: c.experimentalAllowRelativeImports, } } @@ -520,6 +526,16 @@ func (c *Config) LabelNormalization() LabelNormalizationType { return c.labelNormalization } +// SetExperimentalAllowRelativeImports sets whether relative imports are allowed. +func (c *Config) SetExperimentalAllowRelativeImports(allowRelativeImports bool) { + c.experimentalAllowRelativeImports = allowRelativeImports +} + +// ExperimentalAllowRelativeImports returns whether relative imports are allowed. +func (c *Config) ExperimentalAllowRelativeImports() bool { + return c.experimentalAllowRelativeImports +} + // FormatThirdPartyDependency returns a label to a third-party dependency performing all formating and normalization. func (c *Config) FormatThirdPartyDependency(repositoryName string, distributionName string) label.Label { conventionalDistributionName := strings.ReplaceAll(c.labelConvention, distributionNameLabelConventionSubstitution, distributionName) From 332e008301459778cbd8359e0eeb3ac32dc2ff51 Mon Sep 17 00:00:00 2001 From: Douglas Thor Date: Sat, 21 Jun 2025 16:48:36 -0700 Subject: [PATCH 4/4] Update gazelle/README.md --- gazelle/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gazelle/README.md b/gazelle/README.md index 7145217fa2..58ec55eb11 100644 --- a/gazelle/README.md +++ b/gazelle/README.md @@ -583,7 +583,7 @@ deps = [ ] ``` -#### Annotation: `experimental_allow_relative_imports` +#### Directive: `experimental_allow_relative_imports` Enables experimental support for resolving relative imports in `python_generation_mode package`.