Skip to content

Commit a401061

Browse files
pfmooregaborbernat
authored andcommitted
Further fix for #1339 (--python option) (#1364)
* Further fix for #1339 (--python option) * Add a changelog entry * Handle the case where site.py doesn't set sys._base_executable * Should have run black before committing
1 parent 7dc8315 commit a401061

File tree

2 files changed

+68
-40
lines changed

2 files changed

+68
-40
lines changed

docs/changelog/1364.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an additional issue with #1339, where the user specifies ``--python``
2+
pointing to a venv redirector executable.

virtualenv.py

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -738,55 +738,81 @@ def main():
738738

739739
def should_reinvoke(options):
740740
"""Do we need to reinvoke ourself?"""
741-
# 1. Did the user specify the --python option?
741+
# Did the user specify the --python option?
742742
if options.python and not os.environ.get("VIRTUALENV_INTERPRETER_RUNNING"):
743-
return options.python
744-
# All of the remaining cases are only for Windows
743+
interpreter = resolve_interpreter(options.python)
744+
if interpreter != sys.executable:
745+
# The user specified a different interpreter, so we have to reinvoke.
746+
return interpreter
747+
748+
# At this point, we know the user wants to use sys.executable to create the
749+
# virtual environment. But on Windows, sys.executable may be a venv redirector,
750+
# in which case we still need to locate the underlying actual interpreter, and
751+
# reinvoke using that.
745752
if IS_WIN:
746-
# 2. Are we running from a venv-style virtual environment with a redirector?
753+
# OK. Now things get really fun...
754+
#
755+
# If we are running from a venv, with a redirector, then what happens is as
756+
# follows:
757+
#
758+
# 1. The redirector sets __PYVENV_LAUNCHER__ in the environment to point
759+
# to the redirector executable.
760+
# 2. The redirector launches the "base" Python (from the home value in
761+
# pyvenv.cfg).
762+
# 3. The base Python executable sees __PYVENV_LAUNCHER__ in the environment
763+
# and sets sys.executable to that value.
764+
# 4. If site.py gets run, it sees __PYVENV_LAUNCHER__, and sets
765+
# sys._base_executable to _winapi.GetModuleFileName(0) and removes
766+
# __PYVENV_LAUNCHER__.
767+
#
768+
# Unfortunately, that final step (site.py) may not happen. There are 2 key
769+
# times when that is the case:
770+
#
771+
# 1. Python 3.7.2, which had the redirector but not the site.py code.
772+
# 2. Running a venv from a virtualenv, which uses virtualenv's custom
773+
# site.py.
774+
#
775+
# So, we check for sys._base_executable, but if it's not present and yet we
776+
# hand __PYVENV_LAUNCHER__, we do what site.py would have done and get our
777+
# interpreter from GetModuleFileName(0). We also remove __PYVENV_LAUNCHER__
778+
# from the environment, to avoid loops (actually, mainly because site.py
779+
# does so, and my head hurts enough buy now that I just want to be safe!)
780+
781+
# Phew.
782+
747783
if hasattr(sys, "_base_executable"):
748784
return sys._base_executable
749-
# 3. Special case for Python 3.7.2, where we have a redirector,
750-
# but sys._base_executable does not exist.
751-
if sys.version_info[:3] == (3, 7, 2):
752-
# We are in a venv if the environment variable __PYVENV_LAUNCHER__ is set.
753-
if "__PYVENV_LAUNCHER__" in os.environ:
754-
# The base environment is either sys.real_prefix (if
755-
# we were invoked from a venv built from a virtualenv) or
756-
# sys.base_prefix if real_prefix doesn't exist (a simple venv).
757-
base_prefix = getattr(sys, "real_prefix", sys.base_prefix)
758-
# We assume the Python executable is directly under the prefix
759-
# directory. The only known case where that won't be the case is
760-
# an in-place source build, which we don't support. We don't need
761-
# to consider virtuale environments (where python.exe is in "Scripts"
762-
# because we've just followed the links back to a non-virtual
763-
# environment - we hope!)
764-
base_exe = os.path.join(base_prefix, "python.exe")
765-
if os.path.exists(base_exe):
766-
return base_exe
785+
786+
if "__PYVENV_LAUNCHER__" in os.environ:
787+
import _winapi
788+
789+
del os.environ["__PYVENV_LAUNCHER__"]
790+
return _winapi.GetModuleFileName(0)
791+
767792
# We don't need to reinvoke
768793
return None
769794

770795
interpreter = should_reinvoke(options)
771-
if interpreter:
796+
if interpreter is None:
797+
# We don't need to reinvoke - if the user asked us to, tell them why we
798+
# aren't.
799+
if options.python:
800+
logger.warn("Already using interpreter {}".format(sys.executable))
801+
else:
772802
env = os.environ.copy()
773-
interpreter = resolve_interpreter(interpreter)
774-
if interpreter == sys.executable:
775-
logger.warn("Already using interpreter {}".format(interpreter))
776-
else:
777-
logger.notify("Running virtualenv with interpreter {}".format(interpreter))
778-
env["VIRTUALENV_INTERPRETER_RUNNING"] = "true"
779-
# Remove the variable __PYVENV_LAUNCHER__ if it's present, as it causes the
780-
# interpreter to redirect back to the virtual environment.
781-
if "__PYVENV_LAUNCHER__" in env:
782-
del env["__PYVENV_LAUNCHER__"]
783-
file = __file__
784-
if file.endswith(".pyc"):
785-
file = file[:-1]
786-
elif IS_ZIPAPP:
787-
file = HERE
788-
sub_process_call = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env)
789-
raise SystemExit(sub_process_call.wait())
803+
logger.notify("Running virtualenv with interpreter {}".format(interpreter))
804+
env["VIRTUALENV_INTERPRETER_RUNNING"] = "true"
805+
# Remove the variable __PYVENV_LAUNCHER__ if it's present, as it causes the
806+
# interpreter to redirect back to the virtual environment.
807+
if "__PYVENV_LAUNCHER__" in env:
808+
del env["__PYVENV_LAUNCHER__"]
809+
file = __file__
810+
if file.endswith(".pyc"):
811+
file = file[:-1]
812+
elif IS_ZIPAPP:
813+
file = HERE
814+
sub_process_call = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env)
815+
raise SystemExit(sub_process_call.wait())
790816

791817
if not args:
792818
print("You must provide a DEST_DIR")

0 commit comments

Comments
 (0)