Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ END_UNRELEASED_TEMPLATE
([#2921](https://github.com/bazel-contrib/rules_python/issues/2921)).
* (repl) Normalize the path for the `REPL` stub to make it possible to use the
default stub template from outside `rules_python` ({gh-issue}`3101`).
* (gazelle) Fixes gazelle adding sibling module dependencies to resolve
absolute imports (Python 2's behavior without `absolute_import`). Previous
behavior can be restored using the directive
`# gazelle:python_resolve_sibling_imports true`

{#v0-0-0-added}
### Added
Expand Down
2 changes: 2 additions & 0 deletions gazelle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ Python-specific directives are as follows:
| Controls whether to generate a separate `pyi_deps` attribute for type-checking dependencies or merge them into the regular `deps` attribute. When `false` (default), type-checking dependencies are merged into `deps` for backward compatibility. When `true`, generates separate `pyi_deps`. Imports in blocks with the format `if typing.TYPE_CHECKING:`/`if TYPE_CHECKING:` and type-only stub packages (eg. boto3-stubs) are recognized as type-checking dependencies. |
| [`# gazelle:python_generate_proto`](#directive-python_generate_proto) | `false` |
| Controls whether to generate a `py_proto_library` for each `proto_library` in the package. By default we load this rule from the `@protobuf` repository; use `gazelle:map_kind` if you need to load this from somewhere else. |
| `# gazelle:python_resolve_sibling_imports` | `false` |
| Allows absolute imports to be resolved to sibling modules (Python 2's behavior without `absolute_import`). |

#### Directive: `python_root`:

Expand Down
7 changes: 7 additions & 0 deletions gazelle/python/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func (py *Configurer) KnownDirectives() []string {
pythonconfig.GeneratePyiDeps,
pythonconfig.ExperimentalAllowRelativeImports,
pythonconfig.GenerateProto,
pythonconfig.PythonResolveSiblingImports,
}
}

Expand Down Expand Up @@ -247,6 +248,12 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
log.Fatal(err)
}
config.SetGenerateProto(v)
case pythonconfig.PythonResolveSiblingImports:
v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
if err != nil {
log.Fatal(err)
}
config.SetResolveSiblingImports(v)
}
}

Expand Down
16 changes: 8 additions & 8 deletions gazelle/python/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
fqTarget.String(), actualPyBinaryKind, err)
continue
}
pyBinary := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
pyBinary := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addVisibility(visibility).
addSrc(filename).
addModuleDependencies(mainModules[filename]).
Expand Down Expand Up @@ -301,7 +301,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors.Add(err)
}

pyLibrary := newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
pyLibrary := newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addVisibility(visibility).
addSrcs(srcs).
addModuleDependencies(allDeps).
Expand Down Expand Up @@ -354,7 +354,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors.Add(err)
}

pyBinaryTarget := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
pyBinaryTarget := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
setMain(pyBinaryEntrypointFilename).
addVisibility(visibility).
addSrc(pyBinaryEntrypointFilename).
Expand Down Expand Up @@ -387,7 +387,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors.Add(err)
}

conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyFileNames).
conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addSrc(conftestFilename).
addModuleDependencies(deps).
addResolvedDependencies(annotations.includeDeps).
Expand Down Expand Up @@ -419,7 +419,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
fqTarget.String(), actualPyTestKind, err, pythonconfig.TestNamingConvention)
collisionErrors.Add(err)
}
return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyFileNames).
return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addSrcs(srcs).
addModuleDependencies(deps).
addResolvedDependencies(annotations.includeDeps).
Expand Down Expand Up @@ -476,7 +476,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes

