Skip to content

Commit 50b4b6b

Browse files
authored
Try alternate filenames for system_executable (#2442)
1 parent dbc57c2 commit 50b4b6b

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

docs/changelog/2442.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
In POSIX virtual environments, try alternate binary names if ``sys._base_executable`` does not exist - by :user:`vfazio`.

src/virtualenv/discovery/py_info.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,24 @@ def _fast_get_system_executable(self):
138138
base_executable = getattr(sys, "_base_executable", None) # some platforms may set this to help us
139139
if base_executable is not None: # use the saved system executable if present
140140
if sys.executable != base_executable: # we know we're in a virtual environment, cannot be us
141-
return base_executable
141+
if os.path.exists(base_executable):
142+
return base_executable
143+
# Python may return "python" because it was invoked from the POSIX virtual environment
144+
# however some installs/distributions do not provide a version-less "python" binary in
145+
# the system install location (see PEP 394) so try to fallback to a versioned binary.
146+
#
147+
# Gate this to Python 3.11 as `sys._base_executable` path resolution is now relative to
148+
# the 'home' key from pyvenv.cfg which often points to the system install location.
149+
major, minor = self.version_info.major, self.version_info.minor
150+
if self.os == "posix" and (major, minor) >= (3, 11):
151+
# search relative to the directory of sys._base_executable
152+
base_dir = os.path.dirname(base_executable)
153+
for base_executable in [
154+
os.path.join(base_dir, exe)
155+
for exe in ("python{}".format(major), "python{}.{}".format(major, minor))
156+
]:
157+
if os.path.exists(base_executable):
158+
return base_executable
142159
return None # in this case we just can't tell easily without poking around FS and calling them, bail
143160
# if we're not in a virtual environment, this is already a system python, so return the original executable
144161
# note we must choose the original and not the pure executable as shim scripts might throw us off

tests/unit/discovery/py_info/test_py_info.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,29 @@ def test_custom_venv_install_scheme_is_prefered(mocker):
378378
assert pyinfo.install_path("purelib").replace(os.sep, "/") == f"lib/python{pyver}/site-packages"
379379

380380

381+
@pytest.mark.skipif(not (os.name == "posix" and sys.version_info[:2] >= (3, 11)), reason="POSIX 3.11+ specific")
382+
def test_fallback_existent_system_executable(mocker):
383+
current = PythonInfo()
384+
# Posix may execute a "python" out of a venv but try to set the base_executable
385+
# to "python" out of the system installation path. PEP 394 informs distributions
386+
# that "python" is not required and the standard `make install` does not provide one
387+
388+
# Falsify some data to look like we're in a venv
389+
current.prefix = current.exec_prefix = "/tmp/tmp.izZNCyINRj/venv"
390+
current.executable = current.original_executable = os.path.join(current.prefix, "bin/python")
391+
392+
# Since we don't know if the distribution we're on provides python, use a binary that should not exist
393+
mocker.patch.object(sys, "_base_executable", os.path.join(os.path.dirname(current.system_executable), "idontexist"))
394+
mocker.patch.object(sys, "executable", current.executable)
395+
396+
# ensure it falls back to an alternate binary name that exists
397+
current._fast_get_system_executable()
398+
assert os.path.basename(current.system_executable) in [
399+
f"python{v}" for v in (current.version_info.major, f"{current.version_info.major}.{current.version_info.minor}")
400+
]
401+
assert os.path.exists(current.system_executable)
402+
403+
381404
@pytest.mark.skipif(sys.version_info[:2] != (3, 10), reason="3.10 specific")
382405
def test_uses_posix_prefix_on_debian_3_10_without_venv(mocker):
383406
# this is taken from ubuntu 22.04 /usr/lib/python3.10/sysconfig.py

0 commit comments

Comments
 (0)