Skip to content

Commit e3ba016

Browse files
committed
Merge branch 'main' of https://github.com/bazel-contrib/rules_python into sys.py.win.venv
2 parents 8495123 + 6d38770 commit e3ba016

3 files changed

Lines changed: 41 additions & 20 deletions

File tree

python/private/zipapp/py_zipapp_rule.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ def _create_zipapp_main_py(ctx, py_runtime, py_executable, stage2_bootstrap):
3535
template = py_runtime.zip_main_template,
3636
output = zip_main_py,
3737
substitutions = {
38+
"%EXTRACT_DIR%": paths.join(
39+
(ctx.label.repo_name or "_main"),
40+
ctx.label.package,
41+
ctx.label.name,
42+
),
3843
"%python_binary%": venv_python_exe_path,
3944
"%python_binary_actual%": python_binary_actual_path,
4045
"%stage2_bootstrap%": runfiles_root_path(ctx, stage2_bootstrap.short_path),

python/private/zipapp/zip_main_template.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import subprocess
2929
import tempfile
3030
import zipfile
31+
from os.path import dirname, join
3132

3233
# runfiles-root-relative path
3334
_STAGE2_BOOTSTRAP = "%stage2_bootstrap%"
@@ -42,6 +43,7 @@
4243

4344
EXTRACT_ROOT = os.environ.get("RULES_PYTHON_EXTRACT_ROOT")
4445

46+
4547
def print_verbose(*args, mapping=None, values=None):
4648
if bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")):
4749
if mapping is not None:
@@ -111,7 +113,7 @@ def search_path(name):
111113
search_path = os.getenv("PATH", os.defpath).split(os.pathsep)
112114
for directory in search_path:
113115
if directory:
114-
path = os.path.join(directory, name)
116+
path = join(directory, name)
115117
if os.path.isfile(path) and os.access(path, os.X_OK):
116118
return path
117119
return None
@@ -132,7 +134,7 @@ def find_binary(runfiles_root, bin_name):
132134
# Use normpath() to convert slashes to os.sep on Windows.
133135
elif os.sep in os.path.normpath(bin_name):
134136
# Case 3: Path is relative to the repo root.
135-
return os.path.join(runfiles_root, bin_name)
137+
return join(runfiles_root, bin_name)
136138
else:
137139
# Case 4: Path has to be looked up in the search path.
138140
return search_path(bin_name)
@@ -154,10 +156,18 @@ def extract_zip(zip_path, dest_dir):
154156
dest_dir = get_windows_path_with_unc_prefix(dest_dir)
155157
with zipfile.ZipFile(zip_path) as zf:
156158
for info in zf.infolist():
159+
file_path = os.path.abspath(join(dest_dir, info.filename))
160+
# If the file exists, it might be a symlink or read-only file from a previous extraction.
161+
# Unlink it first so zipfile.extract doesn't corrupt the symlink target or fail on read-only files.
162+
if os.path.lexists(file_path) and not os.path.isdir(file_path):
163+
try:
164+
os.unlink(file_path)
165+
except OSError:
166+
# On Windows, unlinking a read-only file fails.
167+
os.chmod(file_path, stat.S_IWRITE)
168+
os.unlink(file_path)
169+
157170
zf.extract(info, dest_dir)
158-
# UNC-prefixed paths must be absolute/normalized. See
159-
# https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation
160-
file_path = os.path.abspath(os.path.join(dest_dir, info.filename))
161171
# The Unix st_mode bits (see "man 7 inode") are stored in the upper 16
162172
# bits of external_attr.
163173
attrs = info.external_attr >> 16
@@ -181,10 +191,10 @@ def create_runfiles_root():
181191
extract_root = join(EXTRACT_ROOT, EXTRACT_DIR)
182192
else:
183193
extract_root = tempfile.mkdtemp("", "Bazel.runfiles_")
184-
extract_zip(os.path.dirname(__file__), extract_root)
194+
extract_zip(dirname(__file__), extract_root)
185195
# IMPORTANT: Later code does `rm -fr` on dirname(runfiles_root) -- it's
186196
# important that deletion code be in sync with this directory structure
187-
return os.path.join(extract_root, "runfiles")
197+
return join(extract_root, "runfiles")
188198

189199

190200
def execute_file(
@@ -223,9 +233,10 @@ def execute_file(
223233
try:
224234
subprocess_argv = [python_program]
225235
if not EXTRACT_ROOT:
226-
subprocess_argv.append(f"-XRULES_PYTHON_ZIP_DIR={os.path.dirname(runfiles_root)}")
236+
subprocess_argv.append(f"-XRULES_PYTHON_ZIP_DIR={dirname(runfiles_root)}")
227237
subprocess_argv.append(main_filename)
228238
subprocess_argv += args
239+
print_verbose("subprocess argv:", values=subprocess_argv)
229240
print_verbose("subprocess env:", mapping=env)
230241
print_verbose("subprocess cwd:", workspace)
231242
print_verbose("subprocess argv:", values=subprocess_argv)
@@ -236,7 +247,7 @@ def execute_file(
236247
# NOTE: dirname() is called because create_runfiles_root() creates a
237248
# sub-directory within a temporary directory, and we want to remove the
238249
# whole temporary directory.
239-
extract_root = os.path.dirname(runfiles_root)
250+
extract_root = dirname(runfiles_root)
240251
print_verbose("cleanup: rmtree: ", extract_root)
241252
shutil.rmtree(extract_root, True)
242253

@@ -306,7 +317,7 @@ def main():
306317
# See: https://docs.python.org/3.11/using/cmdline.html#envvar-PYTHONSAFEPATH
307318
new_env["PYTHONSAFEPATH"] = "1"
308319

309-
main_filename = os.path.join(runfiles_root, main_rel_path)
320+
main_filename = join(runfiles_root, main_rel_path)
310321
main_filename = get_windows_path_with_unc_prefix(main_filename)
311322
assert os.path.exists(main_filename), (
312323
"Cannot exec() %r: file not found." % main_filename
@@ -338,7 +349,7 @@ def main():
338349
# change directory to the right runfiles directory.
339350
# (So that the data files are accessible)
340351
if os.environ.get("RUN_UNDER_RUNFILES") == "1":
341-
workspace = os.path.join(runfiles_root, _WORKSPACE_NAME)
352+
workspace = join(runfiles_root, _WORKSPACE_NAME)
342353

343354
sys.stdout.flush()
344355
execute_file(

tests/py_zipapp/system_python_zipapp_external_bootstrap_test.sh

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ fi
1313
ZIPAPP="${ZIPAPP/.exe/.zip}"
1414

1515
export RULES_PYTHON_BOOTSTRAP_VERBOSE=1
16+
1617
# We're testing the invocation of `__main__.py`, so we have to
1718
# manually pass the zipapp to python.
18-
set +e
19+
echo "Running zipapp using an automatic temp directory..."
20+
"$PYTHON" "$ZIPAPP"
21+
22+
echo "Running zipapp with extract root set..."
23+
export RULES_PYTHON_EXTRACT_ROOT="${TEST_TMPDIR:-/tmp}/extract_root_test"
1924
"$PYTHON" "$ZIPAPP"
20-
exit_code=$?
21-
set +x
22-
23-
if [[ "$exit_code" -ne 0 ]]; then
24-
echo "==============="
25-
echo "Invocation failed, exit code: $exit_code"
26-
echo "==============="
27-
exit "$exit_code"
25+
26+
# Verify that the directory was created
27+
if [[ ! -d "$RULES_PYTHON_EXTRACT_ROOT" ]]; then
28+
echo "Error: Extract root directory $RULES_PYTHON_EXTRACT_ROOT was not created!"
29+
exit 1
2830
fi
31+
32+
echo "Running zipapp with extract root set a second time..."
33+
"$PYTHON" "$ZIPAPP"

0 commit comments

Comments
 (0)