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")