Skip to content

Commit c4d0643

Browse files
committed
have explicit symlinks add files to runfiles to ensure target exists
1 parent 4451571 commit c4d0643

File tree

4 files changed

+51
-36
lines changed

4 files changed

+51
-36
lines changed

python/private/py_executable.bzl

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,21 @@ ExplicitSymlink = provider(
7979
A runfile that should be created as a symlink pointing to a specific location.
8080
8181
This is only needed on Windows, where Bazel doesn't preserve declare_symlink
82-
with relative paths.
82+
with relative paths. This is basically manually captures what using
83+
declare_symlink(), symlink() and runfiles like so would capture:
84+
85+
```
86+
link = declare_symlink(...)
87+
link_to_path = relative_path(from=link, to=target)
88+
symlink(output=link, target_path=link_to_path)
89+
runfiles.add([link, target])
90+
```
8391
""",
8492
fields = {
85-
"link_to": "Path the symlink should point to",
86-
"rf_path": "runfile-root-relative path for the link",
87-
"venv_rel_path": "venv-root-relative path for the link",
93+
"link_to_path": "Path the symlink should point to",
94+
"runfiles_path": "runfiles-root-relative path for the symlink",
95+
"venv_path": "venv-root-relative path for the symlink",
96+
"files": "depset[File] of files that should be included if this symlink is used",
8897
},
8998
)
9099

@@ -736,7 +745,7 @@ def _create_venv_unixy(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_pa
736745

737746
def _create_venv_windows(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_path):
738747
interpreter_runfiles = builders.RunfilesBuilder()
739-
interpreter_symlinks = []
748+
interpreter_symlinks = builders.DepsetBuilder()
740749

741750
# Some wrappers around the interpreter (e.g. pyenv) use the program
742751
# name to decide what to do, so preserve the name.
@@ -751,17 +760,15 @@ def _create_venv_windows(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_
751760
ctx.actions.symlink(output = interpreter, target_file = runtime.interpreter)
752761

753762
rf_path = runfiles_root_path(ctx, interpreter.short_path)
754-
interpreter_symlinks.append(ExplicitSymlink(
755-
rf_path = rf_path,
756-
venv_rel_path = venv_rel_path,
757-
link_to = relative_path(
758-
# dirname is necessary because a relative symlink is relative to
759-
# the directory the symlink resides within.
760-
from_ = paths.dirname(rf_path),
761-
to = interpreter_actual_path,
762-
),
763+
interpreter_symlinks.add(ExplicitSymlink(
764+
runfiles_path = rf_path,
765+
venv_path = venv_rel_path,
766+
link_to_path = interpreter_actual_path,
767+
files = depset([runtime.interpreter]),
763768
))
764769
else:
770+
# It's OK to use declare_symlink here because an absolute path
771+
# will be written to it, so Bazel won't mangle it.
765772
interpreter = ctx.actions.declare_symlink("{}/{}".format(venv_bin_ctx_rel_path, py_exe_basename))
766773
interpreter_runfiles.add(interpreter)
767774
ctx.actions.symlink(output = interpreter, target_path = runtime.interpreter_path)
@@ -771,19 +778,18 @@ def _create_venv_windows(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_
771778
for f in runtime.venv_bin_files:
772779
venv_rel_path = paths.join(venv_bin_rel_path, f.basename)
773780
venv_ctx_rel_path = paths.join(venv_ctx_rel_root, venv_rel_path)
781+
774782
venv_file = ctx.actions.declare_file(venv_ctx_rel_path)
775783
ctx.actions.symlink(output = venv_file, target_file = f)
784+
776785
interpreter_runfiles.add(venv_file)
786+
777787
rf_path = runfiles_root_path(ctx, venv_file.short_path)
778-
interpreter_symlinks.append(ExplicitSymlink(
779-
rf_path = rf_path,
780-
venv_rel_path = venv_rel_path,
781-
link_to = relative_path(
782-
# dirname is necessary because a relative symlink is relative to
783-
# the directory the symlink resides within.
784-
from_ = paths.dirname(rf_path),
785-
to = runfiles_root_path(ctx, f.short_path),
786-
),
788+
interpreter_symlinks.add(ExplicitSymlink(
789+
runfiles_path = rf_path,
790+
venv_path = venv_rel_path,
791+
link_to_path = runfiles_root_path(ctx, f.short_path),
792+
files = depset([f])
787793
))
788794

789795
# See site.py logic: Windows uses a version/build agnostic site-packages path
@@ -796,7 +802,7 @@ def _create_venv_windows(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_
796802
bin_dir = venv_bin_ctx_rel_path,
797803
recreate_venv_at_runtime = True,
798804
interpreter_runfiles = interpreter_runfiles.build(ctx),
799-
interpreter_symlinks = depset(interpreter_symlinks),
805+
interpreter_symlinks = interpreter_symlinks.build(),
800806
)
801807

802808
def _venv_details(
@@ -972,7 +978,7 @@ def _create_stage1_bootstrap(
972978
)
973979

974980
def _map_runtime_venv_symlink(entry):
975-
return entry.venv_rel_path + "|" + entry.link_to
981+
return entry.venv_path + "|" + entry.link_to_path
976982

977983
def _create_zip_file(ctx, *, output, zip_main, runfiles):
978984
"""Create a Python zipapp (zip with __main__.py entry point)."""

python/private/python_bootstrap_template.txt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -371,22 +371,25 @@ print(site.getsitepackages(["{venv_src}"])[-1])
371371
target = join(python_home, f)
372372
_symlink_exist_ok(from_=venv_path, to=target)
373373

374-
for venv_rel_path, link_to in RUNTIME_VENV_SYMLINKS.items():
375-
venv_abs_path = join(venv, venv_rel_path)
376-
link_to = normpath(join(runfiles_venv, dirname(venv_rel_path), link_to))
377-
_symlink_exist_ok(from_=venv_abs_path, to=link_to)
378-
379374
runfiles_venv_bin = join(runfiles_venv, BIN_DIR_NAME)
380375
# The runfiles bin directory may not exist if it's empty, e.g.
381376
# supports_build_time_venv=False and the interpreter is resolved at
382377
# runtime.
383378
if os.path.exists(runfiles_venv_bin):
384-
# Add any missing build-time entries under bin/
379+
# Add any missing build-time entries under bin/. Do this before
380+
# the manual symlink creation to better mimic what the
381+
# regular runfiles directory looks like.
385382
for f_basename in os.listdir(runfiles_venv_bin):
386383
venv_path = join(venv, BIN_DIR_NAME, f_basename)
387384
target = join(runfiles_venv_bin, f_basename)
388385
_symlink_exist_ok(from_=venv_path, to=target)
389386

387+
for venv_rel_path, link_to_rf_path in RUNTIME_VENV_SYMLINKS.items():
388+
venv_abs_path = join(venv, venv_rel_path)
389+
link_to = normpath(join(runfiles_venv, link_to_rf_path))
390+
os.makedirs(dirname(venv_abs_path), exist_ok=True)
391+
_symlink_exist_ok(from_=venv_abs_path, to=link_to)
392+
390393
_symlink_exist_ok(from_=join(venv, "lib"), to=join(runfiles_venv, "lib"))
391394
_symlink_exist_ok(from_=venv_site_packages, to=runfiles_venv_site_packages)
392395

python/private/zipapp/py_zipapp_rule.bzl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,12 @@ def _create_zip(ctx, py_runtime, py_executable, stage2_bootstrap):
138138
)
139139
inputs = builders.DepsetBuilder()
140140
manifest.add("regular|0|__main__.py|{}".format(zip_main.path))
141-
is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints)
141+
142+
# unfortunately, we have to flatten to add the files to inputs.
142143
for entry in py_executable.venv_interpreter_symlinks.to_list():
143-
manifest.add("symlink|{}|{}".format(entry.rf_path, entry.link_to))
144+
manifest.add("symlink|{}|{}".format(entry.runfiles_path, entry.link_to_path))
145+
inputs.add(entry.files)
146+
144147
inputs.add(zip_main)
145148
_build_manifest(ctx, manifest, runfiles, inputs)
146149

@@ -155,6 +158,7 @@ def _create_zip(ctx, py_runtime, py_executable, stage2_bootstrap):
155158
zipper_args.add(ctx.attr.compression, format = "--compression=%s")
156159
zipper_args.add("--runfiles-dir=runfiles")
157160

161+
is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints)
158162
zipper_args.add("\\" if is_windows else "/", format = "--pathsep=%s")
159163

160164
actions_run(

tools/private/zipapp/zipper.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ def _parse_entry(
6161
_, is_symlink_str, runfile_path, content_path = parts
6262
zip_path = unix_join(runfiles_dir, runfile_path)
6363
elif type_ == "symlink":
64-
_, runfile_path, content_path = parts
64+
_, runfile_path, link_to_rf_path = parts
6565
zip_path = unix_join(runfiles_dir, runfile_path)
66+
link_to_rf_path = unix_join(runfiles_dir, link_to_rf_path)
67+
content_path = os.path.relpath(link_to_rf_path, start=zip_path)
6668
is_symlink_str = "2"
6769
else:
6870
raise ValueError(
@@ -214,8 +216,8 @@ def main():
214216
5. `rf-root-symlink|is_symlink|runfile_root_path|content_path`: Store a
215217
runfiles-root-relative path in the zip.
216218
217-
6. `symlink|runfile_root_path|link_to`: Store a symlink with the value
218-
of `link_to`.
219+
6. `symlink|runfile_root_path|link_to_path_rf_path`: Store a symlink that
220+
stores a relative path from `runfile_root_path` to `link_to_rf_path`
219221
220222
In all cases, `is_symlink` has the following values:
221223
* `1` means it should be stored as a symlink whose value is read

0 commit comments

Comments
 (0)