Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try alternate filenames for system_executable #2442

Merged
merged 1 commit into from Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog/2442.bugfix.rst
@@ -0,0 +1 @@
In POSIX virtual environments, try alternate binary names if ``sys._base_executable`` does not exist - by :user:`vfazio`.
19 changes: 18 additions & 1 deletion src/virtualenv/discovery/py_info.py
Expand Up @@ -138,7 +138,24 @@ 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
return base_executable
if os.path.exists(base_executable):
return base_executable
# Python may return "python" because it was invoked from the POSIX virtual environment
# however some installs/distributions do not provide a version-less "python" binary in
# the system install location (see PEP 394) so try to fallback to a versioned binary.
#
# Gate this to Python 3.11 as `sys._base_executable` path resolution is now relative to
# the 'home' key from pyvenv.cfg which often points to the system install location.
major, minor = self.version_info.major, self.version_info.minor
if self.os == "posix" and (major, minor) >= (3, 11):
# search relative to the directory of sys._base_executable
base_dir = os.path.dirname(base_executable)
for base_executable in [
os.path.join(base_dir, exe)
for exe in ("python{}".format(major), "python{}.{}".format(major, minor))
]:
if os.path.exists(base_executable):
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
# note we must choose the original and not the pure executable as shim scripts might throw us off
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/discovery/py_info/test_py_info.py
Expand Up @@ -378,6 +378,29 @@ def test_custom_venv_install_scheme_is_prefered(mocker):
assert pyinfo.install_path("purelib").replace(os.sep, "/") == f"lib/python{pyver}/site-packages"


@pytest.mark.skipif(not (os.name == "posix" and sys.version_info[:2] >= (3, 11)), reason="POSIX 3.11+ specific")
def test_fallback_existent_system_executable(mocker):
current = PythonInfo()
# Posix may execute a "python" out of a venv but try to set the base_executable
# to "python" out of the system installation path. PEP 394 informs distributions
# that "python" is not required and the standard `make install` does not provide one

# Falsify some data to look like we're in a venv
current.prefix = current.exec_prefix = "/tmp/tmp.izZNCyINRj/venv"
gaborbernat marked this conversation as resolved.
Show resolved Hide resolved
current.executable = current.original_executable = os.path.join(current.prefix, "bin/python")

# Since we don't know if the distribution we're on provides python, use a binary that should not exist
mocker.patch.object(sys, "_base_executable", os.path.join(os.path.dirname(current.system_executable), "idontexist"))
mocker.patch.object(sys, "executable", current.executable)

# ensure it falls back to an alternate binary name that exists
current._fast_get_system_executable()
assert os.path.basename(current.system_executable) in [
f"python{v}" for v in (current.version_info.major, f"{current.version_info.major}.{current.version_info.minor}")
]
assert os.path.exists(current.system_executable)


@pytest.mark.skipif(sys.version_info[:2] != (3, 10), reason="3.10 specific")
def test_uses_posix_prefix_on_debian_3_10_without_venv(mocker):
# this is taken from ubuntu 22.04 /usr/lib/python3.10/sysconfig.py
Expand Down