From 36c7ed575f1e1a38364e2360ca14525b4cd1c485 Mon Sep 17 00:00:00 2001 From: Vincent Fazio Date: Mon, 7 Nov 2022 14:21:13 -0600 Subject: [PATCH] Try alternate filenames for system_executable The value of `sys._base_executable` may not be a real file due to changes made in CPython 3.11. The value is derived from the current executable name and the "home" key from pyvenv.cfg. On POSIX systems, virtual environments deploy "python" for use within the venv however CPython's `make install` and a number of distributions do not provide a system "python" in part because of PEP 394. Virtualenv exposes this via `PythonInfo.system_executable` and can encounter issues when attempting to execute a non-existent file. Attempt to fallback to "python" and "python." if "python" does not exist. Signed-off-by: Vincent Fazio --- src/virtualenv/discovery/py_info.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/virtualenv/discovery/py_info.py b/src/virtualenv/discovery/py_info.py index 7c680eeaa..d9f9fce2d 100644 --- a/src/virtualenv/discovery/py_info.py +++ b/src/virtualenv/discovery/py_info.py @@ -138,6 +138,20 @@ def _fast_get_system_executable(self): base_executable = getattr(sys, "_base_executable", None) # some platforms may set this to help us if base_executable is not None: # use the saved system executable if present if sys.executable != base_executable: # we know we're in a virtual environment, cannot be us + # Do light validation for non-Windows virtual environments + # Some installs/distributions do not provide a version-less "python" binary (PEP 394). + # Try to fallback to an alternative based on version number + # This should still be relatively fast since it's just a couple of `stat` calls + if self.os != "nt" and (self.version_info.major, self.version_info.minor) >= (3, 11): + if not os.path.exists(base_executable): + major, minor = self.version_info.major, self.version_info.minor + base_dir = os.path.dirname(base_executable) + for candidate in [ + os.path.join(base_dir, f"python{v}") for v in (major, f"{major}.{minor}") + ]: + if os.path.exists(candidate): + base_executable = candidate + break return base_executable return None # in this case we just can't tell easily without poking around FS and calling them, bail # if we're not in a virtual environment, this is already a system python, so return the original executable