diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index 1a336a4abca..eaf4a992796 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -328,6 +328,38 @@ class MockGetPathTests(unittest.TestCase): actual = getpath(ns, expected) self.assertEqual(expected, actual) + def test_venv_changed_name_posix(self): + "Test a venv layout on *nix." + ns = MockPosixNamespace( + argv0="python", + PREFIX="/usr", + ENV_PATH="/venv/bin:/usr/bin", + ) + ns.add_known_xfile("/usr/bin/python3") + ns.add_known_xfile("/venv/bin/python") + ns.add_known_link("/venv/bin/python", "/usr/bin/python3") + 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/python3", + 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/2021-12-16-15-04-58.bpo-46028.zfWacB.rst b/Misc/NEWS.d/next/Core and Builtins/2021-12-16-15-04-58.bpo-46028.zfWacB.rst new file mode 100644 index 00000000000..cc34c0fa240 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-12-16-15-04-58.bpo-46028.zfWacB.rst @@ -0,0 +1,3 @@ +Fixes calculation of :data:`sys._base_executable` when inside a virtual +environment that uses symlinks with different binary names than the base +environment provides. diff --git a/Modules/getpath.py b/Modules/getpath.py index 6f2e0385577..f84e6e8afaf 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -351,7 +351,18 @@ if not home and not py_setpath: key, had_equ, value = line.partition('=') if had_equ and key.strip().lower() == 'home': executable_dir = real_executable_dir = value.strip() - base_executable = joinpath(executable_dir, basename(executable)) + if not base_executable: + # First try to resolve symlinked executables, since that may be + # more accurate than assuming the executable in 'home'. + try: + base_executable = realpath(executable) + if base_executable == executable: + # No change, so probably not a link. Clear it and fall back + base_executable = '' + except OSError: + pass + if not base_executable: + base_executable = joinpath(executable_dir, basename(executable)) break else: venv_prefix = None