Skip to content

Commit 33cb431

Browse files
authored
fix: make plain zipapp work with bootstrap=script (#2598)
The `__main__.py` template (zip_main_template.py) was using the wrong path when creating the interpreter symlinks. It as computing it correctly, just the wrong variable was used in the symlink() call. To fix, pass the correct variable. Also adds a test to check that it's runnable. Fixes #2596
1 parent 466da1d commit 33cb431

File tree

5 files changed

+111
-12
lines changed

5 files changed

+111
-12
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ Unreleased changes template.
6262

6363
{#v0-0-0-fixed}
6464
### Fixed
65+
* (rules) `python_zip_file` output with `--bootstrap_impl=script` works again
66+
([#2596](https://github.com/bazelbuild/rules_python/issues/2596)).
6567
* (docs) Using `python_version` attribute for specifying python versions introduced in `v1.1.0`
6668
* (gazelle) Providing multiple input requirements files to `gazelle_python_manifest` now works correctly.
6769
* (pypi) Handle trailing slashes in pip index URLs in environment variables,

python/private/zip_main_template.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,10 @@ def main():
286286
# The bin/ directory may not exist if it is empty.
287287
os.makedirs(os.path.dirname(python_program), exist_ok=True)
288288
try:
289-
os.symlink(_PYTHON_BINARY_ACTUAL, python_program)
289+
os.symlink(symlink_to, python_program)
290290
except OSError as e:
291291
raise Exception(
292-
f"Unable to create venv python interpreter symlink: {python_program} -> {PYTHON_BINARY_ACTUAL}"
292+
f"Unable to create venv python interpreter symlink: {python_program} -> {symlink_to}"
293293
) from e
294294

295295
# Some older Python versions on macOS (namely Python 3.7) may unintentionally

tests/bootstrap_impls/BUILD.bazel

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
load("@rules_shell//shell:sh_test.bzl", "sh_test")
2+
13
# Copyright 2023 The Bazel Authors. All rights reserved.
24
#
35
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -11,10 +13,40 @@
1113
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1214
# See the License for the specific language governing permissions and
1315
# limitations under the License.
14-
load("//tests/support:sh_py_run_test.bzl", "py_reconfig_test", "sh_py_run_test")
16+
load("//tests/support:sh_py_run_test.bzl", "py_reconfig_binary", "py_reconfig_test", "sh_py_run_test")
1517
load("//tests/support:support.bzl", "SUPPORTS_BOOTSTRAP_SCRIPT")
1618
load(":venv_relative_path_tests.bzl", "relative_path_test_suite")
1719

20+
py_reconfig_binary(
21+
name = "bootstrap_script_zipapp_bin",
22+
srcs = ["bin.py"],
23+
bootstrap_impl = "script",
24+
# Force it to not be self-executable
25+
build_python_zip = "no",
26+
main = "bin.py",
27+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
28+
)
29+
30+
filegroup(
31+
name = "bootstrap_script_zipapp_zip",
32+
testonly = 1,
33+
srcs = [":bootstrap_script_zipapp_bin"],
34+
output_group = "python_zip_file",
35+
)
36+
37+
sh_test(
38+
name = "bootstrap_script_zipapp_test",
39+
srcs = ["bootstrap_script_zipapp_test.sh"],
40+
data = [":bootstrap_script_zipapp_zip"],
41+
env = {
42+
"ZIP_RLOCATION": "$(rlocationpaths :bootstrap_script_zipapp_zip)".format(),
43+
},
44+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
45+
deps = [
46+
"@bazel_tools//tools/bash/runfiles",
47+
],
48+
)
49+
1850
sh_py_run_test(
1951
name = "run_binary_zip_no_test",
2052
build_python_zip = "no",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# --- begin runfiles.bash initialization v3 ---
16+
# Copy-pasted from the Bazel Bash runfiles library v3.
17+
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
18+
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
19+
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
20+
source "$0.runfiles/$f" 2>/dev/null || \
21+
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
22+
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
23+
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
24+
# --- end runfiles.bash initialization v3 ---
25+
set +e
26+
27+
bin=$(rlocation $ZIP_RLOCATION)
28+
if [[ -z "$bin" ]]; then
29+
echo "Unable to locate test binary: $ZIP_RLOCATION"
30+
exit 1
31+
fi
32+
set -x
33+
actual=$(python3 $bin)
34+
35+
# How we detect if a zip file was executed from depends on which bootstrap
36+
# is used.
37+
# bootstrap_impl=script outputs RULES_PYTHON_ZIP_DIR=<somepath>
38+
# bootstrap_impl=system_python outputs file:.*Bazel.runfiles
39+
expected_pattern="Hello"
40+
if ! (echo "$actual" | grep "$expected_pattern" ) >/dev/null; then
41+
echo "Test case failed: $1"
42+
echo "expected output to match: $expected_pattern"
43+
echo "but got:\n$actual"
44+
exit 1
45+
fi
46+
47+
exit 0

tests/support/sh_py_run_test.bzl

+27-9
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def _py_reconfig_impl(ctx):
8686
default_info.default_runfiles,
8787
),
8888
),
89+
ctx.attr.target[OutputGroupInfo],
8990
# Inherit the expanded environment from the inner target.
9091
ctx.attr.target[RunEnvironmentInfo],
9192
]
@@ -120,31 +121,48 @@ _py_reconfig_binary = _make_reconfig_rule(executable = True)
120121

121122
_py_reconfig_test = _make_reconfig_rule(test = True)
122123

123-
def py_reconfig_test(*, name, **kwargs):
124-
"""Create a py_test with customized build settings for testing.
125-
126-
Args:
127-
name: str, name of teset target.
128-
**kwargs: kwargs to pass along to _py_reconfig_test and py_test.
129-
"""
124+
def _py_reconfig_executable(*, name, py_reconfig_rule, py_inner_rule, **kwargs):
130125
reconfig_kwargs = {}
131126
reconfig_kwargs["bootstrap_impl"] = kwargs.pop("bootstrap_impl", None)
132127
reconfig_kwargs["extra_toolchains"] = kwargs.pop("extra_toolchains", None)
133128
reconfig_kwargs["python_version"] = kwargs.pop("python_version", None)
134129
reconfig_kwargs["target_compatible_with"] = kwargs.get("target_compatible_with")
130+
reconfig_kwargs["build_python_zip"] = kwargs.pop("build_python_zip", None)
135131

136132
inner_name = "_{}_inner".format(name)
137-
_py_reconfig_test(
133+
py_reconfig_rule(
138134
name = name,
139135
target = inner_name,
140136
**reconfig_kwargs
141137
)
142-
py_test(
138+
py_inner_rule(
143139
name = inner_name,
144140
tags = ["manual"],
145141
**kwargs
146142
)
147143

144+
def py_reconfig_test(*, name, **kwargs):
145+
"""Create a py_test with customized build settings for testing.
146+
147+
Args:
148+
name: str, name of teset target.
149+
**kwargs: kwargs to pass along to _py_reconfig_test and py_test.
150+
"""
151+
_py_reconfig_executable(
152+
name = name,
153+
py_reconfig_rule = _py_reconfig_test,
154+
py_inner_rule = py_test,
155+
**kwargs
156+
)
157+
158+
def py_reconfig_binary(*, name, **kwargs):
159+
_py_reconfig_executable(
160+
name = name,
161+
py_reconfig_rule = _py_reconfig_binary,
162+
py_inner_rule = py_binary,
163+
**kwargs
164+
)
165+
148166
def sh_py_run_test(*, name, sh_src, py_src, **kwargs):
149167
"""Run a py_binary within a sh_test.
150168

0 commit comments

Comments
 (0)