feat(gazelle): Add "python_visibility" directive that appends additional visibility labels (#1784)
Fixes #1783.
Add a new gazelle directive, `python_visibility`, that allows
users to add labels to the `visibility` attribute of generated targets.
out by the way, hence this PR), I noticed that the docs were a little
This directive acts similar to[^1] the [`go_visibility`
directive](https://github.com/bazelbuild/bazel-gazelle#directives).
The primary use case is for python projects that separate unit test
files from the python packages/modules that they test, like so:
```
packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│ └── mypackage/
│ ├── __init__.py
│ └── foo.py
└── tests/
├── __init__.py
└── test_foo.py
```
A future PR will add an example to the `./examples` directory (issue
#1775).
[^1]: At least, similar based on docs. I haven't done any actual
comparison.diff --git a/gazelle/README.md b/gazelle/README.md
index 117aacb..0017034 100644
--- a/gazelle/README.md
+++ b/gazelle/README.md
@@ -198,6 +198,8 @@
| Controls the `py_test` naming convention. Follows the same interpolation rules as `python_library_naming_convention`. | |
| `# gazelle:resolve py ...` | n/a |
| Instructs the plugin what target to add as a dependency to satisfy a given import statement. The syntax is `# gazelle:resolve py import-string label` where `import-string` is the symbol in the python `import` statement, and `label` is the Bazel label that Gazelle should write in `deps`. | |
+| [`# gazelle:python_visibility label`](#directive-python_visibility) | |
+| Appends additional visibility labels to each generated target. This directive can be set multiple times. | |
#### Directive: `python_root`:
@@ -236,6 +238,50 @@
[python-packaging-user-guide]: https://github.com/pypa/packaging.python.org/blob/4c86169a/source/tutorials/packaging-projects.rst
+#### Directive: `python_visibility`:
+
+Appends additional `visibility` labels to each generated target.
+
+This directive can be set multiple times. The generated `visibility` attribute
+will include the default visibility and all labels defined by this directive.
+All labels will be ordered alphabetically.
+
+```starlark
+# ./BUILD.bazel
+# gazelle:python_visibility //tests:__pkg__
+# gazelle:python_visibility //bar:baz
+
+py_library(
+ ...
+ visibility = [
+ "//:__subpackages__", # default visibility
+ "//bar:baz",
+ "//tests:__pkg__",
+ ],
+ ...
+)
+```
+
+Child Bazel packages inherit values from parents:
+
+```starlark
+# ./bar/BUILD.bazel
+# gazelle:python_visibility //tests:__subpackages__
+
+py_library(
+ ...
+ visibility = [
+ "//:__subpackages__", # default visibility
+ "//bar:baz", # defined in ../BUILD.bazel
+ "//tests:__pkg__", # defined in ../BUILD.bazel
+ "//tests:__subpackages__", # defined in this ./BUILD.bazel
+ ],
+ ...
+)
+
+```
+
+
### Libraries
Python source files are those ending in `.py` but not ending in `_test.py`.
diff --git a/gazelle/python/configure.go b/gazelle/python/configure.go
index 69d2762..4315688 100644
--- a/gazelle/python/configure.go
+++ b/gazelle/python/configure.go
@@ -63,6 +63,7 @@
pythonconfig.LibraryNamingConvention,
pythonconfig.BinaryNamingConvention,
pythonconfig.TestNamingConvention,
+ pythonconfig.Visibility,
}
}
@@ -162,6 +163,8 @@
config.SetBinaryNamingConvention(strings.TrimSpace(d.Value))
case pythonconfig.TestNamingConvention:
config.SetTestNamingConvention(strings.TrimSpace(d.Value))
+ case pythonconfig.Visibility:
+ config.AppendVisibility(strings.TrimSpace(d.Value))
}
}
diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go
index ba273be..6973d2d 100644
--- a/gazelle/python/generate.go
+++ b/gazelle/python/generate.go
@@ -212,7 +212,8 @@
}
parser := newPython3Parser(args.Config.RepoRoot, args.Rel, cfg.IgnoresDependency)
- visibility := fmt.Sprintf("//%s:__subpackages__", pythonProjectRoot)
+ visibility := []string{fmt.Sprintf("//%s:__subpackages__", pythonProjectRoot)}
+ visibility = append(visibility, cfg.Visibility()...)
var result language.GenerateResult
result.Gen = make([]*rule.Rule, 0)
diff --git a/gazelle/python/target.go b/gazelle/python/target.go
index e310405..a941a7c 100644
--- a/gazelle/python/target.go
+++ b/gazelle/python/target.go
@@ -99,9 +99,11 @@
return t
}
-// addVisibility adds a visibility to the target.
-func (t *targetBuilder) addVisibility(visibility string) *targetBuilder {
- t.visibility.Add(visibility)
+// addVisibility adds visibility labels to the target.
+func (t *targetBuilder) addVisibility(visibility []string) *targetBuilder {
+ for _, item := range visibility {
+ t.visibility.Add(item)
+ }
return t
}
diff --git a/gazelle/python/testdata/directive_python_visibility/BUILD.in b/gazelle/python/testdata/directive_python_visibility/BUILD.in
new file mode 100644
index 0000000..c1ba9e4
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/BUILD.in
@@ -0,0 +1,4 @@
+# Directives can be added in any order. They will be ordered alphabetically
+# when added.
+# gazelle:python_visibility //tests:__pkg__
+# gazelle:python_visibility //bar:baz
diff --git a/gazelle/python/testdata/directive_python_visibility/BUILD.out b/gazelle/python/testdata/directive_python_visibility/BUILD.out
new file mode 100644
index 0000000..70715e8
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/BUILD.out
@@ -0,0 +1,16 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# Directives can be added in any order. They will be ordered alphabetically
+# when added.
+# gazelle:python_visibility //tests:__pkg__
+# gazelle:python_visibility //bar:baz
+
+py_library(
+ name = "directive_python_visibility",
+ srcs = ["foo.py"],
+ visibility = [
+ "//:__subpackages__",
+ "//bar:baz",
+ "//tests:__pkg__",
+ ],
+)
diff --git a/gazelle/python/testdata/directive_python_visibility/README.md b/gazelle/python/testdata/directive_python_visibility/README.md
new file mode 100644
index 0000000..51ab7ae
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/README.md
@@ -0,0 +1,4 @@
+# Directive: `python_visibility`
+
+This test case asserts that the `# gazelle:python_visibility` directive correctly
+appends multiple labels to the target's `visibility` parameter.
diff --git a/gazelle/python/testdata/directive_python_visibility/WORKSPACE b/gazelle/python/testdata/directive_python_visibility/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/directive_python_visibility/foo.py b/gazelle/python/testdata/directive_python_visibility/foo.py
new file mode 100644
index 0000000..98907eb
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/foo.py
@@ -0,0 +1,2 @@
+def func():
+ print("library_func")
diff --git a/gazelle/python/testdata/directive_python_visibility/subdir/BUILD.in b/gazelle/python/testdata/directive_python_visibility/subdir/BUILD.in
new file mode 100644
index 0000000..5193e69
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/subdir/BUILD.in
@@ -0,0 +1,4 @@
+# python_visibilty directive applies to all child bazel packages.
+# Thus, the generated file for this package will also have vis for
+# //tests:__pkg__ and //bar:baz in addition to the default.
+# gazelle:python_visibility //tests:__subpackages__
diff --git a/gazelle/python/testdata/directive_python_visibility/subdir/BUILD.out b/gazelle/python/testdata/directive_python_visibility/subdir/BUILD.out
new file mode 100644
index 0000000..722c840
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/subdir/BUILD.out
@@ -0,0 +1,20 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# python_visibilty directive applies to all child bazel packages.
+# Thus, the generated file for this package will also have vis for
+# //tests:__pkg__ and //bar:baz in addition to the default.
+# gazelle:python_visibility //tests:__subpackages__
+
+py_library(
+ name = "subdir",
+ srcs = [
+ "__init__.py",
+ "bar.py",
+ ],
+ visibility = [
+ "//:__subpackages__",
+ "//bar:baz",
+ "//tests:__pkg__",
+ "//tests:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/directive_python_visibility/subdir/__init__.py b/gazelle/python/testdata/directive_python_visibility/subdir/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/subdir/__init__.py
diff --git a/gazelle/python/testdata/directive_python_visibility/subdir/bar.py b/gazelle/python/testdata/directive_python_visibility/subdir/bar.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/subdir/bar.py
diff --git a/gazelle/python/testdata/directive_python_visibility/test.yaml b/gazelle/python/testdata/directive_python_visibility/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/directive_python_visibility/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
diff --git a/gazelle/pythonconfig/pythonconfig.go b/gazelle/pythonconfig/pythonconfig.go
index cecf9dc..e350a7c 100644
--- a/gazelle/pythonconfig/pythonconfig.go
+++ b/gazelle/pythonconfig/pythonconfig.go
@@ -67,6 +67,10 @@
// naming convention. See python_library_naming_convention for more info on
// the package name interpolation.
TestNamingConvention = "python_test_naming_convention"
+ // Visibility represents the directive that controls what additional
+ // visibility labels are added to generated targets. It mimics the behavior
+ // of the `go_visibility` directive.
+ Visibility = "python_visibility"
)
// GenerationModeType represents one of the generation modes for the Python
@@ -136,6 +140,7 @@
libraryNamingConvention string
binaryNamingConvention string
testNamingConvention string
+ visibility []string
}
// New creates a new Config.
@@ -157,6 +162,7 @@
libraryNamingConvention: packageNameNamingConventionSubstitution,
binaryNamingConvention: fmt.Sprintf("%s_bin", packageNameNamingConventionSubstitution),
testNamingConvention: fmt.Sprintf("%s_test", packageNameNamingConventionSubstitution),
+ visibility: []string{},
}
}
@@ -183,6 +189,7 @@
libraryNamingConvention: c.libraryNamingConvention,
binaryNamingConvention: c.binaryNamingConvention,
testNamingConvention: c.testNamingConvention,
+ visibility: c.visibility,
}
}
@@ -388,3 +395,13 @@
func (c *Config) RenderTestName(packageName string) string {
return strings.ReplaceAll(c.testNamingConvention, packageNameNamingConventionSubstitution, packageName)
}
+
+// AppendVisibility adds additional items to the target's visibility.
+func (c *Config) AppendVisibility(visibility string) {
+ c.visibility = append(c.visibility, visibility)
+}
+
+// Visibility returns the target's visibility.
+func (c *Config) Visibility() []string {
+ return c.visibility
+}