Resolving sibling modules with absolute imports (#1029)

* Resolving sibling modules with absolute imports

* unconditionally importing conftest

* handle from statements

* adding tests

* adding readme for the new test case
diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go
index 4ebb40f..74e5f66 100644
--- a/gazelle/python/generate.go
+++ b/gazelle/python/generate.go
@@ -224,7 +224,6 @@
 		}
 
 		pyLibrary = newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyLibraryFilenames.Union(pyTestFilenames)).
-			setUUID(label.New("", args.Rel, pyLibraryTargetName).String()).
 			addVisibility(visibility).
 			addSrcs(pyLibraryFilenames).
 			addModuleDependencies(deps).
@@ -267,10 +266,6 @@
 			addModuleDependencies(deps).
 			generateImportsAttribute()
 
-		if pyLibrary != nil {
-			pyBinaryTarget.addModuleDependency(module{Name: pyLibrary.PrivateAttr(uuidKey).(string)})
-		}
-
 		pyBinary := pyBinaryTarget.build()
 
 		result.Gen = append(result.Gen, pyBinary)
@@ -301,7 +296,6 @@
 		}
 
 		conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyLibraryFilenames.Union(pyTestFilenames)).
-			setUUID(label.New("", args.Rel, conftestTargetname).String()).
 			addSrc(conftestFilename).
 			addModuleDependencies(deps).
 			addVisibility(visibility).
@@ -315,8 +309,8 @@
 	}
 
 	var pyTestTargets []*targetBuilder
-	newPyTestTargetBuilder := func(pyTestFilenames *treeset.Set, pyTestTargetName string) *targetBuilder {
-		deps, err := parser.parse(pyTestFilenames)
+	newPyTestTargetBuilder := func(srcs *treeset.Set, pyTestTargetName string) *targetBuilder {
+		deps, err := parser.parse(srcs)
 		if err != nil {
 			log.Fatalf("ERROR: %v\n", err)
 		}
@@ -337,7 +331,7 @@
 			}
 		}
 		return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyLibraryFilenames.Union(pyTestFilenames)).
-			addSrcs(pyTestFilenames).
+			addSrcs(srcs).
 			addModuleDependencies(deps).
 			generateImportsAttribute()
 	}
@@ -371,14 +365,9 @@
 	}
 
 	for _, pyTestTarget := range pyTestTargets {
-		if pyLibrary != nil {
-			pyTestTarget.addModuleDependency(module{Name: pyLibrary.PrivateAttr(uuidKey).(string)})
-		}
-
 		if conftest != nil {
-			pyTestTarget.addModuleDependency(module{Name: conftest.PrivateAttr(uuidKey).(string)})
+			pyTestTarget.addModuleDependency(module{Name: strings.TrimSuffix(conftestFilename, ".py")})
 		}
-
 		pyTest := pyTestTarget.build()
 
 		result.Gen = append(result.Gen, pyTest)
diff --git a/gazelle/python/resolve.go b/gazelle/python/resolve.go
index 607776a..46014e5 100644
--- a/gazelle/python/resolve.go
+++ b/gazelle/python/resolve.go
@@ -39,10 +39,6 @@
 	// resolvedDepsKey is the attribute key used to pass dependencies that don't
 	// need to be resolved by the dependency resolver in the Resolver step.
 	resolvedDepsKey = "_gazelle_python_resolved_deps"
-	// uuidKey is the attribute key used to uniquely identify a py_library
-	// target that should be imported by a py_test or py_binary in the same
-	// Bazel package.
-	uuidKey = "_gazelle_python_library_uuid"
 )
 
 // Resolver satisfies the resolve.Resolver interface. It resolves dependencies
@@ -71,13 +67,6 @@
 			provides = append(provides, provide)
 		}
 	}
-	if r.PrivateAttr(uuidKey) != nil {
-		provide := resolve.ImportSpec{
-			Lang: languageName,
-			Imp:  r.PrivateAttr(uuidKey).(string),
-		}
-		provides = append(provides, provide)
-	}
 	if len(provides) == 0 {
 		return nil
 	}
diff --git a/gazelle/python/target.go b/gazelle/python/target.go
index 69711ce..fdc99fc 100644
--- a/gazelle/python/target.go
+++ b/gazelle/python/target.go
@@ -15,12 +15,11 @@
 package python
 
 import (
-	"path/filepath"
-
 	"github.com/bazelbuild/bazel-gazelle/config"
 	"github.com/bazelbuild/bazel-gazelle/rule"
 	"github.com/emirpasic/gods/sets/treeset"
 	godsutils "github.com/emirpasic/gods/utils"
+	"path/filepath"
 )
 
 // targetBuilder builds targets to be generated by Gazelle.
