From 539f391fcb1b11d73902138255a37c6fff0bb962 Mon Sep 17 00:00:00 2001 From: Douglas Thor Date: Sat, 14 Feb 2026 05:25:38 +0000 Subject: [PATCH 1/4] pythonconfig.go --- gazelle/pythonconfig/pythonconfig.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/gazelle/pythonconfig/pythonconfig.go b/gazelle/pythonconfig/pythonconfig.go index a1271af3be..17db9aae0d 100644 --- a/gazelle/pythonconfig/pythonconfig.go +++ b/gazelle/pythonconfig/pythonconfig.go @@ -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 @@ -209,6 +218,7 @@ type Config struct { generatePyiSrcs bool generateProto bool resolveSiblingImports bool + includeAncestorConftest bool } type LabelNormalizationType int @@ -250,6 +260,7 @@ func New( generatePyiSrcs: false, generateProto: false, resolveSiblingImports: false, + includeAncestorConftest: true, } } @@ -288,6 +299,7 @@ func (c *Config) NewChild() *Config { generatePyiSrcs: c.generatePyiSrcs, generateProto: c.generateProto, resolveSiblingImports: c.resolveSiblingImports, + includeAncestorConftest: c.includeAncestorConftest, } } @@ -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) From ff30dd0f1e8d75d501b0cf9d981dfca9241ea908 Mon Sep 17 00:00:00 2001 From: Douglas Thor Date: Sat, 14 Feb 2026 05:55:10 +0000 Subject: [PATCH 2/4] docs --- gazelle/docs/annotations.md | 1 + gazelle/docs/directives.md | 69 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/gazelle/docs/annotations.md b/gazelle/docs/annotations.md index 728027ffda..b6eb96d2cd 100644 --- a/gazelle/docs/annotations.md +++ b/gazelle/docs/annotations.md @@ -116,6 +116,7 @@ deps = [ ``` +(annotation-include-pytest-conftest)= ## `include_pytest_conftest` :::{versionadded} 1.6.0 diff --git a/gazelle/docs/directives.md b/gazelle/docs/directives.md index c7936d539d..1f990d7f6d 100644 --- a/gazelle/docs/directives.md +++ b/gazelle/docs/directives.md @@ -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` @@ -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", + ], +) +``` From 1091a17d1b3d4602becc0850e3fde0a4530e772e Mon Sep 17 00:00:00 2001 From: Douglas Thor Date: Sat, 14 Feb 2026 06:14:40 +0000 Subject: [PATCH 3/4] tests --- .../BUILD.in | 0 .../BUILD.out | 8 +++++++ .../MODULE.bazel | 0 .../README.md | 17 +++++++++++++++ .../WORKSPACE | 0 .../conftest.py | 0 .../one/BUILD.in | 0 .../one/BUILD.out | 17 +++++++++++++++ .../one/conftest.py | 0 .../one/my_test.py | 0 .../one/two/BUILD.in | 1 + .../one/two/BUILD.out | 16 ++++++++++++++ .../one/two/conftest.py | 0 .../one/two/my_test.py | 0 .../one/two/three/BUILD.in | 1 + .../one/two/three/BUILD.out | 21 +++++++++++++++++++ .../one/two/three/conftest.py | 0 .../one/two/three/my_test.py | 0 .../test.yaml | 3 +++ 19 files changed, 84 insertions(+) create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/BUILD.in create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/BUILD.out create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/MODULE.bazel create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/README.md create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/WORKSPACE create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/conftest.py create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/BUILD.in create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/BUILD.out create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/conftest.py create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/my_test.py create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.in create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.out create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/conftest.py create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/my_test.py create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.in create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.out create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/conftest.py create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/my_test.py create mode 100644 gazelle/python/testdata/directive_python_include_ancestor_conftest/test.yaml diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/BUILD.in b/gazelle/python/testdata/directive_python_include_ancestor_conftest/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/BUILD.out b/gazelle/python/testdata/directive_python_include_ancestor_conftest/BUILD.out new file mode 100644 index 0000000000..c7adad8336 --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/BUILD.out @@ -0,0 +1,8 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "conftest", + testonly = True, + srcs = ["conftest.py"], + visibility = ["//:__subpackages__"], +) diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/MODULE.bazel b/gazelle/python/testdata/directive_python_include_ancestor_conftest/MODULE.bazel new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/README.md b/gazelle/python/testdata/directive_python_include_ancestor_conftest/README.md new file mode 100644 index 0000000000..8a1ab092ee --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/README.md @@ -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` +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). diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/WORKSPACE b/gazelle/python/testdata/directive_python_include_ancestor_conftest/WORKSPACE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/conftest.py b/gazelle/python/testdata/directive_python_include_ancestor_conftest/conftest.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/BUILD.in b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/BUILD.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/BUILD.out b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/BUILD.out new file mode 100644 index 0000000000..9d0405e92f --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/BUILD.out @@ -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", + ], +) diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/conftest.py b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/conftest.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/my_test.py b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/my_test.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.in b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.in new file mode 100644 index 0000000000..3805e248e2 --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.in @@ -0,0 +1 @@ +# gazelle:python_include_ancestor_conftest false diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.out b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.out new file mode 100644 index 0000000000..edb91a38f5 --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/BUILD.out @@ -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"], +) diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/conftest.py b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/conftest.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/my_test.py b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/my_test.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.in b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.in new file mode 100644 index 0000000000..987cd7dc20 --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.in @@ -0,0 +1 @@ +# gazelle:python_include_ancestor_conftest true diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.out b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.out new file mode 100644 index 0000000000..605aa00f88 --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/BUILD.out @@ -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", + ], +) diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/conftest.py b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/conftest.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/my_test.py b/gazelle/python/testdata/directive_python_include_ancestor_conftest/one/two/three/my_test.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/directive_python_include_ancestor_conftest/test.yaml b/gazelle/python/testdata/directive_python_include_ancestor_conftest/test.yaml new file mode 100644 index 0000000000..36dd656b39 --- /dev/null +++ b/gazelle/python/testdata/directive_python_include_ancestor_conftest/test.yaml @@ -0,0 +1,3 @@ +--- +expect: + exit_code: 0 From 3346dc98d9092433a4d5811c584889e12318d783 Mon Sep 17 00:00:00 2001 From: Douglas Thor Date: Sat, 14 Feb 2026 06:25:12 +0000 Subject: [PATCH 4/4] configure.go --- gazelle/python/configure.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gazelle/python/configure.go b/gazelle/python/configure.go index 88b15e91eb..1fe95a1683 100644 --- a/gazelle/python/configure.go +++ b/gazelle/python/configure.go @@ -74,6 +74,7 @@ func (py *Configurer) KnownDirectives() []string { pythonconfig.ExperimentalAllowRelativeImports, pythonconfig.GenerateProto, pythonconfig.PythonResolveSiblingImports, + pythonconfig.PythonIncludeAncestorConftest, } } @@ -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) } }