aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouglas Thor <dougthor42@users.noreply.github.com>2024-05-08 18:33:09 -0700
committerGitHub <noreply@github.com>2024-05-09 01:33:09 +0000
commita1d3c0de0ae23ee2470c512db88c46f2fd06098b (patch)
tree2a0bb7609e667c443de2b154df026a9928fa1480
parentd3cec48e415dd598a773335532cbc5647c985a93 (diff)
downloadbazelbuild-rules_python-a1d3c0de0ae23ee2470c512db88c46f2fd06098b.tar.gz
feat(gazelle): Add "include_dep" Python comment annotation (#1863)
Add a new Python comment annotation for Gazelle: `include_dep`. This annotation accepts a comma-separated string of values. Values _should_ be targets names, but no validation is done. The annotation can be added multiple times, and all values are combined and de-duplicated. For `python_generation_mode = "package"`, the `include_dep` annotations found across all files included in the generated target. The `parser.annotations` struct is updated to include a new `includeDep` field, and `parser.parse` is updated to return the `annotations` struct. All target builders then add the resolved dependencies. Fixes #1862. Example: ```python # gazelle:include_dep //foo:bar,:hello_world,//:abc # gazelle:include_dep //:def,//foo:bar ``` will cause gazelle to generate: ```starlark deps = [ ":hello_world", "//:abc", "//:def", "//foo:bar", ] ``` --------- Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com>
-rw-r--r--CHANGELOG.md2
-rw-r--r--gazelle/README.md102
-rw-r--r--gazelle/python/generate.go13
-rw-r--r--gazelle/python/parser.go58
-rw-r--r--gazelle/python/target.go9
-rw-r--r--gazelle/python/testdata/annotation_include_dep/BUILD.in1
-rw-r--r--gazelle/python/testdata/annotation_include_dep/BUILD.out53
-rw-r--r--gazelle/python/testdata/annotation_include_dep/README.md10
-rw-r--r--gazelle/python/testdata/annotation_include_dep/WORKSPACE (renamed from gazelle/python/testdata/invalid_annotation/BUILD.in)0
-rw-r--r--gazelle/python/testdata/annotation_include_dep/__init__.py9
-rw-r--r--gazelle/python/testdata/annotation_include_dep/__main__.py7
-rw-r--r--gazelle/python/testdata/annotation_include_dep/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/annotation_include_dep/module1.py (renamed from gazelle/python/testdata/invalid_annotation/BUILD.out)0
-rw-r--r--gazelle/python/testdata/annotation_include_dep/module2.py5
-rw-r--r--gazelle/python/testdata/annotation_include_dep/module2_test.py5
-rw-r--r--gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.in1
-rw-r--r--gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.out29
-rw-r--r--gazelle/python/testdata/annotation_include_dep/subpkg/__init__.py0
-rw-r--r--gazelle/python/testdata/annotation_include_dep/subpkg/module1.py3
-rw-r--r--gazelle/python/testdata/annotation_include_dep/subpkg/module1_test.py5
-rw-r--r--gazelle/python/testdata/annotation_include_dep/subpkg/module2.py4
-rw-r--r--gazelle/python/testdata/annotation_include_dep/subpkg/module3.py3
-rw-r--r--gazelle/python/testdata/annotation_include_dep/test.yaml17
-rw-r--r--gazelle/python/testdata/invalid_annotation_exclude/BUILD.in0
-rw-r--r--gazelle/python/testdata/invalid_annotation_exclude/BUILD.out0
-rw-r--r--gazelle/python/testdata/invalid_annotation_exclude/README.md (renamed from gazelle/python/testdata/invalid_annotation/README.md)0
-rw-r--r--gazelle/python/testdata/invalid_annotation_exclude/WORKSPACE (renamed from gazelle/python/testdata/invalid_annotation/WORKSPACE)0
-rw-r--r--gazelle/python/testdata/invalid_annotation_exclude/__init__.py (renamed from gazelle/python/testdata/invalid_annotation/__init__.py)0
-rw-r--r--gazelle/python/testdata/invalid_annotation_exclude/test.yaml (renamed from gazelle/python/testdata/invalid_annotation/test.yaml)0
-rw-r--r--gazelle/python/testdata/invalid_annotation_include_dep/BUILD.in0
-rw-r--r--gazelle/python/testdata/invalid_annotation_include_dep/BUILD.out0
-rw-r--r--gazelle/python/testdata/invalid_annotation_include_dep/README.md3
-rw-r--r--gazelle/python/testdata/invalid_annotation_include_dep/WORKSPACE1
-rw-r--r--gazelle/python/testdata/invalid_annotation_include_dep/__init__.py15
-rw-r--r--gazelle/python/testdata/invalid_annotation_include_dep/test.yaml19
35 files changed, 379 insertions, 13 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 415b936..449dae0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -73,6 +73,8 @@ A brief description of the categories of changes:
the whl and sdist files will be written to the lock file. Controlling whether
the downloading of metadata is done in parallel can be done using
`parallel_download` attribute.
+* (gazelle) Add a new annotation `include_deps`. Also add documentation for
+ annotations to `gazelle/README.md`.
* (deps): `rules_python` depends now on `rules_cc` 0.0.9
* (pip_parse): A new flag `use_hub_alias_dependencies` has been added that is going
to become default in the next release. This makes use of `dep_template` flag
diff --git a/gazelle/README.md b/gazelle/README.md
index 4c1cb27..e7b1766 100644
--- a/gazelle/README.md
+++ b/gazelle/README.md
@@ -425,6 +425,108 @@ py_library(
[issue-1826]: https://github.com/bazelbuild/rules_python/issues/1826
+### Annotations
+
+*Annotations* refer to comments found _within Python files_ that configure how
+Gazelle acts for that particular file.
+
+Annotations have the form:
+
+```python
+# gazelle:annotation_name value
+```
+
+and can reside anywhere within a Python file where comments are valid. For example:
+
+```python
+import foo
+# gazelle:annotation_name value
+
+def bar(): # gazelle:annotation_name value
+ pass
+```
+
+The annotations are:
+
+| **Annotation** | **Default value** |
+|---------------------------------------------------------------|-------------------|
+| [`# gazelle:ignore imports`](#annotation-ignore) | N/A |
+| Tells Gazelle to ignore import statements. `imports` is a comma-separated list of imports to ignore. | |
+| [`# gazelle:include_dep targets`](#annotation-include_dep) | N/A |
+| Tells Gazelle to include a set of dependencies, even if they are not imported in a Python module. `targets` is a comma-separated list of target names to include as dependencies. | |
+
+
+#### Annotation: `ignore`
+
+This annotation accepts a comma-separated string of values. Values are names of Python
+imports that Gazelle should _not_ include in target dependencies.
+
+The annotation can be added multiple times, and all values are combined and
+de-duplicated.
+
+For `python_generation_mode = "package"`, the `ignore` annotations
+found across all files included in the generated target are removed from `deps`.
+
+Example:
+
+```python
+import numpy # a pypi package
+
+# gazelle:ignore bar.baz.hello,foo
+import bar.baz.hello
+import foo
+
+# Ignore this import because _reasons_
+import baz # gazelle:ignore baz
+```
+
+will cause Gazelle to generate:
+
+```starlark
+deps = ["@pypi//numpy"],
+```
+
+
+#### Annotation: `include_dep`
+
+This annotation accepts a comma-separated string of values. Values _must_
+be Python targets, but _no validation is done_. If a value is not a Python
+target, building will result in an error saying:
+
+```
+<target> does not have mandatory providers: 'PyInfo' or 'CcInfo' or 'PyInfo'.
+```
+
+Adding non-Python targets to the generated target is a feature request being
+tracked in [Issue #1865](https://github.com/bazelbuild/rules_python/issues/1865).
+
+The annotation can be added multiple times, and all values are combined
+and de-duplicated.
+
+For `python_generation_mode = "package"`, the `include_dep` annotations
+found across all files included in the generated target are included in `deps`.
+
+Example:
+
+```python
+# gazelle:include_dep //foo:bar,:hello_world,//:abc
+# gazelle:include_dep //:def,//foo:bar
+import numpy # a pypi package
+```
+
+will cause Gazelle to generate:
+
+```starlark
+deps = [
+ ":hello_world",
+ "//:abc",
+ "//:def",
+ "//foo:bar",
+ "@pypi//numpy",
+]
+```
+
+
### Libraries
Python source files are those ending in `.py` but not ending in `_test.py`.
diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go
index 1937831..8889438 100644
--- a/gazelle/python/generate.go
+++ b/gazelle/python/generate.go
@@ -233,7 +233,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors := singlylinkedlist.New()
appendPyLibrary := func(srcs *treeset.Set, pyLibraryTargetName string) {
- allDeps, mainModules, err := parser.parse(srcs)
+ allDeps, mainModules, annotations, err := parser.parse(srcs)
if err != nil {
log.Fatalf("ERROR: %v\n", err)
}
@@ -263,6 +263,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
addVisibility(visibility).
addSrc(filename).
addModuleDependencies(mainModules[filename]).
+ addResolvedDependencies(annotations.includeDeps).
generateImportsAttribute().build()
result.Gen = append(result.Gen, pyBinary)
result.Imports = append(result.Imports, pyBinary.PrivateAttr(config.GazelleImportsKey))
@@ -290,6 +291,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
addVisibility(visibility).
addSrcs(srcs).
addModuleDependencies(allDeps).
+ addResolvedDependencies(annotations.includeDeps).
generateImportsAttribute().
build()
@@ -314,7 +316,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
}
if hasPyBinaryEntryPointFile {
- deps, _, err := parser.parseSingle(pyBinaryEntrypointFilename)
+ deps, _, annotations, err := parser.parseSingle(pyBinaryEntrypointFilename)
if err != nil {
log.Fatalf("ERROR: %v\n", err)
}
@@ -338,6 +340,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
addVisibility(visibility).
addSrc(pyBinaryEntrypointFilename).
addModuleDependencies(deps).
+ addResolvedDependencies(annotations.includeDeps).
generateImportsAttribute()
pyBinary := pyBinaryTarget.build()
@@ -348,7 +351,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
var conftest *rule.Rule
if hasConftestFile {
- deps, _, err := parser.parseSingle(conftestFilename)
+ deps, _, annotations, err := parser.parseSingle(conftestFilename)
if err != nil {
log.Fatalf("ERROR: %v\n", err)
}
@@ -367,6 +370,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyFileNames).
addSrc(conftestFilename).
addModuleDependencies(deps).
+ addResolvedDependencies(annotations.includeDeps).
addVisibility(visibility).
setTestonly().
generateImportsAttribute()
@@ -379,7 +383,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
var pyTestTargets []*targetBuilder
newPyTestTargetBuilder := func(srcs *treeset.Set, pyTestTargetName string) *targetBuilder {
- deps, _, err := parser.parse(srcs)
+ deps, _, annotations, err := parser.parse(srcs)
if err != nil {
log.Fatalf("ERROR: %v\n", err)
}
@@ -397,6 +401,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyFileNames).
addSrcs(srcs).
addModuleDependencies(deps).
+ addResolvedDependencies(annotations.includeDeps).
generateImportsAttribute()
}
if (hasPyTestEntryPointFile || hasPyTestEntryPointTarget || cfg.CoarseGrainedGeneration()) && !cfg.PerFileGeneration() {
diff --git a/gazelle/python/parser.go b/gazelle/python/parser.go
index 9b00b83..184fad7 100644
--- a/gazelle/python/parser.go
+++ b/gazelle/python/parser.go
@@ -101,7 +101,7 @@ func newPython3Parser(
// parseSingle parses a single Python file and returns the extracted modules
// from the import statements as well as the parsed comments.
-func (p *python3Parser) parseSingle(pyFilename string) (*treeset.Set, map[string]*treeset.Set, error) {
+func (p *python3Parser) parseSingle(pyFilename string) (*treeset.Set, map[string]*treeset.Set, *annotations, error) {
pyFilenames := treeset.NewWith(godsutils.StringComparator)
pyFilenames.Add(pyFilename)
return p.parse(pyFilenames)
@@ -109,7 +109,7 @@ func (p *python3Parser) parseSingle(pyFilename string) (*treeset.Set, map[string
// parse parses multiple Python files and returns the extracted modules from
// the import statements as well as the parsed comments.
-func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, map[string]*treeset.Set, error) {
+func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, map[string]*treeset.Set, *annotations, error) {
parserMutex.Lock()
defer parserMutex.Unlock()
@@ -122,28 +122,30 @@ func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, map[strin
}
encoder := json.NewEncoder(parserStdin)
if err := encoder.Encode(&req); err != nil {
- return nil, nil, fmt.Errorf("failed to parse: %w", err)
+ return nil, nil, nil, fmt.Errorf("failed to parse: %w", err)
}
reader := bufio.NewReader(parserStdout)
data, err := reader.ReadBytes(0)
if err != nil {
- return nil, nil, fmt.Errorf("failed to parse: %w", err)
+ return nil, nil, nil, fmt.Errorf("failed to parse: %w", err)
}
data = data[:len(data)-1]
var allRes []parserResponse
if err := json.Unmarshal(data, &allRes); err != nil {
- return nil, nil, fmt.Errorf("failed to parse: %w", err)
+ return nil, nil, nil, fmt.Errorf("failed to parse: %w", err)
}
mainModules := make(map[string]*treeset.Set, len(allRes))
+ allAnnotations := new(annotations)
+ allAnnotations.ignore = make(map[string]struct{})
for _, res := range allRes {
if res.HasMain {
mainModules[res.FileName] = treeset.NewWith(moduleComparator)
}
annotations, err := annotationsFromComments(res.Comments)
if err != nil {
- return nil, nil, fmt.Errorf("failed to parse annotations: %w", err)
+ return nil, nil, nil, fmt.Errorf("failed to parse annotations: %w", err)
}
for _, m := range res.Modules {
@@ -164,9 +166,32 @@ func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, map[strin
mainModules[res.FileName].Add(m)
}
}
+
+ // Collect all annotations from each file into a single annotations struct.
+ for k, v := range annotations.ignore {
+ allAnnotations.ignore[k] = v
+ }
+ allAnnotations.includeDeps = append(allAnnotations.includeDeps, annotations.includeDeps...)
}
- return modules, mainModules, nil
+ allAnnotations.includeDeps = removeDupesFromStringTreeSetSlice(allAnnotations.includeDeps)
+
+ return modules, mainModules, allAnnotations, nil
+}
+
+// removeDupesFromStringTreeSetSlice takes a []string, makes a set out of the
+// elements, and then returns a new []string with all duplicates removed. Order
+// is preserved.
+func removeDupesFromStringTreeSetSlice(array []string) []string {
+ s := treeset.NewWith(godsutils.StringComparator)
+ for _, v := range array {
+ s.Add(v)
+ }
+ dedupe := make([]string, s.Size())
+ for i, v := range s.Values() {
+ dedupe[i] = fmt.Sprint(v)
+ }
+ return dedupe
}
// parserResponse represents a response returned by the parser.py for a given
@@ -211,7 +236,8 @@ const (
// The Gazelle annotation prefix.
annotationPrefix string = "gazelle:"
// The ignore annotation kind. E.g. '# gazelle:ignore <module_name>'.
- annotationKindIgnore annotationKind = "ignore"
+ annotationKindIgnore annotationKind = "ignore"
+ annotationKindIncludeDep annotationKind = "include_dep"
)
// comment represents a Python comment.
@@ -247,12 +273,15 @@ type annotation struct {
type annotations struct {
// The parsed modules to be ignored by Gazelle.
ignore map[string]struct{}
+ // Labels that Gazelle should include as deps of the generated target.
+ includeDeps []string
}
// annotationsFromComments returns all the annotations parsed out of the
// comments of a Python module.
func annotationsFromComments(comments []comment) (*annotations, error) {
ignore := make(map[string]struct{})
+ includeDeps := []string{}
for _, comment := range comments {
annotation, err := comment.asAnnotation()
if err != nil {
@@ -269,10 +298,21 @@ func annotationsFromComments(comments []comment) (*annotations, error) {
ignore[m] = struct{}{}
}
}
+ if annotation.kind == annotationKindIncludeDep {
+ targets := strings.Split(annotation.value, ",")
+ for _, t := range targets {
+ if t == "" {
+ continue
+ }
+ t = strings.TrimSpace(t)
+ includeDeps = append(includeDeps, t)
+ }
+ }
}
}
return &annotations{
- ignore: ignore,
+ ignore: ignore,
+ includeDeps: includeDeps,
}, nil
}
diff --git a/gazelle/python/target.go b/gazelle/python/target.go
index a941a7c..c40d6fb 100644
--- a/gazelle/python/target.go
+++ b/gazelle/python/target.go
@@ -99,6 +99,15 @@ func (t *targetBuilder) addResolvedDependency(dep string) *targetBuilder {
return t
}
+// addResolvedDependencies adds multiple dependencies, that have already been
+// resolved or generated, to the target.
+func (t *targetBuilder) addResolvedDependencies(deps []string) *targetBuilder {
+ for _, dep := range deps {
+ t.addResolvedDependency(dep)
+ }
+ return t
+}
+
// addVisibility adds visibility labels to the target.
func (t *targetBuilder) addVisibility(visibility []string) *targetBuilder {
for _, item := range visibility {
diff --git a/gazelle/python/testdata/annotation_include_dep/BUILD.in b/gazelle/python/testdata/annotation_include_dep/BUILD.in
new file mode 100644
index 0000000..af2c2ce
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_generation_mode file
diff --git a/gazelle/python/testdata/annotation_include_dep/BUILD.out b/gazelle/python/testdata/annotation_include_dep/BUILD.out
new file mode 100644
index 0000000..1cff8f4
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/BUILD.out
@@ -0,0 +1,53 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+
+# gazelle:python_generation_mode file
+
+py_library(
+ name = "__init__",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":module1",
+ ":module2",
+ "//foo/bar:baz",
+ "//hello:world",
+ "@gazelle_python_test//foo",
+ "@star_wars//rebel_alliance/luke:skywalker",
+ ],
+)
+
+py_library(
+ name = "module1",
+ srcs = ["module1.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_library(
+ name = "module2",
+ srcs = ["module2.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//checking/py_binary/from/if:works",
+ "//foo:bar",
+ ],
+)
+
+py_binary(
+ name = "annotation_include_dep_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":module2",
+ "//checking/py_binary/from/__main__:works",
+ ],
+)
+
+py_test(
+ name = "module2_test",
+ srcs = ["module2_test.py"],
+ deps = [
+ ":module2",
+ "//checking/py_test/works:too",
+ ],
+)
diff --git a/gazelle/python/testdata/annotation_include_dep/README.md b/gazelle/python/testdata/annotation_include_dep/README.md
new file mode 100644
index 0000000..4c8afbe
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/README.md
@@ -0,0 +1,10 @@
+# Annotation: Include Dep
+
+Test that the Python gazelle annotation `# gazelle:include_dep` correctly adds dependences
+to the generated target even if those dependencies are not imported by the Python module.
+
+The root directory tests that all `py_*` targets will correctly include the additional
+dependencies.
+
+The `subpkg` directory tests that all `# gazlle:include_dep` annotations found in all source
+files are included in the generated target (such as during `generation_mode package`).
diff --git a/gazelle/python/testdata/invalid_annotation/BUILD.in b/gazelle/python/testdata/annotation_include_dep/WORKSPACE
index e69de29..e69de29 100644
--- a/gazelle/python/testdata/invalid_annotation/BUILD.in
+++ b/gazelle/python/testdata/annotation_include_dep/WORKSPACE
diff --git a/gazelle/python/testdata/annotation_include_dep/__init__.py b/gazelle/python/testdata/annotation_include_dep/__init__.py
new file mode 100644
index 0000000..6101534
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/__init__.py
@@ -0,0 +1,9 @@
+import module1
+import foo # third party package
+
+# gazelle:include_dep //foo/bar:baz
+# gazelle:include_dep //hello:world,@star_wars//rebel_alliance/luke:skywalker
+# gazelle:include_dep :module2
+
+del module1
+del foo
diff --git a/gazelle/python/testdata/annotation_include_dep/__main__.py b/gazelle/python/testdata/annotation_include_dep/__main__.py
new file mode 100644
index 0000000..6d9d8aa
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/__main__.py
@@ -0,0 +1,7 @@
+# gazelle:include_dep //checking/py_binary/from/__main__:works
+# Check deduping
+# gazelle:include_dep //checking/py_binary/from/__main__:works
+
+import module2
+
+del module2
diff --git a/gazelle/python/testdata/annotation_include_dep/gazelle_python.yaml b/gazelle/python/testdata/annotation_include_dep/gazelle_python.yaml
new file mode 100644
index 0000000..7afe81f
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# Copyright 2024 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.
+
+manifest:
+ modules_mapping:
+ foo: foo
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/invalid_annotation/BUILD.out b/gazelle/python/testdata/annotation_include_dep/module1.py
index e69de29..e69de29 100644
--- a/gazelle/python/testdata/invalid_annotation/BUILD.out
+++ b/gazelle/python/testdata/annotation_include_dep/module1.py
diff --git a/gazelle/python/testdata/annotation_include_dep/module2.py b/gazelle/python/testdata/annotation_include_dep/module2.py
new file mode 100644
index 0000000..23a75af
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/module2.py
@@ -0,0 +1,5 @@
+# gazelle:include_dep //foo:bar
+
+if __name__ == "__main__":
+ # gazelle:include_dep //checking/py_binary/from/if:works
+ print("hello")
diff --git a/gazelle/python/testdata/annotation_include_dep/module2_test.py b/gazelle/python/testdata/annotation_include_dep/module2_test.py
new file mode 100644
index 0000000..6fa18c6
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/module2_test.py
@@ -0,0 +1,5 @@
+# gazelle:include_dep //checking/py_test/works:too
+
+import module2
+
+del module2
diff --git a/gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.in b/gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.in
new file mode 100644
index 0000000..421b486
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_generation_mode package
diff --git a/gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.out b/gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.out
new file mode 100644
index 0000000..921c892
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/subpkg/BUILD.out
@@ -0,0 +1,29 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+# gazelle:python_generation_mode package
+
+py_library(
+ name = "subpkg",
+ srcs = [
+ "__init__.py",
+ "module1.py",
+ "module2.py",
+ "module3.py",
+ ],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":nonexistant_target_from_include_dep_in_module3",
+ "//me_from_module1",
+ "//other/thing:from_include_dep_in_module2",
+ "//you_from_module1",
+ ],
+)
+
+py_test(
+ name = "module1_test",
+ srcs = ["module1_test.py"],
+ deps = [
+ ":subpkg",
+ "//:bagel_from_include_dep_in_module1_test",
+ ],
+)
diff --git a/gazelle/python/testdata/annotation_include_dep/subpkg/__init__.py b/gazelle/python/testdata/annotation_include_dep/subpkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/subpkg/__init__.py
diff --git a/gazelle/python/testdata/annotation_include_dep/subpkg/module1.py b/gazelle/python/testdata/annotation_include_dep/subpkg/module1.py
new file mode 100644
index 0000000..01566a0
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/subpkg/module1.py
@@ -0,0 +1,3 @@
+def hello():
+ # gazelle:include_dep //you_from_module1,//me_from_module1
+ pass
diff --git a/gazelle/python/testdata/annotation_include_dep/subpkg/module1_test.py b/gazelle/python/testdata/annotation_include_dep/subpkg/module1_test.py
new file mode 100644
index 0000000..087763a
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/subpkg/module1_test.py
@@ -0,0 +1,5 @@
+# gazelle:include_dep //:bagel_from_include_dep_in_module1_test
+
+import module1
+
+del module1
diff --git a/gazelle/python/testdata/annotation_include_dep/subpkg/module2.py b/gazelle/python/testdata/annotation_include_dep/subpkg/module2.py
new file mode 100644
index 0000000..dabeb67
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/subpkg/module2.py
@@ -0,0 +1,4 @@
+# gazelle:include_dep //other/thing:from_include_dep_in_module2
+import module1
+
+del module1
diff --git a/gazelle/python/testdata/annotation_include_dep/subpkg/module3.py b/gazelle/python/testdata/annotation_include_dep/subpkg/module3.py
new file mode 100644
index 0000000..899a7c4
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/subpkg/module3.py
@@ -0,0 +1,3 @@
+def goodbye():
+ # gazelle:include_dep :nonexistant_target_from_include_dep_in_module3
+ pass
diff --git a/gazelle/python/testdata/annotation_include_dep/test.yaml b/gazelle/python/testdata/annotation_include_dep/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/annotation_include_dep/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/python/testdata/invalid_annotation_exclude/BUILD.in b/gazelle/python/testdata/invalid_annotation_exclude/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_exclude/BUILD.in
diff --git a/gazelle/python/testdata/invalid_annotation_exclude/BUILD.out b/gazelle/python/testdata/invalid_annotation_exclude/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_exclude/BUILD.out
diff --git a/gazelle/python/testdata/invalid_annotation/README.md b/gazelle/python/testdata/invalid_annotation_exclude/README.md
index b2544b5..b2544b5 100644
--- a/gazelle/python/testdata/invalid_annotation/README.md
+++ b/gazelle/python/testdata/invalid_annotation_exclude/README.md
diff --git a/gazelle/python/testdata/invalid_annotation/WORKSPACE b/gazelle/python/testdata/invalid_annotation_exclude/WORKSPACE
index faff6af..faff6af 100644
--- a/gazelle/python/testdata/invalid_annotation/WORKSPACE
+++ b/gazelle/python/testdata/invalid_annotation_exclude/WORKSPACE
diff --git a/gazelle/python/testdata/invalid_annotation/__init__.py b/gazelle/python/testdata/invalid_annotation_exclude/__init__.py
index 7aee876..7aee876 100644
--- a/gazelle/python/testdata/invalid_annotation/__init__.py
+++ b/gazelle/python/testdata/invalid_annotation_exclude/__init__.py
diff --git a/gazelle/python/testdata/invalid_annotation/test.yaml b/gazelle/python/testdata/invalid_annotation_exclude/test.yaml
index 19924b1..19924b1 100644
--- a/gazelle/python/testdata/invalid_annotation/test.yaml
+++ b/gazelle/python/testdata/invalid_annotation_exclude/test.yaml
diff --git a/gazelle/python/testdata/invalid_annotation_include_dep/BUILD.in b/gazelle/python/testdata/invalid_annotation_include_dep/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_include_dep/BUILD.in
diff --git a/gazelle/python/testdata/invalid_annotation_include_dep/BUILD.out b/gazelle/python/testdata/invalid_annotation_include_dep/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_include_dep/BUILD.out
diff --git a/gazelle/python/testdata/invalid_annotation_include_dep/README.md b/gazelle/python/testdata/invalid_annotation_include_dep/README.md
new file mode 100644
index 0000000..2f8e024
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_include_dep/README.md
@@ -0,0 +1,3 @@
+# Invalid annotation
+This test case asserts that the parse step fails as expected due to invalid annotation format of
+the `include_dep` annotation.
diff --git a/gazelle/python/testdata/invalid_annotation_include_dep/WORKSPACE b/gazelle/python/testdata/invalid_annotation_include_dep/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_include_dep/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/invalid_annotation_include_dep/__init__.py b/gazelle/python/testdata/invalid_annotation_include_dep/__init__.py
new file mode 100644
index 0000000..61f4c76
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_include_dep/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2024 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.
+
+# gazelle:include_dep
diff --git a/gazelle/python/testdata/invalid_annotation_include_dep/test.yaml b/gazelle/python/testdata/invalid_annotation_include_dep/test.yaml
new file mode 100644
index 0000000..f2159a6
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation_include_dep/test.yaml
@@ -0,0 +1,19 @@
+# Copyright 2024 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: 1
+ stderr: |
+ gazelle: ERROR: failed to parse annotations: `# gazelle:include_dep` requires a value