Skip to content

Commit 8ff9b2a

Browse files
committed
make runtime env toolchain work with bootstrap=script venv
1 parent 0fb8f52 commit 8ff9b2a

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

python/private/py_executable.bzl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ def _create_executable(
350350
main_py = main_py,
351351
imports = imports,
352352
runtime_details = runtime_details,
353+
venv = venv,
353354
)
354355
extra_runfiles = ctx.runfiles([stage2_bootstrap] + venv.files_without_interpreter)
355356
zip_main = _create_zip_main(
@@ -557,6 +558,8 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
557558
ctx.actions.write(interpreter, "actual:{}".format(interpreter_actual_path))
558559

559560
elif runtime.interpreter:
561+
# Some wrappers around the interpreter (e.g. pyenv) use the program
562+
# name to decide what to do, so preserve the name.
560563
py_exe_basename = paths.basename(runtime.interpreter.short_path)
561564

562565
# Even though ctx.actions.symlink() is used, using
@@ -594,7 +597,8 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
594597
if "t" in runtime.abi_flags:
595598
version += "t"
596599

597-
site_packages = "{}/lib/python{}/site-packages".format(venv, version)
600+
venv_site_packages = "lib/python{}/site-packages".format(version)
601+
site_packages = "{}/{}".format(venv, venv_site_packages)
598602
pth = ctx.actions.declare_file("{}/bazel.pth".format(site_packages))
599603
ctx.actions.write(pth, "import _bazel_site_init\n")
600604

@@ -620,6 +624,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
620624
# Runfiles root relative path or absolute path
621625
interpreter_actual_path = interpreter_actual_path,
622626
files_without_interpreter = [pyvenv_cfg, pth, site_init] + site_packages_symlinks,
627+
venv_site_packages = venv_site_packages,
623628
)
624629

625630
def _create_site_packages_symlinks(ctx, site_packages):
@@ -716,7 +721,8 @@ def _create_stage2_bootstrap(
716721
output_sibling,
717722
main_py,
718723
imports,
719-
runtime_details):
724+
runtime_details,
725+
venv = None):
720726
output = ctx.actions.declare_file(
721727
# Prepend with underscore to prevent pytest from trying to
722728
# process the bootstrap for files starting with `test_`
@@ -742,6 +748,7 @@ def _create_stage2_bootstrap(
742748
"%main_module%": ctx.attr.main_module,
743749
"%target%": str(ctx.label),
744750
"%workspace_name%": ctx.workspace_name,
751+
"%site_packages%": venv.venv_site_packages if venv else "",
745752
},
746753
is_executable = True,
747754
)

python/private/runtime_env_toolchain_interpreter.sh

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,27 @@ documentation for py_runtime_pair \
5353
(https://github.com/bazel-contrib/rules_python/blob/master/docs/python.md#py_runtime_pair)."
5454
fi
5555

56-
exec "$PYTHON_BIN" "$@"
56+
# Because this is a wrapper script that invokes Python, it prevents Python from
57+
# detecting virtualenvs like normal (i.e. using the venv symlink to find the
58+
# real interpreter). To work around this, we have to manually detect the venv,
59+
# then trick the interpreter into understanding we're in a virtual env.
60+
self_dir=$(dirname $0)
61+
if [[ -e "$self_dir/pyvenv.cfg" || -e "$self_dir/../pyvenv.cfg" ]]; then
62+
if [[ "$0" == /* ]]; then
63+
venv_bin="$0"
64+
else
65+
venv_bin="$PWD/$0"
66+
fi
5767

68+
# PYTHONEXECUTABLE is also used because `exec -a` doesn't fully trick the
69+
# pyenv wrappers.
70+
# NOTE: The PYTHONEXECUTABLE envvar docs say it's only for Mac, however,
71+
# emperically it also works on e.g. Linux
72+
export PYTHONEXECUTABLE="$venv_bin"
73+
# Python uses to det
74+
# Use exec -a so that Python looks at the venv's binary, not the
75+
# actual one invoked.
76+
exec -a "$venv_bin" "$PYTHON_BIN" "$@"
77+
else
78+
exec "$PYTHON_BIN" "$@"
79+
fi

python/private/stage2_bootstrap_template.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
# Module name to execute. Empty if MAIN is used.
3333
MAIN_MODULE = "%main_module%"
3434

35+
# venv-relative path to the expected location of the binary's site-packages
36+
# directory.
37+
VENV_SITE_PACKAGES = "%site_packages%"
38+
3539
# ===== Template substitutions end =====
3640

3741

@@ -365,6 +369,18 @@ def main():
365369
print_verbose("initial environ:", mapping=os.environ)
366370
print_verbose("initial sys.path:", values=sys.path)
367371

372+
site_packages = os.path.join(sys.prefix, VENV_SITE_PACKAGES)
373+
if site_packages not in sys.path:
374+
# NOTE: if this happens, it likely means we're running with a different
375+
# Python version than was built with. Things may or may not work.
376+
# Such a situation is likely due to the runtime_env toolchain, or some
377+
# toolchain configuration. In any case, this better matches how the
378+
# previous bootstrap=system_python bootstrap worked (using PYTHONPATH,
379+
# which isn't version-specific).
380+
print_verbose(f"sys.path missing expected site-packages: adding {site_packages}")
381+
import site
382+
site.addsitedir(site_packages)
383+
368384
main_rel_path = None
369385
# todo: things happen to work because find_runfiles_root
370386
# ends up using stage2_bootstrap, and ends up computing the proper

0 commit comments

Comments
 (0)