feat(gazelle): Add directives for label format & normalisation (#1976)
Adds new directives to alter default Gazelle label format to third-party
dependencies useful for re-using Gazelle plugin with other rules,
including `rules_pycross`.
Fixes #1939
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93707a1..61df086 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -68,6 +68,11 @@
* (toolchains) {obj}`//python/runtime_env_toolchains:all`, which is a drop-in
replacement for the "autodetecting" toolchain.
+### Added
+* (gazelle) Added new `python_label_convention` and `python_label_normalization` directives. These directive
+ allows altering default Gazelle label format to third-party dependencies useful for re-using Gazelle plugin
+ with other rules, including `rules_pycross`. See [#1939](https://github.com/bazelbuild/rules_python/issues/1939).
+
### Removed
* (pip): Removes the `entrypoint` macro that was replaced by `py_console_script_binary` in 0.26.0.
diff --git a/gazelle/README.md b/gazelle/README.md
index bb688b9..d68b94d 100644
--- a/gazelle/README.md
+++ b/gazelle/README.md
@@ -204,7 +204,10 @@
| Appends additional visibility labels to each generated target. This directive can be set multiple times. | |
| [`# gazelle:python_test_file_pattern`](#directive-python_test_file_pattern) | `*_test.py,test_*.py` |
| Filenames matching these comma-separated `glob`s will be mapped to `py_test` targets. |
-
+| `# gazelle:python_label_convention` | `$distribution_name$` |
+| Defines the format of the distribution name in labels to third-party deps. Useful for using Gazelle plugin with other rules with different repository conventions (e.g. `rules_pycross`). Full label is always prepended with (pip) repository name, e.g. `@pip//numpy`. |
+| `# gazelle:python_label_normalization` | `snake_case` |
+| Controls how distribution names in labels to third-party deps are normalized. Useful for using Gazelle plugin with other rules with different label conventions (e.g. `rules_pycross` uses PEP-503). Can be "snake_case", "none", or "pep503". |
#### Directive: `python_root`:
diff --git a/gazelle/python/configure.go b/gazelle/python/configure.go
index c35a261..b82dd81 100644
--- a/gazelle/python/configure.go
+++ b/gazelle/python/configure.go
@@ -67,6 +67,8 @@
pythonconfig.DefaultVisibilty,
pythonconfig.Visibility,
pythonconfig.TestFilePattern,
+ pythonconfig.LabelConvention,
+ pythonconfig.LabelNormalization,
}
}
@@ -196,6 +198,23 @@
}
}
config.SetTestFilePattern(globStrings)
+ case pythonconfig.LabelConvention:
+ value := strings.TrimSpace(d.Value)
+ if value == "" {
+ log.Fatalf("directive '%s' requires a value", pythonconfig.LabelConvention)
+ }
+ config.SetLabelConvention(value)
+ case pythonconfig.LabelNormalization:
+ switch directiveArg := strings.ToLower(strings.TrimSpace(d.Value)); directiveArg {
+ case "pep503":
+ config.SetLabelNormalization(pythonconfig.Pep503LabelNormalizationType)
+ case "none":
+ config.SetLabelNormalization(pythonconfig.NoLabelNormalizationType)
+ case "snake_case":
+ config.SetLabelNormalization(pythonconfig.SnakeCaseLabelNormalizationType)
+ default:
+ config.SetLabelNormalization(pythonconfig.DefaultLabelNormalizationType)
+ }
}
}
diff --git a/gazelle/python/testdata/annotation_include_dep/__init__.py b/gazelle/python/testdata/annotation_include_dep/__init__.py
index 6101534..a90a1b9 100644
--- a/gazelle/python/testdata/annotation_include_dep/__init__.py
+++ b/gazelle/python/testdata/annotation_include_dep/__init__.py
@@ -1,5 +1,5 @@
-import module1
import foo # third party package
+import module1
# gazelle:include_dep //foo/bar:baz
# gazelle:include_dep //hello:world,@star_wars//rebel_alliance/luke:skywalker
diff --git a/gazelle/python/testdata/directive_python_label_convention/README.md b/gazelle/python/testdata/directive_python_label_convention/README.md
new file mode 100644
index 0000000..8ce0155
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/README.md
@@ -0,0 +1,4 @@
+# Directive: `python_label_convention`
+
+This test case asserts that the `# gazelle:python_label_convention` directive
+works as intended when set.
\ No newline at end of file
diff --git a/gazelle/python/testdata/directive_python_label_convention/WORKSPACE b/gazelle/python/testdata/directive_python_label_convention/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/WORKSPACE
diff --git a/gazelle/python/testdata/directive_python_label_convention/test.yaml b/gazelle/python/testdata/directive_python_label_convention/test.yaml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test.yaml
diff --git a/gazelle/python/testdata/directive_python_label_convention/test1_unset/BUILD.in b/gazelle/python/testdata/directive_python_label_convention/test1_unset/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test1_unset/BUILD.in
diff --git a/gazelle/python/testdata/directive_python_label_convention/test1_unset/BUILD.out b/gazelle/python/testdata/directive_python_label_convention/test1_unset/BUILD.out
new file mode 100644
index 0000000..697a202
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test1_unset/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "test1_unset",
+ srcs = ["bar.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "@gazelle_python_test//google_cloud_aiplatform",
+ "@gazelle_python_test//google_cloud_storage",
+ ],
+)
diff --git a/gazelle/python/testdata/directive_python_label_convention/test1_unset/bar.py b/gazelle/python/testdata/directive_python_label_convention/test1_unset/bar.py
new file mode 100644
index 0000000..99a4b1c
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test1_unset/bar.py
@@ -0,0 +1,6 @@
+from google.cloud import aiplatform, storage
+
+
+def main():
+ a = dir(aiplatform)
+ b = dir(storage)
diff --git a/gazelle/python/testdata/directive_python_label_convention/test1_unset/gazelle_python.yaml b/gazelle/python/testdata/directive_python_label_convention/test1_unset/gazelle_python.yaml
new file mode 100644
index 0000000..bd5efab
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test1_unset/gazelle_python.yaml
@@ -0,0 +1,6 @@
+manifest:
+ modules_mapping:
+ google.cloud.aiplatform: google_cloud_aiplatform
+ google.cloud.storage: google_cloud_storage
+ pip_repository:
+ name: gazelle_python_test
diff --git a/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/BUILD.in b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/BUILD.in
new file mode 100644
index 0000000..83ce6af
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_label_convention :$distribution_name$
\ No newline at end of file
diff --git a/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/BUILD.out b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/BUILD.out
new file mode 100644
index 0000000..061c8e5
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/BUILD.out
@@ -0,0 +1,13 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_label_convention :$distribution_name$
+
+py_library(
+ name = "test2_custom_prefix_colon",
+ srcs = ["bar.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "@gazelle_python_test//:google_cloud_aiplatform",
+ "@gazelle_python_test//:google_cloud_storage",
+ ],
+)
diff --git a/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/bar.py b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/bar.py
new file mode 100644
index 0000000..99a4b1c
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/bar.py
@@ -0,0 +1,6 @@
+from google.cloud import aiplatform, storage
+
+
+def main():
+ a = dir(aiplatform)
+ b = dir(storage)
diff --git a/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/gazelle_python.yaml b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/gazelle_python.yaml
new file mode 100644
index 0000000..bd5efab
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_convention/test2_custom_prefix_colon/gazelle_python.yaml
@@ -0,0 +1,6 @@
+manifest:
+ modules_mapping:
+ google.cloud.aiplatform: google_cloud_aiplatform
+ google.cloud.storage: google_cloud_storage
+ pip_repository:
+ name: gazelle_python_test
diff --git a/gazelle/python/testdata/directive_python_label_normalization/README.md b/gazelle/python/testdata/directive_python_label_normalization/README.md
new file mode 100644
index 0000000..a2e1801
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/README.md
@@ -0,0 +1,4 @@
+# Directive: `python_label_normalization`
+
+This test case asserts that the `# gazelle:python_label_normalization` directive
+works as intended when set.
\ No newline at end of file
diff --git a/gazelle/python/testdata/directive_python_label_normalization/WORKSPACE b/gazelle/python/testdata/directive_python_label_normalization/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/WORKSPACE
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test.yaml b/gazelle/python/testdata/directive_python_label_normalization/test.yaml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test.yaml
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/BUILD.in b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/BUILD.in
new file mode 100644
index 0000000..5f5620a
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_label_normalization none
\ No newline at end of file
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/BUILD.out b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/BUILD.out
new file mode 100644
index 0000000..6e70778
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/BUILD.out
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_label_normalization none
+
+py_library(
+ name = "test1_type_none",
+ srcs = ["bar.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test//google.cloud.storage"],
+)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/bar.py b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/bar.py
new file mode 100644
index 0000000..8b3839e
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/bar.py
@@ -0,0 +1,5 @@
+from google.cloud import storage
+
+
+def main():
+ b = dir(storage)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/gazelle_python.yaml b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/gazelle_python.yaml
new file mode 100644
index 0000000..5bfada4
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test1_type_none/gazelle_python.yaml
@@ -0,0 +1,6 @@
+manifest:
+ modules_mapping:
+ # Weird google.cloud.storage here on purpose to make normalization apparent
+ google.cloud.storage: google.cloud.storage
+ pip_repository:
+ name: gazelle_python_test
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/BUILD.in b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/BUILD.in
new file mode 100644
index 0000000..a2cca53
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_label_normalization pep503
\ No newline at end of file
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/BUILD.out b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/BUILD.out
new file mode 100644
index 0000000..7a88c8b
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/BUILD.out
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_label_normalization pep503
+
+py_library(
+ name = "test2_type_pep503",
+ srcs = ["bar.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test//google-cloud-storage"],
+)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/bar.py b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/bar.py
new file mode 100644
index 0000000..8b3839e
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/bar.py
@@ -0,0 +1,5 @@
+from google.cloud import storage
+
+
+def main():
+ b = dir(storage)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/gazelle_python.yaml b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/gazelle_python.yaml
new file mode 100644
index 0000000..5bfada4
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test2_type_pep503/gazelle_python.yaml
@@ -0,0 +1,6 @@
+manifest:
+ modules_mapping:
+ # Weird google.cloud.storage here on purpose to make normalization apparent
+ google.cloud.storage: google.cloud.storage
+ pip_repository:
+ name: gazelle_python_test
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/BUILD.in b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/BUILD.in
new file mode 100644
index 0000000..5d1a19a
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_label_normalization snake_case
\ No newline at end of file
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/BUILD.out b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/BUILD.out
new file mode 100644
index 0000000..77f180c
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/BUILD.out
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_label_normalization snake_case
+
+py_library(
+ name = "test3_type_snake_case",
+ srcs = ["bar.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test//google_cloud_storage"],
+)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/bar.py b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/bar.py
new file mode 100644
index 0000000..8b3839e
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/bar.py
@@ -0,0 +1,5 @@
+from google.cloud import storage
+
+
+def main():
+ b = dir(storage)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/gazelle_python.yaml b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/gazelle_python.yaml
new file mode 100644
index 0000000..5bfada4
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test3_type_snake_case/gazelle_python.yaml
@@ -0,0 +1,6 @@
+manifest:
+ modules_mapping:
+ # Weird google.cloud.storage here on purpose to make normalization apparent
+ google.cloud.storage: google.cloud.storage
+ pip_repository:
+ name: gazelle_python_test
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/BUILD.in b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/BUILD.in
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/BUILD.out b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/BUILD.out
new file mode 100644
index 0000000..2297193
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "test4_unset_defaults_to_snake_case",
+ srcs = ["bar.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test//google_cloud_storage"],
+)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/bar.py b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/bar.py
new file mode 100644
index 0000000..8b3839e
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/bar.py
@@ -0,0 +1,5 @@
+from google.cloud import storage
+
+
+def main():
+ b = dir(storage)
diff --git a/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/gazelle_python.yaml b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/gazelle_python.yaml
new file mode 100644
index 0000000..5bfada4
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_label_normalization/test4_unset_defaults_to_snake_case/gazelle_python.yaml
@@ -0,0 +1,6 @@
+manifest:
+ modules_mapping:
+ # Weird google.cloud.storage here on purpose to make normalization apparent
+ google.cloud.storage: google.cloud.storage
+ pip_repository:
+ name: gazelle_python_test
diff --git a/gazelle/pythonconfig/pythonconfig.go b/gazelle/pythonconfig/pythonconfig.go
index aa92552..41a470a 100644
--- a/gazelle/pythonconfig/pythonconfig.go
+++ b/gazelle/pythonconfig/pythonconfig.go
@@ -17,6 +17,7 @@
import (
"fmt"
"path"
+ "regexp"
"strings"
"github.com/emirpasic/gods/lists/singlylinkedlist"
@@ -77,6 +78,13 @@
// TestFilePattern represents the directive that controls which python
// files are mapped to `py_test` targets.
TestFilePattern = "python_test_file_pattern"
+ // LabelConvention represents the directive that defines the format of the
+ // labels to third-party dependencies.
+ LabelConvention = "python_label_convention"
+ // LabelNormalization represents the directive that controls how distribution
+ // names of labels to third-party dependencies are normalized. Supported values
+ // are 'none', 'pep503' and 'snake_case' (default). See LabelNormalizationType.
+ LabelNormalization = "python_label_normalization"
)
// GenerationModeType represents one of the generation modes for the Python
@@ -96,7 +104,8 @@
)
const (
- packageNameNamingConventionSubstitution = "$package_name$"
+ packageNameNamingConventionSubstitution = "$package_name$"
+ distributionNameLabelConventionSubstitution = "$distribution_name$"
)
const (
@@ -104,6 +113,10 @@
DefaultVisibilityFmtString = "//%s:__subpackages__"
// The default globs used to determine pt_test targets.
DefaultTestFilePatternString = "*_test.py,test_*.py"
+ // The default convention of label of third-party dependencies.
+ DefaultLabelConvention = "$distribution_name$"
+ // The default normalization applied to distribution names of third-party dependency labels.
+ DefaultLabelNormalizationType = SnakeCaseLabelNormalizationType
)
// defaultIgnoreFiles is the list of default values used in the
@@ -112,14 +125,6 @@
"setup.py": {},
}
-func SanitizeDistribution(distributionName string) string {
- sanitizedDistribution := strings.ToLower(distributionName)
- sanitizedDistribution = strings.ReplaceAll(sanitizedDistribution, "-", "_")
- sanitizedDistribution = strings.ReplaceAll(sanitizedDistribution, ".", "_")
-
- return sanitizedDistribution
-}
-
// Configs is an extension of map[string]*Config. It provides finding methods
// on top of the mapping.
type Configs map[string]*Config
@@ -156,8 +161,18 @@
defaultVisibility []string
visibility []string
testFilePattern []string
+ labelConvention string
+ labelNormalization LabelNormalizationType
}
+type LabelNormalizationType int
+
+const (
+ NoLabelNormalizationType LabelNormalizationType = iota
+ Pep503LabelNormalizationType
+ SnakeCaseLabelNormalizationType
+)
+
// New creates a new Config.
func New(
repoRoot string,
@@ -180,6 +195,8 @@
defaultVisibility: []string{fmt.Sprintf(DefaultVisibilityFmtString, "")},
visibility: []string{},
testFilePattern: strings.Split(DefaultTestFilePatternString, ","),
+ labelConvention: DefaultLabelConvention,
+ labelNormalization: DefaultLabelNormalizationType,
}
}
@@ -209,6 +226,8 @@
defaultVisibility: c.defaultVisibility,
visibility: c.visibility,
testFilePattern: c.testFilePattern,
+ labelConvention: c.labelConvention,
+ labelNormalization: c.labelNormalization,
}
}
@@ -263,10 +282,8 @@
} else if gazelleManifest.PipRepository != nil {
distributionRepositoryName = gazelleManifest.PipRepository.Name
}
- sanitizedDistribution := SanitizeDistribution(distributionName)
- // @<repository_name>//<distribution_name>
- lbl := label.New(distributionRepositoryName, sanitizedDistribution, sanitizedDistribution)
+ lbl := currentCfg.FormatThirdPartyDependency(distributionRepositoryName, distributionName)
return lbl.String(), true
}
}
@@ -443,3 +460,48 @@
func (c *Config) TestFilePattern() []string {
return c.testFilePattern
}
+
+// SetLabelConvention sets the label convention used for third-party dependencies.
+func (c *Config) SetLabelConvention(convention string) {
+ c.labelConvention = convention
+}
+
+// LabelConvention returns the label convention used for third-party dependencies.
+func (c *Config) LabelConvention() string {
+ return c.labelConvention
+}
+
+// SetLabelConvention sets the label normalization applied to distribution names of third-party dependencies.
+func (c *Config) SetLabelNormalization(normalizationType LabelNormalizationType) {
+ c.labelNormalization = normalizationType
+}
+
+// LabelConvention returns the label normalization applied to distribution names of third-party dependencies.
+func (c *Config) LabelNormalization() LabelNormalizationType {
+ return c.labelNormalization
+}
+
+// FormatThirdPartyDependency returns a label to a third-party dependency performing all formating and normalization.
+func (c *Config) FormatThirdPartyDependency(repositoryName string, distributionName string) label.Label {
+ conventionalDistributionName := strings.ReplaceAll(c.labelConvention, distributionNameLabelConventionSubstitution, distributionName)
+
+ var normConventionalDistributionName string
+ switch norm := c.LabelNormalization(); norm {
+ case SnakeCaseLabelNormalizationType:
+ // See /python/private/normalize_name.bzl
+ normConventionalDistributionName = strings.ToLower(conventionalDistributionName)
+ normConventionalDistributionName = regexp.MustCompile(`[-_.]+`).ReplaceAllString(normConventionalDistributionName, "_")
+ normConventionalDistributionName = strings.Trim(normConventionalDistributionName, "_")
+ case Pep503LabelNormalizationType:
+ // See https://packaging.python.org/en/latest/specifications/name-normalization/#name-format
+ normConventionalDistributionName = strings.ToLower(conventionalDistributionName) // ... "should be lowercased"
+ normConventionalDistributionName = regexp.MustCompile(`[-_.]+`).ReplaceAllString(normConventionalDistributionName, "-") // ... "all runs of the characters ., -, or _ replaced with a single -"
+ normConventionalDistributionName = strings.Trim(normConventionalDistributionName, "-") // ... "must start and end with a letter or number"
+ default:
+ fallthrough
+ case NoLabelNormalizationType:
+ normConventionalDistributionName = conventionalDistributionName
+ }
+
+ return label.New(repositoryName, normConventionalDistributionName, normConventionalDistributionName)
+}
diff --git a/gazelle/pythonconfig/pythonconfig_test.go b/gazelle/pythonconfig/pythonconfig_test.go
index bf31106..7cdb9af 100644
--- a/gazelle/pythonconfig/pythonconfig_test.go
+++ b/gazelle/pythonconfig/pythonconfig_test.go
@@ -4,20 +4,244 @@
"testing"
)
-func TestDistributionSanitizing(t *testing.T) {
+func TestFormatThirdPartyDependency(t *testing.T) {
+ type testInput struct {
+ RepositoryName string
+ DistributionName string
+ LabelNormalization LabelNormalizationType
+ LabelConvention string
+ }
+
tests := map[string]struct {
- input string
+ input testInput
want string
}{
- "upper case": {input: "DistWithUpperCase", want: "distwithuppercase"},
- "dashes": {input: "dist-with-dashes", want: "dist_with_dashes"},
- "dots": {input: "dist.with.dots", want: "dist_with_dots"},
- "mixed": {input: "To-be.sanitized", want: "to_be_sanitized"},
+ "default / upper case": {
+ input: testInput{
+ DistributionName: "DistWithUpperCase",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//distwithuppercase",
+ },
+ "default / dashes": {
+ input: testInput{
+ DistributionName: "dist-with-dashes",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//dist_with_dashes",
+ },
+ "default / repeating dashes inside": {
+ input: testInput{
+ DistributionName: "friendly--bard",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//friendly_bard",
+ },
+ "default / repeating underscores inside": {
+ input: testInput{
+ DistributionName: "hello___something",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello_something",
+ },
+ "default / prefix repeating underscores": {
+ input: testInput{
+ DistributionName: "__hello-something",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello_something",
+ },
+ "default / suffix repeating underscores": {
+ input: testInput{
+ DistributionName: "hello-something___",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello_something",
+ },
+ "default / prefix repeating dashes": {
+ input: testInput{
+ DistributionName: "---hello-something",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello_something",
+ },
+ "default / suffix repeating dashes": {
+ input: testInput{
+ DistributionName: "hello-something----",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello_something",
+ },
+ "default / dots": {
+ input: testInput{
+ DistributionName: "dist.with.dots",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//dist_with_dots",
+ },
+ "default / mixed": {
+ input: testInput{
+ DistributionName: "FrIeNdLy-._.-bArD",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//friendly_bard",
+ },
+ "default / upper case / custom prefix & suffix": {
+ input: testInput{
+ DistributionName: "DistWithUpperCase",
+ RepositoryName: "pip",
+ LabelNormalization: DefaultLabelNormalizationType,
+ LabelConvention: "pReFiX-$distribution_name$-sUfFiX",
+ },
+ want: "@pip//prefix_distwithuppercase_suffix",
+ },
+ "noop normalization / mixed": {
+ input: testInput{
+ DistributionName: "not-TO-be.sanitized",
+ RepositoryName: "pip",
+ LabelNormalization: NoLabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//not-TO-be.sanitized",
+ },
+ "noop normalization / mixed / custom prefix & suffix": {
+ input: testInput{
+ DistributionName: "not-TO-be.sanitized",
+ RepositoryName: "pip",
+ LabelNormalization: NoLabelNormalizationType,
+ LabelConvention: "pre___$distribution_name$___fix",
+ },
+ want: "@pip//pre___not-TO-be.sanitized___fix",
+ },
+ "pep503 / upper case": {
+ input: testInput{
+ DistributionName: "DistWithUpperCase",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//distwithuppercase",
+ },
+ "pep503 / underscores": {
+ input: testInput{
+ DistributionName: "dist_with_underscores",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//dist-with-underscores",
+ },
+ "pep503 / repeating dashes inside": {
+ input: testInput{
+ DistributionName: "friendly--bard",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//friendly-bard",
+ },
+ "pep503 / repeating underscores inside": {
+ input: testInput{
+ DistributionName: "hello___something",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello-something",
+ },
+ "pep503 / prefix repeating underscores": {
+ input: testInput{
+ DistributionName: "__hello-something",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello-something",
+ },
+ "pep503 / suffix repeating underscores": {
+ input: testInput{
+ DistributionName: "hello-something___",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello-something",
+ },
+ "pep503 / prefix repeating dashes": {
+ input: testInput{
+ DistributionName: "---hello-something",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello-something",
+ },
+ "pep503 / suffix repeating dashes": {
+ input: testInput{
+ DistributionName: "hello-something----",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//hello-something",
+ },
+ "pep503 / dots": {
+ input: testInput{
+ DistributionName: "dist.with.dots",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//dist-with-dots",
+ },
+ "pep503 / mixed": {
+ input: testInput{
+ DistributionName: "To-be.sanitized",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: DefaultLabelConvention,
+ },
+ want: "@pip//to-be-sanitized",
+ },
+ "pep503 / underscores / custom prefix & suffix": {
+ input: testInput{
+ DistributionName: "dist_with_underscores",
+ RepositoryName: "pip",
+ LabelNormalization: Pep503LabelNormalizationType,
+ LabelConvention: "pre___$distribution_name$___fix",
+ },
+ want: "@pip//pre-dist-with-underscores-fix",
+ },
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
- got := SanitizeDistribution(tc.input)
+ c := Config{
+ labelNormalization: tc.input.LabelNormalization,
+ labelConvention: tc.input.LabelConvention,
+ }
+ gotLabel := c.FormatThirdPartyDependency(tc.input.RepositoryName, tc.input.DistributionName)
+ got := gotLabel.String()
if tc.want != got {
t.Fatalf("expected %q, got %q", tc.want, got)
}