Skip to content

Commit 68f752e

Browse files
authored
fix: make mac zip builds work (#2052)
Macs have an older version of `mktemp`, one that doesn't support the `--suffix` arg. This caused the combination of Macs and `--build_python_zip --bootstrap_impl=script` to fail. To fix, remove the `--suffix` arg. As far as I can tell, the suffix string, "Bazel.runfiles_", is just informational, so this is fine to remove. Also adds tests to verify that a binary runs with/without zip and for the script bootstrap. Fixes #2030
1 parent 7093d91 commit 68f752e

8 files changed

+316
-3
lines changed

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ A brief description of the categories of changes:
3131
* (rules) Signals are properly received when using {obj}`--bootstrap_impl=script`
3232
(for non-zip builds).
3333
([#2043](https://github.com/bazelbuild/rules_python/issues/2043))
34-
* (rules) Fixes python builds when the `--build_python_zip` is set to `false` on Windows. See [#1840](https://github.com/bazelbuild/rules_python/issues/1840).
34+
* (rules) Fixes Python builds when the `--build_python_zip` is set to `false` on
35+
Windows. See [#1840](https://github.com/bazelbuild/rules_python/issues/1840).
36+
* (rules) Fixes Mac + `--build_python_zip` + {obj}`--bootstrap_impl=script`
37+
([#2030](https://github.com/bazelbuild/rules_python/issues/2030)).
3538
* (pip) Fixed pypi parse_simpleapi_html function for feeds with package metadata
3639
containing ">" sign
3740

python/private/stage1_bootstrap_template.sh

+9-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ PYTHON_BINARY='%python_binary%'
1616
IS_ZIPFILE="%is_zipfile%"
1717

1818
if [[ "$IS_ZIPFILE" == "1" ]]; then
19-
zip_dir=$(mktemp -d --suffix Bazel.runfiles_)
19+
# NOTE: Macs have an old version of mktemp, so we must use only the
20+
# minimal functionality of it.
21+
zip_dir=$(mktemp -d)
2022

2123
if [[ -n "$zip_dir" && -z "${RULES_PYTHON_BOOTSTRAP_VERBOSE:-}" ]]; then
2224
trap 'rm -fr "$zip_dir"' EXIT
@@ -27,7 +29,7 @@ if [[ "$IS_ZIPFILE" == "1" ]]; then
2729
# The alternative requires having to copy ourselves elsewhere with the prelude
2830
# stripped (because zip can't extract from a stream). We avoid that because
2931
# it's wasteful.
30-
( unzip -q -d "$zip_dir" "$0" 2>/dev/null || /bin/true )
32+
( unzip -q -d "$zip_dir" "$0" 2>/dev/null || true )
3133

3234
RUNFILES_DIR="$zip_dir/runfiles"
3335
if [[ ! -d "$RUNFILES_DIR" ]]; then
@@ -105,6 +107,11 @@ declare -a interpreter_args
105107
# NOTE: Only works for 3.11+
106108
interpreter_env+=("PYTHONSAFEPATH=1")
107109

110+
if [[ "$IS_ZIPFILE" == "1" ]]; then
111+
interpreter_args+=("-XRULES_PYTHON_ZIP_DIR=$zip_dir")
112+
fi
113+
114+
108115
export RUNFILES_DIR
109116

110117
command=(

tests/base_rules/BUILD.bazel

+40
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,43 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
15+
load("//python/private:util.bzl", "IS_BAZEL_7_OR_HIGHER") # buildifier: disable=bzl-visibility
16+
load("//tests/support:sh_py_run_test.bzl", "sh_py_run_test")
17+
18+
_SUPPORTS_BOOTSTRAP_SCRIPT = select({
19+
"@platforms//os:windows": ["@platforms//:incompatible"],
20+
"//conditions:default": [],
21+
}) if IS_BAZEL_7_OR_HIGHER else ["@platforms//:incompatible"]
22+
23+
sh_py_run_test(
24+
name = "run_binary_zip_no_test",
25+
build_python_zip = "no",
26+
py_src = "bin.py",
27+
sh_src = "run_binary_zip_no_test.sh",
28+
)
29+
30+
sh_py_run_test(
31+
name = "run_binary_zip_yes_test",
32+
build_python_zip = "yes",
33+
py_src = "bin.py",
34+
sh_src = "run_binary_zip_yes_test.sh",
35+
)
36+
37+
sh_py_run_test(
38+
name = "run_binary_bootstrap_script_zip_yes_test",
39+
bootstrap_impl = "script",
40+
build_python_zip = "yes",
41+
py_src = "bin.py",
42+
sh_src = "run_binary_zip_yes_test.sh",
43+
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
44+
)
45+
46+
sh_py_run_test(
47+
name = "run_binary_bootstrap_script_zip_no_test",
48+
bootstrap_impl = "script",
49+
build_python_zip = "no",
50+
py_src = "bin.py",
51+
sh_src = "run_binary_zip_no_test.sh",
52+
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
53+
)

tests/base_rules/bin.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
import sys
16+
17+
print("Hello")
18+
print(
19+
"RULES_PYTHON_ZIP_DIR:{}".format(sys._xoptions.get("RULES_PYTHON_ZIP_DIR", "UNSET"))
20+
)
21+
print("file:", __file__)
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 $BIN_RLOCATION)
28+
if [[ -z "$bin" ]]; then
29+
echo "Unable to locate test binary: $BIN_RLOCATION"
30+
exit 1
31+
fi
32+
actual=$($bin 2>&1)
33+
34+
# How we detect if a zip file was executed from depends on which bootstrap
35+
# is used.
36+
# bootstrap_impl=script outputs RULES_PYTHON_ZIP_DIR=<somepath>
37+
# bootstrap_impl=system_python outputs file:.*Bazel.runfiles
38+
expected_pattern="Hello"
39+
if ! (echo "$actual" | grep "$expected_pattern" ) >/dev/null; then
40+
echo "expected output to match: $expected_pattern"
41+
echo "but got:\n$actual"
42+
exit 1
43+
fi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 $BIN_RLOCATION)
28+
if [[ -z "$bin" ]]; then
29+
echo "Unable to locate test binary: $BIN_RLOCATION"
30+
exit 1
31+
fi
32+
actual=$($bin)
33+
34+
# How we detect if a zip file was executed from depends on which bootstrap
35+
# is used.
36+
# bootstrap_impl=script outputs RULES_PYTHON_ZIP_DIR:<somepath>
37+
# bootstrap_impl=system_python outputs file:.*Bazel.runfiles
38+
expected_pattern="RULES_PYTHON_ZIP_DIR:/\|file:.*Bazel.runfiles"
39+
if ! (echo "$actual" | grep "$expected_pattern" ) >/dev/null; then
40+
echo "expected output to match: $expected_pattern"
41+
echo "but got: $actual"
42+
exit 1
43+
fi
44+

tests/base_rules/run_zip_test.sh

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2023 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 _main/tests/base_rules/_run_zip_test_bin)
28+
if [[ -z "$bin" ]]; then
29+
echo "Unable to locate test binary"
30+
exit 1
31+
fi
32+
actual=$($bin)
33+
34+
if [[ ! "$actual" == RULES_PYTHON_ZIP_DIR=/* ]]; then
35+
echo "expected output: RULES_PYTHON_ZIP_DIR=<some path>"
36+
echo "but got: $actual"
37+
exit 1
38+
fi

tests/support/sh_py_run_test.bzl

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
"""Run a py_binary with altered config settings in an sh_test.
15+
16+
This facilitates verify running binaries with different configuration settings
17+
without the overhead of a bazel-in-bazel integration test.
18+
"""
19+
20+
load("//python:py_binary.bzl", "py_binary")
21+
22+
def _perform_transition_impl(input_settings, attr):
23+
settings = dict(input_settings)
24+
settings["//command_line_option:build_python_zip"] = attr.build_python_zip
25+
if attr.bootstrap_impl:
26+
settings["//python/config_settings:bootstrap_impl"] = attr.bootstrap_impl
27+
return settings
28+
29+
_perform_transition = transition(
30+
implementation = _perform_transition_impl,
31+
inputs = [
32+
"//python/config_settings:bootstrap_impl",
33+
],
34+
outputs = [
35+
"//command_line_option:build_python_zip",
36+
"//python/config_settings:bootstrap_impl",
37+
],
38+
)
39+
40+
def _transition_impl(ctx):
41+
default_info = ctx.attr.target[DefaultInfo]
42+
exe_ext = default_info.files_to_run.executable.extension
43+
if exe_ext:
44+
exe_ext = "." + exe_ext
45+
exe_name = ctx.label.name + exe_ext
46+
47+
executable = ctx.actions.declare_file(exe_name)
48+
ctx.actions.symlink(output = executable, target_file = default_info.files_to_run.executable)
49+
50+
default_outputs = [executable]
51+
52+
# todo: could probably check target.owner vs src.owner to check if it should
53+
# be symlinked or included as-is
54+
# For simplicity of implementation, we're assuming the target being run is
55+
# py_binary-like. In order for Windows to work, we need to make sure the
56+
# file that the .exe launcher runs (the .zip or underlying non-exe
57+
# executable) is a sibling of the .exe file with the same base name.
58+
for src in default_info.files.to_list():
59+
if src.extension in ("", "zip"):
60+
ext = ("." if src.extension else "") + src.extension
61+
output = ctx.actions.declare_file(ctx.label.name + ext)
62+
ctx.actions.symlink(output = output, target_file = src)
63+
default_outputs.append(output)
64+
65+
return [
66+
DefaultInfo(
67+
executable = executable,
68+
files = depset(default_outputs),
69+
runfiles = default_info.default_runfiles,
70+
),
71+
testing.TestEnvironment(
72+
environment = ctx.attr.env,
73+
),
74+
]
75+
76+
transition_binary = rule(
77+
implementation = _transition_impl,
78+
attrs = {
79+
"bootstrap_impl": attr.string(),
80+
"build_python_zip": attr.string(default = "auto"),
81+
"env": attr.string_dict(),
82+
"target": attr.label(executable = True, cfg = "target"),
83+
"_allowlist_function_transition": attr.label(
84+
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
85+
),
86+
},
87+
cfg = _perform_transition,
88+
executable = True,
89+
)
90+
91+
def sh_py_run_test(*, name, sh_src, py_src, **kwargs):
92+
bin_name = "_{}_bin".format(name)
93+
native.sh_test(
94+
name = name,
95+
srcs = [sh_src],
96+
data = [bin_name],
97+
deps = [
98+
"@bazel_tools//tools/bash/runfiles",
99+
],
100+
env = {
101+
"BIN_RLOCATION": "$(rlocationpath {})".format(bin_name),
102+
},
103+
)
104+
105+
transition_binary(
106+
name = bin_name,
107+
tags = ["manual"],
108+
target = "_{}_plain_bin".format(name),
109+
**kwargs
110+
)
111+
112+
py_binary(
113+
name = "_{}_plain_bin".format(name),
114+
srcs = [py_src],
115+
main = py_src,
116+
tags = ["manual"],
117+
)

0 commit comments

Comments
 (0)