[Python-checkins] gh-99204: Calculate base_executable by alternate names in POSIX venvs (GH-99206)

zooba webhook-mailer at python.org
Thu Nov 10 11:26:50 EST 2022


https://github.com/python/cpython/commit/c41b13d39ccc2d6e239782de99ba8e3cdd061e5a
commit: c41b13d39ccc2d6e239782de99ba8e3cdd061e5a
branch: main
author: Vincent Fazio <vfazio at gmail.com>
committer: zooba <steve.dower at microsoft.com>
date: 2022-11-10T16:26:42Z
summary:

gh-99204: Calculate base_executable by alternate names in POSIX venvs (GH-99206)

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 <vfazio at gmail.com>

Signed-off-by: Vincent Fazio <vfazio at gmail.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2022-11-07-08-17-12.gh-issue-99204.Mf4hMD.rst
M Lib/test/test_getpath.py
M Modules/getpath.py

diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py
index 12d52442c554..7a22a9a8fb60 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 000000000000..571cdd02cd55
--- /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<MAJOR>", "python<MAJOR>.<MINOR>").
diff --git a/Modules/getpath.py b/Modules/getpath.py
index 90a6473f1e6c..d24b15259a97 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



More information about the Python-checkins mailing list