Skip to content

Commit

Permalink
feat(gazelle): Add "python_visibility" directive that appends additio…
Browse files Browse the repository at this point in the history
…nal 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.
  • Loading branch information
dougthor42 authored Mar 1, 2024
1 parent 3f40e98 commit da10ac4
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 4 deletions.
46 changes: 46 additions & 0 deletions gazelle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ Python-specific directives are as follows:
| 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`:
Expand Down Expand Up @@ -236,6 +238,50 @@ py_libary(
[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`.
Expand Down
3 changes: 3 additions & 0 deletions gazelle/python/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (py *Configurer) KnownDirectives() []string {
pythonconfig.LibraryNamingConvention,
pythonconfig.BinaryNamingConvention,
pythonconfig.TestNamingConvention,
pythonconfig.Visibility,
}
}

Expand Down Expand Up @@ -162,6 +163,8 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
config.SetBinaryNamingConvention(strings.TrimSpace(d.Value))
case pythonconfig.TestNamingConvention:
config.SetTestNamingConvention(strings.TrimSpace(d.Value))
case pythonconfig.Visibility:
config.AppendVisibility(strings.TrimSpace(d.Value))
}
}

Expand Down
3 changes: 2 additions & 1 deletion gazelle/python/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
}

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)
Expand Down
8 changes: 5 additions & 3 deletions gazelle/python/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ func (t *targetBuilder) addResolvedDependency(dep string) *targetBuilder {
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
}

Expand Down
4 changes: 4 additions & 0 deletions gazelle/python/testdata/directive_python_visibility/BUILD.in
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions gazelle/python/testdata/directive_python_visibility/BUILD.out
Original file line number Diff line number Diff line change
@@ -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__",
],
)
4 changes: 4 additions & 0 deletions gazelle/python/testdata/directive_python_visibility/README.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
2 changes: 2 additions & 0 deletions gazelle/python/testdata/directive_python_visibility/foo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def func():
print("library_func")
Original file line number Diff line number Diff line change
@@ -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__
Original file line number Diff line number Diff line change
@@ -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__",
],
)
Empty file.
Empty file.
17 changes: 17 additions & 0 deletions gazelle/python/testdata/directive_python_visibility/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
17 changes: 17 additions & 0 deletions gazelle/pythonconfig/pythonconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const (
// 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
Expand Down Expand Up @@ -136,6 +140,7 @@ type Config struct {
libraryNamingConvention string
binaryNamingConvention string
testNamingConvention string
visibility []string
}

// New creates a new Config.
Expand All @@ -157,6 +162,7 @@ func New(
libraryNamingConvention: packageNameNamingConventionSubstitution,
binaryNamingConvention: fmt.Sprintf("%s_bin", packageNameNamingConventionSubstitution),
testNamingConvention: fmt.Sprintf("%s_test", packageNameNamingConventionSubstitution),
visibility: []string{},
}
}

Expand All @@ -183,6 +189,7 @@ func (c *Config) NewChild() *Config {
libraryNamingConvention: c.libraryNamingConvention,
binaryNamingConvention: c.binaryNamingConvention,
testNamingConvention: c.testNamingConvention,
visibility: c.visibility,
}
}

Expand Down Expand Up @@ -388,3 +395,13 @@ func (c *Config) SetTestNamingConvention(testNamingConvention string) {
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
}

0 comments on commit da10ac4

Please sign in to comment.