From 012edd02fb9d86ed0f4c779bb9ca1871e0e06f08 Mon Sep 17 00:00:00 2001 From: "Jonathan J. Helmus" Date: Thu, 27 Aug 2026 11:26:22 -0500 Subject: [PATCH 1/2] Fix venv path discovery for symlinked executables Use the resolved base executable when locating the base installation instead of relying on the home directory in pyvenv.cfg. Add tests covering relocated Python installations and misleading standard-library landmarks near executable symlinks. --- Lib/test/test_getpath.py | 68 ++++++++++++++++++++++++++++++++++++++++ Modules/getpath.py | 4 +++ 2 files changed, 72 insertions(+) diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index 83f09f3495547ac..2907abe4b4cee67 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -407,6 +407,74 @@ def test_venv_changed_name_posix(self): actual = getpath(ns, expected) self.assertEqual(expected, actual) + def test_venv_relocated_base_installation_posix(self): + "Test a venv whose base installation was relocated from PREFIX." + ns = MockPosixNamespace( + argv0="/venv/bin/python", + PREFIX="/build/install", + ) + ns.add_known_xfile("/relocated/bin/python3") + ns.add_known_xfile("/public/bin/python") + ns.add_known_link("/public/bin/python", "/relocated/bin/python3") + ns.add_known_xfile("/venv/bin/python") + ns.add_known_link("/venv/bin/python", "/public/bin/python") + ns.add_known_file("/relocated/lib/python9.8/os.py") + ns.add_known_dir("/relocated/lib/python9.8/lib-dynload") + ns.add_known_file("/venv/pyvenv.cfg", [ + "home = /public/bin", + ]) + expected = dict( + executable="/venv/bin/python", + prefix="/venv", + exec_prefix="/venv", + base_executable="/relocated/bin/python3", + base_prefix="/relocated", + base_exec_prefix="/relocated", + module_search_paths_set=1, + module_search_paths=[ + "/relocated/lib/python98.zip", + "/relocated/lib/python9.8", + "/relocated/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + + def test_venv_symlink_with_misleading_landmarks_posix(self): + "Test that landmarks near an external symlink do effect resolution." + ns = MockPosixNamespace( + argv0="/venv/bin/python", + PREFIX="/opt/python", + ) + ns.add_known_xfile("/opt/python/bin/python3") + ns.add_known_xfile("/public/bin/python") + ns.add_known_link("/public/bin/python", "/opt/python/bin/python3") + ns.add_known_xfile("/venv/bin/python") + ns.add_known_link("/venv/bin/python", "/public/bin/python") + ns.add_known_file("/opt/python/lib/python9.8/os.py") + ns.add_known_dir("/opt/python/lib/python9.8/lib-dynload") + ns.add_known_file("/public/lib/python9.8/os.py") + ns.add_known_dir("/public/lib/python9.8/lib-dynload") + ns.add_known_file("/venv/pyvenv.cfg", [ + "home = /public/bin", + ]) + expected = dict( + executable="/venv/bin/python", + prefix="/venv", + exec_prefix="/venv", + base_executable="/opt/python/bin/python3", + base_prefix="/opt/python", + base_exec_prefix="/opt/python", + module_search_paths_set=1, + module_search_paths=[ + "/opt/python/lib/python98.zip", + "/opt/python/lib/python9.8", + "/opt/python/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + def test_venv_non_installed_zip_path_posix(self): "Test a venv created from non-installed python has correct zip path.""" ns = MockPosixNamespace( diff --git a/Modules/getpath.py b/Modules/getpath.py index c322d5c4b4c251d..f5f928f1795de53 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -409,6 +409,10 @@ def search_up(prefix, *landmarks, test=isfile): if isfile(candidate): base_executable = candidate break + if base_executable and isfile(base_executable): + # Search relative to the resolved base executable rather + # than the potentially external symlink in pyvenv.cfg. + executable_dir = real_executable_dir = dirname(base_executable) # home key found; stop iterating over lines break From 6dc5d6512fdf4cf52ab0f705e1745b7e889d9c1a Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:26:23 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-08-27-17-26-21.gh-issue-106045.wzTH7f.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-17-26-21.gh-issue-106045.wzTH7f.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-17-26-21.gh-issue-106045.wzTH7f.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-17-26-21.gh-issue-106045.wzTH7f.rst new file mode 100644 index 000000000000000..a092b72d2ecdaa9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-17-26-21.gh-issue-106045.wzTH7f.rst @@ -0,0 +1 @@ +Fix standard library discovery in virtual environments created from a Python executable symlink located outside its base installation.