fix: make first default output the executable again (#2010)
This fixes a small change in behavior identified by some Google
regression tests. When precompiling was introduced, the target's
executable was no longer the first file in the default outputs depset.
While that behavior isn't a strong contract, it is the convention with
many other rules, and the existing behavior for Bazel 7+.
To fix, put the executable as the first value in the default outputs
list. Also adds a test for this behavior.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b259e51..6ae6508 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,7 @@
`interpreter_version_info` arg.
* (bzlmod) Correctly pass `isolated`, `quiet` and `timeout` values to `whl_library`
and drop the defaults from the lock file.
+* (rules) The first element of the default outputs is now the executable again.
### Removed
* (pip): Removes the `entrypoint` macro that was replaced by `py_console_script_binary` in 0.26.0.
diff --git a/python/private/common/py_executable.bzl b/python/private/common/py_executable.bzl
index 6b75b41..2b4a939 100644
--- a/python/private/common/py_executable.bzl
+++ b/python/private/common/py_executable.bzl
@@ -166,9 +166,10 @@
main_py = precompile_result.py_to_pyc_map[main_py]
direct_pyc_files = depset(precompile_result.pyc_files)
- default_outputs = precompile_result.keep_srcs + precompile_result.pyc_files
executable = _declare_executable_file(ctx)
- default_outputs.append(executable)
+ default_outputs = [executable]
+ default_outputs.extend(precompile_result.keep_srcs)
+ default_outputs.extend(precompile_result.pyc_files)
imports = collect_imports(ctx, semantics)
diff --git a/tests/base_rules/py_executable_base_tests.bzl b/tests/base_rules/py_executable_base_tests.bzl
index c96ec4e..eb1a1b6 100644
--- a/tests/base_rules/py_executable_base_tests.bzl
+++ b/tests/base_rules/py_executable_base_tests.bzl
@@ -18,6 +18,7 @@
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
load("@rules_testing//lib:truth.bzl", "matching")
load("@rules_testing//lib:util.bzl", rt_util = "util")
+load("//python/private:util.bzl", "IS_BAZEL_7_OR_HIGHER") # buildifier: disable=bzl-visibility
load("//tests/base_rules:base_tests.bzl", "create_base_tests")
load("//tests/base_rules:util.bzl", "WINDOWS_ATTR", pt_util = "util")
load("//tests/support:support.bzl", "LINUX_X86_64", "WINDOWS_X86_64")
@@ -297,6 +298,16 @@
"{package}/{test_name}_subject.py",
])
+ if IS_BAZEL_7_OR_HIGHER:
+ # As of Bazel 7, the first default output is the executable, so
+ # verify that is the case. rules_testing
+ # DepsetFileSubject.contains_exactly doesn't provide an in_order()
+ # call, nor access to the underlying depset, so we have to do things
+ # manually.
+ first_default_output = target[DefaultInfo].files.to_list()[0]
+ executable = target[DefaultInfo].files_to_run.executable
+ env.expect.that_file(first_default_output).equals(executable)
+
def _test_name_cannot_end_in_py(name, config):
# Bazel 5 will crash with a Java stacktrace when the native Python
# rules have an error.