From b14d0b2e1c427c6f37694952a055d02ae25428cf Mon Sep 17 00:00:00 2001 From: Vincent Fazio Date: Sun, 6 Nov 2022 14:24:43 -0600 Subject: [PATCH] gh-99204: Calculate base_executable by alternate names in POSIX venvs Check to see if `base_executable` exists. If it does not, attempt to use known alternative names of the python binary to find an executable in the path specified by `home`. If no alternative is found, previous behavior is preserved. Signed-off-by: Vincent Fazio --- Lib/test/test_getpath.py | 31 +++++++++++++++++++ ...2-11-07-08-17-12.gh-issue-99204.Mf4hMD.rst | 4 +++ Modules/getpath.py | 19 ++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-11-07-08-17-12.gh-issue-99204.Mf4hMD.rst diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index 12d52442c554a11..7a22a9a8fb6043f 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -382,6 +382,37 @@ def test_venv_changed_name_posix(self): actual = getpath(ns, expected) self.assertEqual(expected, actual) + def test_venv_changed_name_copy_posix(self): + "Test a venv --copies layout on *nix that lacks a distributed 'python'" + ns = MockPosixNamespace( + argv0="python", + PREFIX="/usr", + ENV_PATH="/venv/bin:/usr/bin", + ) + ns.add_known_xfile("/usr/bin/python9") + ns.add_known_xfile("/venv/bin/python") + ns.add_known_file("/usr/lib/python9.8/os.py") + ns.add_known_dir("/usr/lib/python9.8/lib-dynload") + ns.add_known_file("/venv/pyvenv.cfg", [ + r"home = /usr/bin" + ]) + expected = dict( + executable="/venv/bin/python", + prefix="/usr", + exec_prefix="/usr", + base_executable="/usr/bin/python9", + base_prefix="/usr", + base_exec_prefix="/usr", + module_search_paths_set=1, + module_search_paths=[ + "/usr/lib/python98.zip", + "/usr/lib/python9.8", + "/usr/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + def test_symlink_normal_posix(self): "Test a 'standard' install layout via symlink on *nix" ns = MockPosixNamespace( diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-07-08-17-12.gh-issue-99204.Mf4hMD.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-07-08-17-12.gh-issue-99204.Mf4hMD.rst new file mode 100644 index 000000000000000..571cdd02cd55888 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-07-08-17-12.gh-issue-99204.Mf4hMD.rst @@ -0,0 +1,4 @@ +Fix calculation of :data:`sys._base_executable` when inside a POSIX virtual +environment using copies of the python binary when the base installation does +not provide the executable name used by the venv. Calculation will fall back to +alternative names ("python", "python."). diff --git a/Modules/getpath.py b/Modules/getpath.py index 90a6473f1e6ce73..caf1081d9d43fc0 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -375,6 +375,25 @@ def search_up(prefix, *landmarks, test=isfile): pass if not base_executable: base_executable = joinpath(executable_dir, basename(executable)) + # It's possible "python" is executed from within a posix venv but that + # "python" is not available in the "home" directory as the standard + # `make install` does not create it and distros often do not provide it. + # + # In this case, try to fall back to known alternatives + if os_name != 'nt' and not isfile(base_executable): + _base_exe = basename(executable) + for _candidate in (DEFAULT_PROGRAM_NAME, f'python{VERSION_MAJOR}.{VERSION_MINOR}'): + _candidate += EXE_SUFFIX if EXE_SUFFIX else '' + if _base_exe == _candidate: + continue + _candidate = joinpath(executable_dir, _candidate) + # Only set base_executable if the candidate exists. + # If no candidate succeeds, subsequent errors related to + # base_executable (like FileNotFoundError) remain in the + # context of the original executable name + if isfile(_candidate): + base_executable = _candidate + break break else: venv_prefix = None