fix: make executable implicit defaults public so other modules can use rule builders (#3919) (#3970)
Backports #3919 to `release/1.9` (requested by @aignas / @rickeylev).
`create_executable_rule_builder()` (used by `py_binary_rule_builder()` /
`py_test_rule_builder()`, the public `python/api/executables.bzl` API)
bundles three implicit attrs whose defaults point at private targets
under
`//python/private`. These had no explicit visibility, so any external
module constructing a rule via the builder API failed at analysis time
with a visibility error.
Commits:
- `fix: make executable implicit defaults public ... (#3919)` —
cherry-pick
of `19ffd215`, unchanged from main.
- `Release 1.9.2: Update changelog` — finalizes the changelog entry
(`release/1.9` doesn't carry the `news/` fragment infra main uses, so
the
fragment is folded into `CHANGELOG.md` directly, matching
#3775/#3776/#3819).
Fixes #3919 on `1.9.x`.
---------
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/.bazelrc.deleted_packages b/.bazelrc.deleted_packages
index 2d8a807..2cd1090 100644
--- a/.bazelrc.deleted_packages
+++ b/.bazelrc.deleted_packages
@@ -7,6 +7,7 @@
common --deleted_packages=examples/bzlmod/libs/my_lib
common --deleted_packages=examples/bzlmod/other_module
common --deleted_packages=examples/bzlmod/other_module/other_module/pkg
+common --deleted_packages=examples/bzlmod/other_module/other_module/rule_builder
common --deleted_packages=examples/bzlmod/patches
common --deleted_packages=examples/bzlmod/runfiles
common --deleted_packages=examples/bzlmod/tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80412cf..0a73505 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,19 @@
END_UNRELEASED_TEMPLATE
-->
+{#v1-9-2}
+## [1.9.2] - 2026-07-28
+
+[1.9.2]: https://github.com/bazel-contrib/rules_python/releases/tag/1.9.2
+
+{#v1-9-2-fixed}
+### Fixed
+* (executables) `py_binary_rule_builder()` / `py_test_rule_builder()` (from
+ `python/api/executables.bzl`) no longer fail at analysis time with a
+ visibility error when used to construct a custom rule from an external
+ module. Fixes
+ ([#3919](https://github.com/bazel-contrib/rules_python/pull/3919)).
+
{#v1-9-1}
## [1.9.1] - 2026-05-14
diff --git a/examples/bzlmod/other_module/other_module/rule_builder/BUILD.bazel b/examples/bzlmod/other_module/other_module/rule_builder/BUILD.bazel
new file mode 100644
index 0000000..40f66b3
--- /dev/null
+++ b/examples/bzlmod/other_module/other_module/rule_builder/BUILD.bazel
@@ -0,0 +1,13 @@
+load(":rule.bzl", "custom_py_binary")
+
+# This target's mere existence is the regression test: analyzing it exercises
+# the implicit attr defaults (build_data_writer, debugger_if_target_config,
+# uncachable_version_file) that py_binary_rule_builder() bundles from
+# rules_python's private package, from a rule() call made in this external
+# module.
+custom_py_binary(
+ name = "app",
+ srcs = ["app.py"],
+ main = "app.py",
+ visibility = ["//visibility:public"],
+)
diff --git a/examples/bzlmod/other_module/other_module/rule_builder/app.py b/examples/bzlmod/other_module/other_module/rule_builder/app.py
new file mode 100644
index 0000000..db51494
--- /dev/null
+++ b/examples/bzlmod/other_module/other_module/rule_builder/app.py
@@ -0,0 +1 @@
+print("hello from a rule built via py_binary_rule_builder()")
diff --git a/examples/bzlmod/other_module/other_module/rule_builder/rule.bzl b/examples/bzlmod/other_module/other_module/rule_builder/rule.bzl
new file mode 100644
index 0000000..7b15059
--- /dev/null
+++ b/examples/bzlmod/other_module/other_module/rule_builder/rule.bzl
@@ -0,0 +1,17 @@
+"""A minimal custom py_binary built via the executables rule builder API.
+
+Regression coverage for https://github.com/bazel-contrib/rules_python/pull/3919:
+py_binary_rule_builder()'s implicit attr defaults (build_data_writer,
+debugger_if_target_config, uncachable_version_file) previously had no
+visibility outside of rules_python, so any external module (like this one)
+constructing a rule via the builder failed at analysis time with a
+visibility error.
+"""
+
+load("@rules_python//python/api:executables.bzl", "executables")
+
+def _make_rule():
+ builder = executables.py_binary_rule_builder()
+ return builder.build()
+
+custom_py_binary = _make_rule()
diff --git a/examples/bzlmod/tests/other_module/BUILD.bazel b/examples/bzlmod/tests/other_module/BUILD.bazel
index 1bd8a90..906554f 100644
--- a/examples/bzlmod/tests/other_module/BUILD.bazel
+++ b/examples/bzlmod/tests/other_module/BUILD.bazel
@@ -12,3 +12,14 @@
"@our_other_module//other_module/pkg:bin",
],
)
+
+# Regression test for https://github.com/bazel-contrib/rules_python/pull/3919: py_binary_rule_builder()'s implicit
+# attr defaults previously had no visibility outside of rules_python, so
+# building this target (defined via the builder from an external module)
+# failed at analysis time with a visibility error.
+build_test(
+ name = "other_module_rule_builder_build_test",
+ targets = [
+ "@our_other_module//other_module/rule_builder:app",
+ ],
+)
diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel
index 70f7f86..2e2ed92 100644
--- a/python/private/BUILD.bazel
+++ b/python/private/BUILD.bazel
@@ -109,6 +109,9 @@
"@platforms//os:windows": ":build_data_writer.ps1",
"//conditions:default": ":build_data_writer.sh",
}),
+ # Not actually public. Only public because it's an implicit dependency of
+ # rules built via py_binary_rule_builder() / py_test_rule_builder().
+ visibility = ["//visibility:public"],
)
bzl_library(
@@ -700,6 +703,9 @@
define_uncachable_version_file(
name = "uncachable_version_file",
+ # Not actually public. Only public because it's an implicit dependency of
+ # rules built via py_binary_rule_builder() / py_test_rule_builder().
+ visibility = ["//visibility:public"],
)
bzl_library(
@@ -837,6 +843,9 @@
":is_bazel_config_mode_target": "//python/config_settings:debugger",
"//conditions:default": "//python/private:empty",
}),
+ # Not actually public. Only public because it's an implicit dependency of
+ # rules built via py_binary_rule_builder() / py_test_rule_builder().
+ visibility = ["//visibility:public"],
)
bazel_config_mode(name = "bazel_config_mode")
@@ -888,8 +897,16 @@
py_library(
name = "empty",
+ # Not actually public. Only public because it's the resolved default of
+ # debugger_if_target_config, an implicit dependency of rules built via
+ # py_binary_rule_builder() / py_test_rule_builder().
+ visibility = ["//visibility:public"],
)
sentinel(
name = "sentinel",
+ # Not actually public. Only public because it's the resolved default of
+ # uncachable_version_file, an implicit dependency of rules built via
+ # py_binary_rule_builder() / py_test_rule_builder().
+ visibility = ["//visibility:public"],
)
diff --git a/python/private/uncachable_version_file.bzl b/python/private/uncachable_version_file.bzl
index 9b1d65a..0f58df8 100644
--- a/python/private/uncachable_version_file.bzl
+++ b/python/private/uncachable_version_file.bzl
@@ -28,12 +28,13 @@
implementation = _uncachable_version_file_impl,
)
-def define_uncachable_version_file(name):
+def define_uncachable_version_file(name, visibility = None):
native.alias(
name = name,
actual = select({
":stamp_detect": ":uncachable_version_file_impl",
"//conditions:default": ":sentinel",
}),
+ visibility = visibility,
)
uncachable_version_file(name = "uncachable_version_file_impl")