for _, pyTestTarget := range pyTestTargets {
if conftest != nil {
conftestModule := Module{Name: strings.TrimSuffix(conftestFilename, ".py")}
conftestModule := Module{Name: importSpecFromSrc(pythonProjectRoot, args.Rel, conftestFilename).Imp}
if pyTestTarget.annotations.includePytestConftest == nil {
// unset; default behavior
pyTestTarget.addModuleDependency(conftestModule)
Expand Down Expand Up @@ -605,7 +605,7 @@ func generateProtoLibraries(args language.GenerateArgs, cfg *pythonconfig.Config
pyProtoLibraryName = ruleName
}

pyProtoLibrary := newTargetBuilder(pyProtoLibraryKind, pyProtoLibraryName, pythonProjectRoot, args.Rel, &emptySiblings).
pyProtoLibrary := newTargetBuilder(pyProtoLibraryKind, pyProtoLibraryName, pythonProjectRoot, args.Rel, &emptySiblings, false).
addVisibility(visibility).
addResolvedDependency(":" + protoRuleName).
generateImportsAttribute().build()
Expand All @@ -622,7 +622,7 @@ func generateProtoLibraries(args language.GenerateArgs, cfg *pythonconfig.Config
continue
}

emptyRule := newTargetBuilder(pyProtoLibraryKind, ruleName, pythonProjectRoot, args.Rel, &emptySiblings).build()
emptyRule := newTargetBuilder(pyProtoLibraryKind, ruleName, pythonProjectRoot, args.Rel, &emptySiblings, false).build()
res.Empty = append(res.Empty, emptyRule)
}

Expand Down
10 changes: 4 additions & 6 deletions gazelle/python/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,14 @@ 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)
moduleName := mod.Name
// Transform relative imports `.` or `..foo.bar` into the package path from root.
if strings.HasPrefix(mod.From, ".") {
if !cfg.ExperimentalAllowRelativeImports() || !isPackageGeneration {
if !cfg.ExperimentalAllowRelativeImports() {
continue MODULES_LOOP
}

Expand Down Expand Up @@ -210,9 +208,9 @@ func (py *Resolver) Resolve(
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([]string{}, baseParts...) // base path
absParts = append(absParts, fromParts...) // subpath from 'from'
absParts = append(absParts, imported) // actual imported symbol

moduleName = strings.Join(absParts, ".")
}
Expand Down
53 changes: 27 additions & 26 deletions gazelle/python/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,36 @@ import (

// targetBuilder builds targets to be generated by Gazelle.
type targetBuilder struct {
kind string
name string
pythonProjectRoot string
bzlPackage string
srcs *treeset.Set
siblingSrcs *treeset.Set
deps *treeset.Set
resolvedDeps *treeset.Set
visibility *treeset.Set
main *string
imports []string
testonly bool
annotations *annotations
kind string
name string
pythonProjectRoot string
bzlPackage string
srcs *treeset.Set
siblingSrcs *treeset.Set
deps *treeset.Set
resolvedDeps *treeset.Set
visibility *treeset.Set
main *string
imports []string
testonly bool
annotations *annotations
resolveSiblingImports bool
}

// newTargetBuilder constructs a new targetBuilder.
func newTargetBuilder(kind, name, pythonProjectRoot, bzlPackage string, siblingSrcs *treeset.Set) *targetBuilder {
func newTargetBuilder(kind, name, pythonProjectRoot, bzlPackage string, siblingSrcs *treeset.Set, resolveSiblingImports bool) *targetBuilder {
return &targetBuilder{
kind: kind,
name: name,
pythonProjectRoot: pythonProjectRoot,
bzlPackage: bzlPackage,
srcs: treeset.NewWith(godsutils.StringComparator),
siblingSrcs: siblingSrcs,
deps: treeset.NewWith(moduleComparator),
resolvedDeps: treeset.NewWith(godsutils.StringComparator),
visibility: treeset.NewWith(godsutils.StringComparator),
annotations: new(annotations),
kind: kind,
name: name,
pythonProjectRoot: pythonProjectRoot,
bzlPackage: bzlPackage,
srcs: treeset.NewWith(godsutils.StringComparator),
siblingSrcs: siblingSrcs,
deps: treeset.NewWith(moduleComparator),
resolvedDeps: treeset.NewWith(godsutils.StringComparator),
visibility: treeset.NewWith(godsutils.StringComparator),
annotations: new(annotations),
resolveSiblingImports: resolveSiblingImports,
}
}

Expand All @@ -77,7 +79,7 @@ func (t *targetBuilder) addModuleDependency(dep Module) *targetBuilder {
if dep.From != "" {
fileName = dep.From + ".py"
}
if t.siblingSrcs.Contains(fileName) && fileName != filepath.Base(dep.Filepath) {
if t.resolveSiblingImports && t.siblingSrcs.Contains(fileName) && fileName != filepath.Base(dep.Filepath) {
// importing another module from the same package, converting to absolute imports to make
// dependency resolution easier
dep.Name = importSpecFromSrc(t.pythonProjectRoot, t.bzlPackage, fileName).Imp
Expand Down Expand Up @@ -138,7 +140,6 @@ func (t *targetBuilder) setAnnotations(val annotations) *targetBuilder {
return t
}


// generateImportsAttribute generates the imports attribute.
// These are a list of import directories to be added to the PYTHONPATH. In our
// case, the value we add is on Bazel sub-packages to be able to perform imports
Expand Down
1 change: 1 addition & 0 deletions gazelle/python/testdata/annotation_include_dep/BUILD.in
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# gazelle:python_generation_mode file
# gazelle:python_resolve_sibling_imports true
1 change: 1 addition & 0 deletions gazelle/python/testdata/annotation_include_dep/BUILD.out
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

# gazelle:python_generation_mode file
# gazelle:python_resolve_sibling_imports true

py_library(
name = "__init__",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:python_resolve_sibling_imports true
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports true

py_binary(
name = "binary",
srcs = ["binary.py"],
Expand Down
1 change: 1 addition & 0 deletions gazelle/python/testdata/naming_convention/BUILD.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# gazelle:python_library_naming_convention my_$package_name$_library
# gazelle:python_binary_naming_convention my_$package_name$_binary
# gazelle:python_test_naming_convention my_$package_name$_test
# gazelle:python_resolve_sibling_imports true
1 change: 1 addition & 0 deletions gazelle/python/testdata/naming_convention/BUILD.out
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
# gazelle:python_library_naming_convention my_$package_name$_library
# gazelle:python_binary_naming_convention my_$package_name$_binary
# gazelle:python_test_naming_convention my_$package_name$_test
# gazelle:python_resolve_sibling_imports true

py_library(
name = "my_naming_convention_library",
Expand Down
12 changes: 11 additions & 1 deletion gazelle/python/testdata/sibling_imports/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Sibling imports

This test case asserts that imports from sibling modules are resolved correctly. It covers 3 different types of imports in `pkg/unit_test.py`
This test case asserts that imports from sibling modules are resolved correctly
when the `python_resolve_sibling_imports` directive is enabled (default
behavior). It covers 3 different types of imports in `pkg/unit_test.py`:

- `import a` - resolves to the sibling `a.py` in the same package
- `import test_util` - resolves to the sibling `test_util.py` in the same
package
- `from b import run` - resolves to the sibling `b.py` in the same package

When sibling imports are enabled, we allow them to be satisfied by sibling
modules (ie. modules in the same package).
1 change: 1 addition & 0 deletions gazelle/python/testdata/sibling_imports/pkg/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:python_resolve_sibling_imports true
3 changes: 2 additions & 1 deletion gazelle/python/testdata/sibling_imports/pkg/BUILD.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports true

py_library(
name = "pkg",
srcs = [
Expand All @@ -23,4 +25,3 @@ py_test(
":test_util",
],
)

2 changes: 2 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# gazelle:python_resolve_sibling_imports false
# gazelle:experimental_allow_relative_imports true
18 changes: 18 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports false
# gazelle:experimental_allow_relative_imports true

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

py_test(
name = "test_util",
srcs = ["test_util.py"],
)
22 changes: 22 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Sibling imports disabled

This test case asserts that imports from sibling modules are NOT resolved as
absolute imports when the `python_resolve_sibling_imports` directive is
disabled. It covers different types of imports in `pkg/unit_test.py`:

- `import a` - resolves to the root-level `a.py` instead of the sibling
`pkg/a.py`
- `from typing import Iterable` - resolves to the stdlib `typing` module
(not the sibling `typing.py`).
- `from .b import run` / `from .typing import A` - resolves to the sibling
`pkg/b.py` / `pkg/typing.py` (with
`gazelle:experimental_allow_relative_imports` enabled)
- `import test_util` - resolves to the root-level `test_util.py` instead of
the sibling `pkg/test_util.py`
- `from b import run` - resolves to the root-level `b.py` instead of the
sibling `pkg/b.py`

When sibling imports are disabled with
`# gazelle:python_resolve_sibling_imports false`, the imports remain as-is
and follow standard Python resolution rules where absolute imports can't refer
to sibling modules.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
1 change: 1 addition & 0 deletions gazelle/python/testdata/sibling_imports_disabled/a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Root level a.py file for testing disabled sibling imports
3 changes: 3 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Root level b.py file for testing disabled sibling imports
def run():
pass
Empty file.
27 changes: 27 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/pkg/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

py_library(
name = "pkg",
srcs = [
"__init__.py",
"a.py",
"b.py",
"typing.py",
],
visibility = ["//:__subpackages__"],
)

py_test(
name = "test_util",
srcs = ["test_util.py"],
deps = [":pkg"],
)

py_test(
name = "unit_test",
srcs = ["unit_test.py"],
deps = [
"//:sibling_imports_disabled",
"//:test_util",
],
)
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/pkg/b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def run():
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .b import run
from .typing import A
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A = 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from typing import Iterable

import a
import test_util
from b import run
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
---
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Root level test_util.py file for testing disabled sibling imports
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# gazelle:python_generation_mode file
# gazelle:python_resolve_sibling_imports false
# gazelle:experimental_allow_relative_imports true
Loading