@@ -29,7 +28,6 @@
 	name              string
 	pythonProjectRoot string
 	bzlPackage        string
-	uuid              string
 	srcs              *treeset.Set
 	siblingSrcs       *treeset.Set
 	deps              *treeset.Set
@@ -55,15 +53,6 @@
 	}
 }
 
-// setUUID sets the given UUID for the target. It's used to index the generated
-// target based on this value in addition to the other ways the targets can be
-// imported. py_{binary,test} targets in the same Bazel package can add a
-// virtual dependency to this UUID that gets resolved in the Resolver interface.
-func (t *targetBuilder) setUUID(uuid string) *targetBuilder {
-	t.uuid = uuid
-	return t
-}
-
 // addSrc adds a single src to the target.
 func (t *targetBuilder) addSrc(src string) *targetBuilder {
 	t.srcs.Add(src)
@@ -81,9 +70,16 @@
 
 // addModuleDependency adds a single module dep to the target.
 func (t *targetBuilder) addModuleDependency(dep module) *targetBuilder {
-	if dep.Name+".py" == filepath.Base(dep.Filepath) || !t.siblingSrcs.Contains(dep.Name+".py") {
-		t.deps.Add(dep)
+	fileName := dep.Name + ".py"
+	if dep.From != "" {
+		fileName = dep.From + ".py"
 	}
+	if 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
+	}
+	t.deps.Add(dep)
 	return t
 }
 
@@ -138,9 +134,6 @@
 // build returns the assembled *rule.Rule for the target.
 func (t *targetBuilder) build() *rule.Rule {
 	r := rule.NewRule(t.kind, t.name)
-	if t.uuid != "" {
-		r.SetPrivateAttr(uuidKey, t.uuid)
-	}
 	if !t.srcs.Empty() {
 		r.SetAttr("srcs", t.srcs.Values())
 	}
diff --git a/gazelle/python/testdata/generated_test_entrypoint/BUILD.out b/gazelle/python/testdata/generated_test_entrypoint/BUILD.out
index 48df068..e8e304c 100644
--- a/gazelle/python/testdata/generated_test_entrypoint/BUILD.out
+++ b/gazelle/python/testdata/generated_test_entrypoint/BUILD.out
@@ -17,8 +17,5 @@
     name = "generated_test_entrypoint_test",
     srcs = [":__test__"],
     main = ":__test__.py",
-    deps = [
-        ":__test__",
-        ":generated_test_entrypoint",
-    ],
+    deps = [":__test__"],
 )
diff --git a/gazelle/python/testdata/naming_convention/__main__.py b/gazelle/python/testdata/naming_convention/__main__.py
index 7307559..a3afc79 100644
--- a/gazelle/python/testdata/naming_convention/__main__.py
+++ b/gazelle/python/testdata/naming_convention/__main__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import __init__
\ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/__test__.py b/gazelle/python/testdata/naming_convention/__test__.py
index 7307559..a3afc79 100644
--- a/gazelle/python/testdata/naming_convention/__test__.py
+++ b/gazelle/python/testdata/naming_convention/__test__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import __init__
\ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/dont_rename/__main__.py b/gazelle/python/testdata/naming_convention/dont_rename/__main__.py
index 7307559..a3afc79 100644
--- a/gazelle/python/testdata/naming_convention/dont_rename/__main__.py
+++ b/gazelle/python/testdata/naming_convention/dont_rename/__main__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import __init__
\ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/dont_rename/__test__.py b/gazelle/python/testdata/naming_convention/dont_rename/__test__.py
index 7307559..a3afc79 100644
--- a/gazelle/python/testdata/naming_convention/dont_rename/__test__.py
+++ b/gazelle/python/testdata/naming_convention/dont_rename/__test__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import __init__
\ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py b/gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py
index 7307559..a3afc79 100644
--- a/gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py
+++ b/gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import __init__
\ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py b/gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py
index 7307559..a3afc79 100644
--- a/gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py
+++ b/gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import __init__
\ No newline at end of file
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/real_test.py b/gazelle/python/testdata/python_target_with_test_in_name/real_test.py
index 2f03211..e390866 100644
--- a/gazelle/python/testdata/python_target_with_test_in_name/real_test.py
+++ b/gazelle/python/testdata/python_target_with_test_in_name/real_test.py
@@ -13,5 +13,6 @@
 # limitations under the License.
 
 import boto3
+import __init__
 
 _ = boto3
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/test_reality.py b/gazelle/python/testdata/python_target_with_test_in_name/test_reality.py
index 7307559..a3afc79 100644
--- a/gazelle/python/testdata/python_target_with_test_in_name/test_reality.py
+++ b/gazelle/python/testdata/python_target_with_test_in_name/test_reality.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import __init__
\ No newline at end of file
diff --git a/gazelle/python/testdata/sibling_imports/README.md b/gazelle/python/testdata/sibling_imports/README.md
new file mode 100644
index 0000000..e59be07
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/README.md
@@ -0,0 +1,3 @@
+# 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`
\ No newline at end of file
diff --git a/gazelle/python/testdata/sibling_imports/WORKSPACE b/gazelle/python/testdata/sibling_imports/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/sibling_imports/pkg/BUILD.in b/gazelle/python/testdata/sibling_imports/pkg/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/BUILD.in
diff --git a/gazelle/python/testdata/sibling_imports/pkg/BUILD.out b/gazelle/python/testdata/sibling_imports/pkg/BUILD.out
new file mode 100644
index 0000000..edb40a8
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/BUILD.out
@@ -0,0 +1,29 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+    name = "pkg",
+    srcs = [
+        "__init__.py",
+        "a.py",
+        "b.py",
+    ],
+    imports = [".."],
+    visibility = ["//:__subpackages__"],
+)
+
+py_test(
+    name = "test_util",
+    srcs = ["test_util.py"],
+    imports = [".."],
+)
+
+py_test(
+    name = "unit_test",
+    srcs = ["unit_test.py"],
+    imports = [".."],
+    deps = [
+        ":pkg",
+        ":test_util",
+    ],
+)
+
diff --git a/gazelle/python/testdata/sibling_imports/pkg/__init__.py b/gazelle/python/testdata/sibling_imports/pkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/__init__.py
diff --git a/gazelle/python/testdata/sibling_imports/pkg/a.py b/gazelle/python/testdata/sibling_imports/pkg/a.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/a.py
diff --git a/gazelle/python/testdata/sibling_imports/pkg/b.py b/gazelle/python/testdata/sibling_imports/pkg/b.py
new file mode 100644
index 0000000..7095bdc
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/b.py
@@ -0,0 +1,2 @@
+def run():
+    pass
\ No newline at end of file
diff --git a/gazelle/python/testdata/sibling_imports/pkg/test_util.py b/gazelle/python/testdata/sibling_imports/pkg/test_util.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/test_util.py
diff --git a/gazelle/python/testdata/sibling_imports/pkg/unit_test.py b/gazelle/python/testdata/sibling_imports/pkg/unit_test.py
new file mode 100644
index 0000000..a3218e2
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/unit_test.py
@@ -0,0 +1,3 @@
+import a
+from b import run
+import test_util
\ No newline at end of file
diff --git a/gazelle/python/testdata/sibling_imports/test.yaml b/gazelle/python/testdata/sibling_imports/test.yaml
new file mode 100644
index 0000000..ed97d53
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/test.yaml
@@ -0,0 +1 @@
+---
diff --git a/gazelle/python/testdata/simple_binary_with_library/__main__.py b/gazelle/python/testdata/simple_binary_with_library/__main__.py
index 7307559..bc7ddf0 100644
--- a/gazelle/python/testdata/simple_binary_with_library/__main__.py
+++ b/gazelle/python/testdata/simple_binary_with_library/__main__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import foo
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py b/gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py
index 7307559..bd0fe61 100644
--- a/gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py
+++ b/gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import foo.has_main.python.my_module
\ No newline at end of file
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py b/gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py
index 7307559..3c9ed1a 100644
--- a/gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py
+++ b/gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py
@@ -13,3 +13,4 @@
 # limitations under the License.
 
 # For test purposes only.
+import foo.has_test.python.my_module
\ No newline at end of file
diff --git a/gazelle/python/testdata/with_third_party_requirements/BUILD.out b/gazelle/python/testdata/with_third_party_requirements/BUILD.out
index 2da7f2b..2a97d8b 100644
--- a/gazelle/python/testdata/with_third_party_requirements/BUILD.out
+++ b/gazelle/python/testdata/with_third_party_requirements/BUILD.out
@@ -20,5 +20,5 @@
     srcs = ["__main__.py"],
     main = "__main__.py",
     visibility = ["//:__subpackages__"],
-    deps = [":with_third_party_requirements"],
+    deps = ["@gazelle_python_test_baz//:pkg"],
 )