fix: expose private EXECUTABLE_ATTRS defaults to external rule builders#3919
Conversation
build_data_writer, debugger_if_target_config, and uncachable_version_file are added as implicit attrs to all py_binary/py_test targets via EXECUTABLE_ATTRS in 1.9.0. When an external package uses py_binary_rule_builder() to construct a custom rule, Bazel checks visibility of those attr defaults from the external package. These targets inherited the package default (//python/private:__subpackages__) making them inaccessible, causing a visibility error at analysis time. Set visibility = ["//visibility:public"] on the three targets.
a3f1a05 to
dd3f1f5
Compare
There was a problem hiding this comment.
Code Review
This pull request resolves a visibility issue where implicit attribute defaults used by py_binary_rule_builder() and py_test_rule_builder() were private to rules_python, causing analysis failures for external modules. The changes expose these targets as public and add regression tests. The review feedback suggests forwarding the visibility parameter to uncachable_version_file_impl to prevent failures when stamping is enabled, and cleaning up placeholder TODO(pr-number) comments in the test files.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| 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") |
There was a problem hiding this comment.
When --stamp is enabled, the uncachable_version_file alias resolves to :uncachable_version_file_impl. Since :uncachable_version_file_impl is a rule target and remains private (inheriting the package's default visibility), external rule builders will fail with a visibility error during analysis when stamping is active. Forward the visibility parameter to uncachable_version_file_impl as well to ensure it is visible to external consumers.
| 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") | |
| 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", | |
| visibility = visibility, | |
| ) |
| @@ -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: | |||
There was a problem hiding this comment.
Avoid leaving unresolved TODO(pr-number) placeholders in comments. We can describe the regression coverage more generally without referencing a specific PR number.
| Regression coverage for https://github.com/bazel-contrib/rules_python/pull/3919: | |
| Regression coverage for the executables rule builder API visibility issue: |
| ], | ||
| ) | ||
|
|
||
| # Regression test for https://github.com/bazel-contrib/rules_python/pull/3919: py_binary_rule_builder()'s implicit |
There was a problem hiding this comment.
Avoid leaving unresolved TODO(pr-number) placeholders in comments. We can simplify the comment to refer to the regression test generally.
| # Regression test for https://github.com/bazel-contrib/rules_python/pull/3919: py_binary_rule_builder()'s implicit | |
| # Regression test: py_binary_rule_builder()'s implicit |
Why
create_executable_rule_builder()(used bypy_binary_rule_builder()/py_test_rule_builder(), the publicpython/api/executables.bzlAPI) bundlesthree implicit attrs whose defaults point at private targets under
//python/private:build_data_writer,debugger_if_target_config, anduncachable_version_file. These targets had no explicit visibility, so theyinherited the package's
default_visibility(//:__subpackages__), whichonly covers packages inside the rules_python repo itself.
Because the builder API is designed to let external modules call
rule()themselves (via
builder.build()), Bazel checks visibility of these attrdefaults from the calling module's package, not from rules_python's. Any
external repo constructing a rule via
py_binary_rule_builder()/py_test_rule_builder()therefore fails at analysis time:(and the same for
debugger_if_target_configanduncachable_version_file).This has been broken since the builder API's introduction; there's prior art
in this same file for exactly this situation —
stage1_bootstrap_template,stage2_bootstrap_template,site_init_template,bootstrap_template, andcurrent_interpreter_executableare all public for the identical reason(implicit deps of
py_runtime), each with a# Not actually public. Only public because it's an implicit dependency of ...comment.
Before / after
Before: any external module using
py_binary_rule_builder()/py_test_rule_builder()fails at analysis time with the visibility errorabove.
After: it analyzes and builds successfully.
Changes
python/private/BUILD.bazel: markbuild_data_writer,debugger_if_target_config,uncachable_version_file(plusemptyandsentinel, which the alias chains resolve through)//visibility:public,matching the existing comment convention for implicit-dependency targets.
python/private/uncachable_version_file.bzl:define_uncachable_version_file()is a macro, not a
rule(), so it doesn't get avisibilitykwarg for free —added one and forwarded it to the underlying
native.alias().examples/bzlmod/other_module/other_module/rule_builder/: new fixture — aminimal custom
py_binarybuilt viapy_binary_rule_builder()fromother_module(a separate bzlmod module, simulating a real externalconsumer). Verified this fails on the parent commit and passes with the fix.
examples/bzlmod/tests/other_module/BUILD.bazel:build_testwiring thefixture into
other_module's existing CI-covered//...build.Manually verified in an isolated minimal repro (bare BCR,
git_overridepointing at this branch) as well as against the
examples/bzlmodexampleincluded in this PR.