Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ END_UNRELEASED_TEMPLATE
### Added
* (runfiles) Added a pathlib-compatible API: {obj}`Runfiles.root()`
Fixes [#3296](https://github.com/bazel-contrib/rules_python/issues/3296).
* (gazelle) Support alias_kind directive.
Fixes [#3183](https://github.com/bazel-contrib/rules_python/issues/3183).
* (toolchains) `3.13.12`, `3.14.3` Python toolchain from [20260325] release.
* (toolchains) `3.10.20`, `3.11.15`, `3.12.13`, `3.13.13` `3.14.4`, `3.15.0a8`
* Python toolchain from [20260414] release.
Expand Down
49 changes: 26 additions & 23 deletions gazelle/python/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,21 @@ var (
buildFilenames = []string{"BUILD", "BUILD.bazel"}
)

func GetActualKindName(kind string, args language.GenerateArgs) string {
if kindOverride, ok := args.Config.KindMap[kind]; ok {
return kindOverride.KindName
// Returns the mapped kind, or kind if no mapping is configured with the map_kind directive.
func getMappedKind(c *config.Config, kind string) string {
if mapped, ok := c.KindMap[kind]; ok {
return mapped.KindName
}
return kind
}
Comment thread
dougthor42 marked this conversation as resolved.

// kindMatches returns whether r matches the canonical Python rule kind `expected`, respecting `# gazelle:map_kind` and
// `# gazelle:alias_kind` directives in the config.Config c.
func kindMatches(c *config.Config, r *rule.Rule, expected string) bool {
kind := r.Kind()
return kind == getMappedKind(c, expected) || c.AliasMap[kind] == expected
}

func matchesAnyGlob(s string, globs []string) bool {
// This function assumes that the globs have already been validated. If a glob is
// invalid, it's considered a non-match and we move on to the next pattern.
Expand Down Expand Up @@ -112,10 +120,6 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
}
}

actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
actualPyLibraryKind := GetActualKindName(pyLibraryKind, args)
actualPyTestKind := GetActualKindName(pyTestKind, args)

pythonProjectRoot := cfg.PythonProjectRoot()

packageName := filepath.Base(args.Dir)
Expand Down Expand Up @@ -283,10 +287,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
sort.Strings(mainFileNames)
for _, filename := range mainFileNames {
pyBinaryTargetName := strings.TrimSuffix(filepath.Base(filename), ".py")
if err := ensureNoCollision(args.File, pyBinaryTargetName, actualPyBinaryKind); err != nil {
if err := ensureNoCollision(args.Config, args.File, pyBinaryTargetName, pyBinaryKind); err != nil {
fqTarget := label.New("", args.Rel, pyBinaryTargetName)
log.Printf("failed to generate target %q of kind %q: %v",
fqTarget.String(), actualPyBinaryKind, err)
fqTarget.String(), getMappedKind(args.Config, pyBinaryKind), err)
continue
}

Expand Down Expand Up @@ -316,7 +320,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
}
generateEmptyLibrary := false
for _, r := range args.File.Rules {
if r.Kind() == actualPyLibraryKind && r.Name() == pyLibraryTargetName {
if r.Name() == pyLibraryTargetName && kindMatches(args.Config, r, pyLibraryKind) {
generateEmptyLibrary = true
}
}
Expand All @@ -332,11 +336,11 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
// exists, and if it is of a different kind from the one we are
// generating. If so, we have to throw an error since Gazelle won't
// generate it correctly.
if err := ensureNoCollision(args.File, pyLibraryTargetName, actualPyLibraryKind); err != nil {
if err := ensureNoCollision(args.Config, args.File, pyLibraryTargetName, pyLibraryKind); err != nil {
fqTarget := label.New("", args.Rel, pyLibraryTargetName)
err := fmt.Errorf("failed to generate target %q of kind %q: %w. "+
"Use the '# gazelle:%s' directive to change the naming convention.",
fqTarget.String(), actualPyLibraryKind, err, pythonconfig.LibraryNamingConvention)
fqTarget.String(), getMappedKind(args.Config, pyLibraryKind), err, pythonconfig.LibraryNamingConvention)
collisionErrors.Add(err)
}

Expand Down Expand Up @@ -386,11 +390,11 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
// exists, and if it is of a different kind from the one we are
// generating. If so, we have to throw an error since Gazelle won't
// generate it correctly.
if err := ensureNoCollision(args.File, pyBinaryTargetName, actualPyBinaryKind); err != nil {
if err := ensureNoCollision(args.Config, args.File, pyBinaryTargetName, pyBinaryKind); err != nil {
fqTarget := label.New("", args.Rel, pyBinaryTargetName)
err := fmt.Errorf("failed to generate target %q of kind %q: %w. "+
"Use the '# gazelle:%s' directive to change the naming convention.",
fqTarget.String(), actualPyBinaryKind, err, pythonconfig.BinaryNamingConvention)
fqTarget.String(), getMappedKind(args.Config, pyBinaryKind), err, pythonconfig.BinaryNamingConvention)
collisionErrors.Add(err)
}

Expand Down Expand Up @@ -425,10 +429,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
// exists, and if it is of a different kind from the one we are
// generating. If so, we have to throw an error since Gazelle won't
// generate it correctly.
if err := ensureNoCollision(args.File, conftestTargetname, actualPyLibraryKind); err != nil {
if err := ensureNoCollision(args.Config, args.File, conftestTargetname, pyLibraryKind); err != nil {
fqTarget := label.New("", args.Rel, conftestTargetname)
err := fmt.Errorf("failed to generate target %q of kind %q: %w. ",
fqTarget.String(), actualPyLibraryKind, err)
fqTarget.String(), getMappedKind(args.Config, pyLibraryKind), err)
collisionErrors.Add(err)
}

Expand Down Expand Up @@ -462,11 +466,11 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
// exists, and if it is of a different kind from the one we are
// generating. If so, we have to throw an error since Gazelle won't
// generate it correctly.
if err := ensureNoCollision(args.File, pyTestTargetName, actualPyTestKind); err != nil {
if err := ensureNoCollision(args.Config, args.File, pyTestTargetName, pyTestKind); err != nil {
fqTarget := label.New("", args.Rel, pyTestTargetName)
err := fmt.Errorf("failed to generate target %q of kind %q: %w. "+
"Use the '# gazelle:%s' directive to change the naming convention.",
fqTarget.String(), actualPyTestKind, err, pythonconfig.TestNamingConvention)
fqTarget.String(), getMappedKind(args.Config, pyTestKind), err, pythonconfig.TestNamingConvention)
collisionErrors.Add(err)
}

Expand Down Expand Up @@ -575,8 +579,7 @@ func (py *Python) getRulesWithInvalidSrcs(args language.GenerateArgs, validFiles
return strings.HasPrefix(src, "@") || strings.HasPrefix(src, "//") || strings.HasPrefix(src, ":")
}
for _, existingRule := range args.File.Rules {
actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
if existingRule.Kind() != actualPyBinaryKind {
if !kindMatches(args.Config, existingRule, pyBinaryKind) {
continue
}
var hasValidSrcs bool
Expand Down Expand Up @@ -652,12 +655,12 @@ func isEntrypointFile(path string) bool {
}
}

func ensureNoCollision(file *rule.File, targetName, kind string) error {
func ensureNoCollision(c *config.Config, file *rule.File, targetName, kind string) error {
if file == nil {
return nil
}
for _, t := range file.Rules {
if t.Name() == targetName && t.Kind() != kind {
if t.Name() == targetName && !kindMatches(c, t, kind) {
return fmt.Errorf("a target of kind %q with the same name already exists", t.Kind())
}
}
Expand All @@ -680,7 +683,7 @@ func generateProtoLibraries(args language.GenerateArgs, cfg *pythonconfig.Config
pyProtoRulesForProto := map[string]string{}
if args.File != nil {
for _, r := range args.File.Rules {
if r.Kind() == "py_proto_library" {
if kindMatches(args.Config, r, pyProtoLibraryKind) {
pyProtoRules[r.Name()] = false

protos := r.AttrStrings("deps")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# gazelle:map_kind py_library my_lib :mytest.bzl
# gazelle:map_kind py_binary my_bin :mytest.bzl
# gazelle:map_kind py_test my_test :mytest.bzl

py_library(name = "naming_convention_mapped_fail")

py_binary(name = "naming_convention_mapped_fail_bin")

py_test(name = "naming_convention_mapped_fail_test")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# gazelle:map_kind py_library my_lib :mytest.bzl
# gazelle:map_kind py_binary my_bin :mytest.bzl
# gazelle:map_kind py_test my_test :mytest.bzl

py_library(name = "naming_convention_mapped_fail")

py_binary(name = "naming_convention_mapped_fail_bin")

py_test(name = "naming_convention_mapped_fail_test")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Naming convention fail with `map_kind`

This test case asserts that collision error messages reference the mapped rule
kind over than the canonical Python kind when using `# gazelle:map_kind`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty test file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty test file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty test file.
21 changes: 21 additions & 0 deletions gazelle/python/testdata/naming_convention_mapped_fail/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
Comment thread
canislupaster marked this conversation as resolved.
Outdated
#
# 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: 1
stderr: |
gazelle: ERROR: failed to generate target "//:naming_convention_mapped_fail" of kind "my_lib": a target of kind "py_library" with the same name already exists. Use the '# gazelle:python_library_naming_convention' directive to change the naming convention.
gazelle: ERROR: failed to generate target "//:naming_convention_mapped_fail_bin" of kind "my_bin": a target of kind "py_binary" with the same name already exists. Use the '# gazelle:python_binary_naming_convention' directive to change the naming convention.
gazelle: ERROR: failed to generate target "//:naming_convention_mapped_fail_test" of kind "my_test": a target of kind "py_test" with the same name already exists. Use the '# gazelle:python_test_naming_convention' directive to change the naming convention.
16 changes: 16 additions & 0 deletions gazelle/python/testdata/respect_alias_and_map_kind/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load(":mylib.bzl", "my_py_library")

# gazelle:alias_kind my_py_library py_library
# gazelle:map_kind py_test my_test :mytest.bzl

my_py_library(
name = "respect_alias_and_map_kind",
srcs = ["__init__.py"],
)

my_test(
name = "respect_alias_and_map_kind_test",
srcs = ["__test__.py"],
main = "__test__.py",
deps = [],
)
21 changes: 21 additions & 0 deletions gazelle/python/testdata/respect_alias_and_map_kind/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load(":mylib.bzl", "my_py_library")
load(":mytest.bzl", "my_test")

# gazelle:alias_kind my_py_library py_library
# gazelle:map_kind py_test my_test :mytest.bzl

my_py_library(
name = "respect_alias_and_map_kind",
srcs = [
"__init__.py",
"foo.py",
],
visibility = ["//:__subpackages__"],
)

my_test(
name = "respect_alias_and_map_kind_test",
srcs = ["__test__.py"],
main = "__test__.py",
deps = [":respect_alias_and_map_kind"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Respect `alias_kind` and `map_kind`

This test case asserts that `# gazelle:alias_kind` and `# gazelle:map_kind` are
both respected as the expected kind at once and do not collide.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
17 changes: 17 additions & 0 deletions gazelle/python/testdata/respect_alias_and_map_kind/__init__.py
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions gazelle/python/testdata/respect_alias_and_map_kind/__test__.py
Original file line number Diff line number Diff line change
@@ -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()
17 changes: 17 additions & 0 deletions gazelle/python/testdata/respect_alias_and_map_kind/foo.py
Original file line number Diff line number Diff line change
@@ -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 foo():
return "foo"
17 changes: 17 additions & 0 deletions gazelle/python/testdata/respect_alias_and_map_kind/test.yaml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions gazelle/python/testdata/respect_alias_kind/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load(":mylib.bzl", "my_py_library")

# gazelle:alias_kind my_py_library py_library

my_py_library(
name = "respect_alias_kind",
srcs = ["__init__.py"],
)
12 changes: 12 additions & 0 deletions gazelle/python/testdata/respect_alias_kind/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
load(":mylib.bzl", "my_py_library")

# gazelle:alias_kind my_py_library py_library

my_py_library(
name = "respect_alias_kind",
srcs = [
"__init__.py",
"foo.py",
],
visibility = ["//:__subpackages__"],
)
4 changes: 4 additions & 0 deletions gazelle/python/testdata/respect_alias_kind/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Respect `alias_kind`

This test case asserts that Gazelle updates the existing wrapper-macro rule
declared with `# gazelle:alias_kind` in place instead of reporting a collision.
1 change: 1 addition & 0 deletions gazelle/python/testdata/respect_alias_kind/WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
17 changes: 17 additions & 0 deletions gazelle/python/testdata/respect_alias_kind/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Loading