fix: respect kind mapping (#1158)
When using the kind `gazelle:map_kind` directive, `gazelle` will
correctly generate the buildfile on the first pass (or if no target of
that type / name are present).
However, when running gazelle a second time (or if a target of the
mapped kind with the same name is present), `gazelle` will error out
saying that it kind create a target of the original kind because a
target of mapped kind is present and has the same name.
Ex:
Given the directive `# gazelle:map_kind py_test py_pytest_test
//src/bazel/rules/python:py_pytest_test.bzl`, `gazelle` will correctly
generate a `py_pytest_test` target where it would have generated a
`py_test` target.
But on a second invocation of `gazelle` (and subsequent invocations) it
will error our with:
```
gazelle: ERROR: failed to generate target "//test/python/common:common_test" of kind "py_test": a target of kind "py_pytest_test" with the same name already exists. Use the '# gazelle:python_test_naming_convention' directive to change the naming convention.
```
diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go
index 26ffeda..fb41324 100644
--- a/gazelle/python/generate.go
+++ b/gazelle/python/generate.go
@@ -46,6 +46,13 @@
buildFilenames = []string{"BUILD", "BUILD.bazel"}
)
+func GetActualKindName(kind string, args language.GenerateArgs) string {
+ if kindOverride, ok := args.Config.KindMap[kind]; ok {
+ return kindOverride.KindName
+ }
+ return kind
+}
+
// GenerateRules extracts build metadata from source files in a directory.
// GenerateRules is called in each directory where an update is requested
// in depth-first post-order.
@@ -70,6 +77,10 @@
}
}
+ actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
+ actualPyLibraryKind := GetActualKindName(pyLibraryKind, args)
+ actualPyTestKind := GetActualKindName(pyTestKind, args)
+
pythonProjectRoot := cfg.PythonProjectRoot()
packageName := filepath.Base(args.Dir)
@@ -217,12 +228,12 @@
// generate it correctly.
if args.File != nil {
for _, t := range args.File.Rules {
- if t.Name() == pyLibraryTargetName && t.Kind() != pyLibraryKind {
+ if t.Name() == pyLibraryTargetName && t.Kind() != actualPyLibraryKind {
fqTarget := label.New("", args.Rel, pyLibraryTargetName)
err := fmt.Errorf("failed to generate target %q of kind %q: "+
"a target of kind %q with the same name already exists. "+
"Use the '# gazelle:%s' directive to change the naming convention.",
- fqTarget.String(), pyLibraryKind, t.Kind(), pythonconfig.LibraryNamingConvention)
+ fqTarget.String(), actualPyLibraryKind, t.Kind(), pythonconfig.LibraryNamingConvention)
collisionErrors.Add(err)
}
}
@@ -253,12 +264,12 @@
// generate it correctly.
if args.File != nil {
for _, t := range args.File.Rules {
- if t.Name() == pyBinaryTargetName && t.Kind() != pyBinaryKind {
+ if t.Name() == pyBinaryTargetName && t.Kind() != actualPyBinaryKind {
fqTarget := label.New("", args.Rel, pyBinaryTargetName)
err := fmt.Errorf("failed to generate target %q of kind %q: "+
"a target of kind %q with the same name already exists. "+
"Use the '# gazelle:%s' directive to change the naming convention.",
- fqTarget.String(), pyBinaryKind, t.Kind(), pythonconfig.BinaryNamingConvention)
+ fqTarget.String(), actualPyBinaryKind, t.Kind(), pythonconfig.BinaryNamingConvention)
collisionErrors.Add(err)
}
}
@@ -290,11 +301,11 @@
// generate it correctly.
if args.File != nil {
for _, t := range args.File.Rules {
- if t.Name() == conftestTargetname && t.Kind() != pyLibraryKind {
+ if t.Name() == conftestTargetname && t.Kind() != actualPyLibraryKind {
fqTarget := label.New("", args.Rel, conftestTargetname)
err := fmt.Errorf("failed to generate target %q of kind %q: "+
"a target of kind %q with the same name already exists.",
- fqTarget.String(), pyLibraryKind, t.Kind())
+ fqTarget.String(), actualPyLibraryKind, t.Kind())
collisionErrors.Add(err)
}
}
@@ -325,12 +336,12 @@
// generate it correctly.
if args.File != nil {
for _, t := range args.File.Rules {
- if t.Name() == pyTestTargetName && t.Kind() != pyTestKind {
+ if t.Name() == pyTestTargetName && t.Kind() != actualPyTestKind {
fqTarget := label.New("", args.Rel, pyTestTargetName)
err := fmt.Errorf("failed to generate target %q of kind %q: "+
"a target of kind %q with the same name already exists. "+
"Use the '# gazelle:%s' directive to change the naming convention.",
- fqTarget.String(), pyTestKind, t.Kind(), pythonconfig.TestNamingConvention)
+ fqTarget.String(), actualPyTestKind, t.Kind(), pythonconfig.TestNamingConvention)
collisionErrors.Add(err)
}
}
diff --git a/gazelle/python/testdata/respect_kind_mapping/BUILD.in b/gazelle/python/testdata/respect_kind_mapping/BUILD.in
new file mode 100644
index 0000000..6a06737
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/BUILD.in
@@ -0,0 +1,15 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:map_kind py_test my_test :mytest.bzl
+
+py_library(
+ name = "respect_kind_mapping",
+ srcs = ["__init__.py"],
+)
+
+my_test(
+ name = "respect_kind_mapping_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":respect_kind_mapping"],
+)
diff --git a/gazelle/python/testdata/respect_kind_mapping/BUILD.out b/gazelle/python/testdata/respect_kind_mapping/BUILD.out
new file mode 100644
index 0000000..7c5fb0b
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/BUILD.out
@@ -0,0 +1,20 @@
+load(":mytest.bzl", "my_test")
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:map_kind py_test my_test :mytest.bzl
+
+py_library(
+ name = "respect_kind_mapping",
+ srcs = [
+ "__init__.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+my_test(
+ name = "respect_kind_mapping_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":respect_kind_mapping"],
+)
diff --git a/gazelle/python/testdata/respect_kind_mapping/README.md b/gazelle/python/testdata/respect_kind_mapping/README.md
new file mode 100644
index 0000000..9f0fa6c
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/README.md
@@ -0,0 +1,3 @@
+# Respect Kind Mapping
+
+This test case asserts that when using a kind mapping, gazelle will respect that mapping when parsing a BUILD file containing a mapped kind.
diff --git a/gazelle/python/testdata/respect_kind_mapping/WORKSPACE b/gazelle/python/testdata/respect_kind_mapping/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/respect_kind_mapping/__init__.py b/gazelle/python/testdata/respect_kind_mapping/__init__.py
new file mode 100644
index 0000000..b274b0d
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/__init__.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.
+
+from foo import foo
+
+_ = foo
diff --git a/gazelle/python/testdata/respect_kind_mapping/__test__.py b/gazelle/python/testdata/respect_kind_mapping/__test__.py
new file mode 100644
index 0000000..2b180a5
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/__test__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import unittest
+
+from __init__ import foo
+
+
+class FooTest(unittest.TestCase):
+ def test_foo(self):
+ self.assertEqual("foo", foo())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gazelle/python/testdata/respect_kind_mapping/foo.py b/gazelle/python/testdata/respect_kind_mapping/foo.py
new file mode 100644
index 0000000..932de45
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/foo.py
@@ -0,0 +1,16 @@
+# 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 foo():
+ return "foo"
diff --git a/gazelle/python/testdata/respect_kind_mapping/test.yaml b/gazelle/python/testdata/respect_kind_mapping/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/test.yaml
@@ -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.
+
+---
+expect:
+ exit_code: 0