Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gazelle/docs/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ deps = [
```


(annotation-include-pytest-conftest)=
## `include_pytest_conftest`

:::{versionadded} 1.6.0
Expand Down
69 changes: 69 additions & 0 deletions gazelle/docs/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ The Python-specific directives are:
* Default: `false`
* Allowed Values: `true`, `false`

[`# gazelle:python_include_ancestor_conftest bool`](#python-include-ancestor-conftest)
: Controls whether ancestor conftest targets are added to {bzl:obj}`py_test` target
dependencies.
* Default: `true`
* Allowed Values: `true`, `false`

## `python_extension`

Expand Down Expand Up @@ -720,3 +725,67 @@ previously-generated or hand-created rules.
:::{error}
Detailed docs are not yet written.
:::

## `python_include_ancestor_conftest`

Version 1.9.0 includes a fix ({gh-pr}`3498`) for a long-standing issue
({gh-issue}`3497`) where ancestor `conftest.py` files were not automatically
added as dependencies of {bzl:obj}`py_test` targets.

However, some people may not want this behavior (see https://xkcd.com/1172/).
Thus the `python_include_ancestor_conftest` directive controls this behavior.
It defaults to `true`, which causes all ancestor `conftest.py` files to be
included as dependencies for {bzl:obj}`py_test` targets.

Setting the directive to `false` reverts to the pre-1.9.0 behavior.

For example, given this directory tree (not shown: intermediary `BUILD.bazel`
files)

```
./
├── conftest.py
└── one/
├── conftest.py
└── two/
├── conftest.py
└── three/
├── BUILD.bazel
├── conftest.py
└── my_test.py
```

Gazelle will generate this target for `foo_test.py` by default:

```starlark
py_test(
name = "foo_test",
srcs = ["foo_test.py"],
deps = [
":conftest",
"//:conftest",
"//one:conftest",
"//one/two:conftest",
],
)
```

But when `python_include_ancestor_conftest` is `false`, only the sibling
`:conftest` target will be included as a dependency:

:::{tip}
The [`include_pytest_conftest` annotation](annotation-include-pytest-conftest)
controls whether the sibling `:conftest` target is added to {bzl:obj}`py_test`
target dependency list.
:::

```starlark
# gazelle:python_include_ancestor_conftest false
py_test(
name = "foo_test",
srcs = ["foo_test.py"],
deps = [
":conftest",
],
)
```
7 changes: 7 additions & 0 deletions gazelle/python/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (py *Configurer) KnownDirectives() []string {
pythonconfig.ExperimentalAllowRelativeImports,
pythonconfig.GenerateProto,
pythonconfig.PythonResolveSiblingImports,
pythonconfig.PythonIncludeAncestorConftest,
}
}

Expand Down Expand Up @@ -261,6 +262,12 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
log.Fatal(err)
}
config.SetResolveSiblingImports(v)
case pythonconfig.PythonIncludeAncestorConftest:
v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
if err != nil {
log.Fatal(err)
}
config.SetIncludeAncestorConftest(v)
Comment on lines +265 to +270
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The directive is correctly parsed and stored in the configuration here. However, the logic to respect this setting during rule generation seems to be missing from gazelle/python/generate.go. Specifically, the loop that adds ancestor conftest paths (around line 532 in generate.go) should be conditioned on cfg.IncludeAncestorConftest(). Without this change, the directive will have no effect on the generated deps.

}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("@rules_python//python:defs.bzl", "py_library")

py_library(
name = "conftest",
testonly = True,
srcs = ["conftest.py"],
visibility = ["//:__subpackages__"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Directive: `python_include_ancestor_conftest`

This test case asserts that the `# gazelle:python_include_ancestor_conftest`
directive correctly includes or excludes ancestor contest targets in `py_test`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Typo: "contest" should be "conftest".

target dependencies.

The test also asserts that the directive can be applied at any level and that
child levels will inherit the value:

+ The root level does not set the directive (it defaults to True).
+ The next level, `one/`, inherits that value.
+ The next level, `one/two/`, sets the directive to False and thus the
`py_test` target only includes the sibling `:conftest` target.
+ The final level, `one/two/three`, sets the directive back to True and thus
the `py_test` target includes a total of 4 `conftest` targets.

See [Issue #3595](https://github.com/bazel-contrib/rules_python/issues/3595).
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

py_library(
name = "conftest",
testonly = True,
srcs = ["conftest.py"],
visibility = ["//:__subpackages__"],
)

py_test(
name = "my_test",
srcs = ["my_test.py"],
deps = [
":conftest",
"//:conftest",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:python_include_ancestor_conftest false
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_include_ancestor_conftest false

py_library(
name = "conftest",
testonly = True,
srcs = ["conftest.py"],
visibility = ["//:__subpackages__"],
)

py_test(
name = "my_test",
srcs = ["my_test.py"],
deps = [":conftest"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:python_include_ancestor_conftest true
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_include_ancestor_conftest true

py_library(
name = "conftest",
testonly = True,
srcs = ["conftest.py"],
visibility = ["//:__subpackages__"],
)

py_test(
name = "my_test",
srcs = ["my_test.py"],
deps = [
":conftest",
"//:conftest",
"//one:conftest",
"//one/two:conftest",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
expect:
exit_code: 0
22 changes: 22 additions & 0 deletions gazelle/pythonconfig/pythonconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ const (
// like "import a" can be resolved to sibling modules. When disabled, they
// can only be resolved as an absolute import.
PythonResolveSiblingImports = "python_resolve_sibling_imports"
// PythonIncludeAncestorConftest represents the directive that controls
// whether ancestor conftest.py files are added as dependencies to py_test
// targets. When enabled (the default), ancestor conftest.py files are
// included as deps.
// See also https://github.com/bazel-contrib/rules_python/pull/3498, which
// fixed previous behavior that was incorrectly _not_ adding the files and
// https://github.com/bazel-contrib/rules_python/issues/3595 which requested
// that the behavior be configurable.
PythonIncludeAncestorConftest = "python_include_ancestor_conftest"
)

// GenerationModeType represents one of the generation modes for the Python
Expand Down Expand Up @@ -209,6 +218,7 @@ type Config struct {
generatePyiSrcs bool
generateProto bool
resolveSiblingImports bool
includeAncestorConftest bool
}

type LabelNormalizationType int
Expand Down Expand Up @@ -250,6 +260,7 @@ func New(
generatePyiSrcs: false,
generateProto: false,
resolveSiblingImports: false,
includeAncestorConftest: true,
}
}

Expand Down Expand Up @@ -288,6 +299,7 @@ func (c *Config) NewChild() *Config {
generatePyiSrcs: c.generatePyiSrcs,
generateProto: c.generateProto,
resolveSiblingImports: c.resolveSiblingImports,
includeAncestorConftest: c.includeAncestorConftest,
}
}

Expand Down Expand Up @@ -629,6 +641,16 @@ func (c *Config) ResolveSiblingImports() bool {
return c.resolveSiblingImports
}

// SetIncludeAncestorConftest sets whether ancestor conftest files are added to py_test targets.
func (c *Config) SetIncludeAncestorConftest(includeAncestorConftest bool) {
c.includeAncestorConftest = includeAncestorConftest
}

// IncludeAncestorConftest returns whether ancestor conftest files are added to py_test targets.
func (c *Config) IncludeAncestorConftest() bool {
return c.includeAncestorConftest
}

// 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)
Expand Down