Skip to content

Commit b87cf67

Browse files
feat: Add alias_kind directive support to Gazelle plugin. (bazel-contrib#3713)
Currently the collision detection prevents Gazelle from respecting `alias_kind`. This PR cleans up the behavior by referencing the configured alias and map kind instead of storing a bunch of mapped `actualPy*Kind` vars. I've added two tests heavily based on `respect_map_kind`: one with just `alias_kind`, and one using both `map_kind` and `alias_kind`. Fixes bazel-contrib#3183 . --------- Co-authored-by: Ignas Anikevicius <240938+aignas@users.noreply.github.com>
1 parent a6e7d34 commit b87cf67

25 files changed

Lines changed: 171 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ END_UNRELEASED_TEMPLATE
8585
wheel `data`, `bin`, and `include` files are populated into the venv.
8686
* (runfiles) Added a pathlib-compatible API: {obj}`Runfiles.root()`
8787
Fixes [#3296](https://github.com/bazel-contrib/rules_python/issues/3296).
88+
* (gazelle) Support alias_kind directive.
89+
Fixes [#3183](https://github.com/bazel-contrib/rules_python/issues/3183).
8890
* (toolchains) `3.13.12`, `3.14.3` Python toolchain from [20260325] release.
8991
* (toolchains) `3.10.20`, `3.11.15`, `3.12.13`, `3.13.13` `3.14.4`, `3.15.0a8`
9092
* Python toolchain from [20260414] release.

gazelle/python/generate.go

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@ var (
4848
buildFilenames = []string{"BUILD", "BUILD.bazel"}
4949
)
5050

51-
func GetActualKindName(kind string, args language.GenerateArgs) string {
52-
if kindOverride, ok := args.Config.KindMap[kind]; ok {
53-
return kindOverride.KindName
51+
// Returns the mapped kind, or kind if no mapping is configured with the map_kind directive.
52+
func getMappedKind(c *config.Config, kind string) string {
53+
if mapped, ok := c.KindMap[kind]; ok {
54+
return mapped.KindName
5455
}
5556
return kind
5657
}
5758

59+
// kindMatches returns whether r matches the canonical Python rule kind `expected`, respecting `# gazelle:map_kind` and
60+
// `# gazelle:alias_kind` directives in the config.Config c.
61+
func kindMatches(c *config.Config, r *rule.Rule, expected string) bool {
62+
kind := r.Kind()
63+
return kind == getMappedKind(c, expected) || c.AliasMap[kind] == expected
64+
}
65+
5866
func matchesAnyGlob(s string, globs []string) bool {
5967
// This function assumes that the globs have already been validated. If a glob is
6068
// invalid, it's considered a non-match and we move on to the next pattern.
@@ -112,10 +120,6 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
112120
}
113121
}
114122

115-
actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
116-
actualPyLibraryKind := GetActualKindName(pyLibraryKind, args)
117-
actualPyTestKind := GetActualKindName(pyTestKind, args)
118-
119123
pythonProjectRoot := cfg.PythonProjectRoot()
120124

121125
packageName := filepath.Base(args.Dir)
@@ -296,10 +300,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
296300
sort.Strings(mainFileNames)
297301
for _, filename := range mainFileNames {
298302
pyBinaryTargetName := strings.TrimSuffix(filepath.Base(filename), ".py")
299-
if err := ensureNoCollision(args.File, pyBinaryTargetName, actualPyBinaryKind); err != nil {
303+
if err := ensureNoCollision(args.Config, args.File, pyBinaryTargetName, pyBinaryKind); err != nil {
300304
fqTarget := label.New("", args.Rel, pyBinaryTargetName)
301305
log.Printf("failed to generate target %q of kind %q: %v",
302-
fqTarget.String(), actualPyBinaryKind, err)
306+
fqTarget.String(), getMappedKind(args.Config, pyBinaryKind), err)
303307
continue
304308
}
305309

@@ -334,7 +338,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
334338
}
335339
generateEmptyLibrary := false
336340
for _, r := range args.File.Rules {
337-
if r.Kind() == actualPyLibraryKind && r.Name() == pyLibraryTargetName {
341+
if r.Name() == pyLibraryTargetName && kindMatches(args.Config, r, pyLibraryKind) {
338342
generateEmptyLibrary = true
339343
}
340344
}
@@ -350,11 +354,11 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
350354
// exists, and if it is of a different kind from the one we are
351355
// generating. If so, we have to throw an error since Gazelle won't
352356
// generate it correctly.
353-
if err := ensureNoCollision(args.File, pyLibraryTargetName, actualPyLibraryKind); err != nil {
357+
if err := ensureNoCollision(args.Config, args.File, pyLibraryTargetName, pyLibraryKind); err != nil {
354358
fqTarget := label.New("", args.Rel, pyLibraryTargetName)
355359
err := fmt.Errorf("failed to generate target %q of kind %q: %w. "+
356360
"Use the '# gazelle:%s' directive to change the naming convention.",
357-
fqTarget.String(), actualPyLibraryKind, err, pythonconfig.LibraryNamingConvention)
361+
fqTarget.String(), getMappedKind(args.Config, pyLibraryKind), err, pythonconfig.LibraryNamingConvention)
358362
collisionErrors.Add(err)
359363
}
360364

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

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

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

@@ -593,8 +597,7 @@ func (py *Python) getRulesWithInvalidSrcs(args language.GenerateArgs, validFiles
593597
return strings.HasPrefix(src, "@") || strings.HasPrefix(src, "//") || strings.HasPrefix(src, ":")
594598
}
595599
for _, existingRule := range args.File.Rules {
596-
actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
597-
if existingRule.Kind() != actualPyBinaryKind {
600+
if !kindMatches(args.Config, existingRule, pyBinaryKind) {
598601
continue
599602
}
600603
var hasValidSrcs bool
@@ -670,12 +673,12 @@ func isEntrypointFile(path string) bool {
670673
}
671674
}
672675

673-
func ensureNoCollision(file *rule.File, targetName, kind string) error {
676+
func ensureNoCollision(c *config.Config, file *rule.File, targetName, kind string) error {
674677
if file == nil {
675678
return nil
676679
}
677680
for _, t := range file.Rules {
678-
if t.Name() == targetName && t.Kind() != kind {
681+
if t.Name() == targetName && !kindMatches(c, t, kind) {
679682
return fmt.Errorf("a target of kind %q with the same name already exists", t.Kind())
680683
}
681684
}
@@ -698,7 +701,7 @@ func generateProtoLibraries(args language.GenerateArgs, cfg *pythonconfig.Config
698701
pyProtoRulesForProto := map[string]string{}
699702
if args.File != nil {
700703
for _, r := range args.File.Rules {
701-
if r.Kind() == "py_proto_library" {
704+
if kindMatches(args.Config, r, pyProtoLibraryKind) {
702705
pyProtoRules[r.Name()] = false
703706

704707
protos := r.AttrStrings("deps")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# gazelle:map_kind py_library my_lib :mytest.bzl
2+
# gazelle:map_kind py_binary my_bin :mytest.bzl
3+
# gazelle:map_kind py_test my_test :mytest.bzl
4+
5+
py_library(name = "naming_convention_mapped_fail")
6+
7+
py_binary(name = "naming_convention_mapped_fail_bin")
8+
9+
py_test(name = "naming_convention_mapped_fail_test")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# gazelle:map_kind py_library my_lib :mytest.bzl
2+
# gazelle:map_kind py_binary my_bin :mytest.bzl
3+
# gazelle:map_kind py_test my_test :mytest.bzl
4+
5+
py_library(name = "naming_convention_mapped_fail")
6+
7+
py_binary(name = "naming_convention_mapped_fail_bin")
8+
9+
py_test(name = "naming_convention_mapped_fail_test")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Naming convention fail with `map_kind`
2+
3+
This test case asserts that collision error messages reference the mapped rule
4+
kind over than the canonical Python kind when using `# gazelle:map_kind`.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This is a Bazel workspace for the Gazelle test data.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty test file.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty test file.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty test file.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
expect:
3+
exit_code: 1
4+
stderr: |
5+
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.
6+
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.
7+
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.

0 commit comments

Comments
 (0)