From 5fe0a39b66c8bd57aa60ac72f4415532e6b39dab Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 24 Jun 2025 16:28:13 -0700 Subject: [PATCH] feat: relative venv home path --- Lib/test/test_getpath.py | 36 ++++++++++++++++++++++++++++++++++++ Modules/getpath.py | 5 ++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index f86df9d0d03485..01b8179358844c 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -354,6 +354,42 @@ def test_venv_posix(self): actual = getpath(ns, expected) self.assertEqual(expected, actual) + def test_venv_relative_home(self): + ns = MockPosixNamespace( + argv0="/somedir/venv/bin/python3", + PREFIX="/usr/local-fallback", + ENV_PATH="/usr/bin", + ) + + ns.add_known_xfile("/somedir/runtime/bin/python3") + ns.add_known_file("/somedir/runtime/lib/python9.8/os.py") + ns.add_known_dir("/somedir/runtime/lib/python9.8/lib-dynload") + + ns.add_known_xfile("/somedir/venv/bin/python3") + # NOTE: Normally a relative symlink would be used, but the mock + # realpath() doesn't handle relative symlinks, so point it to + # where it ultimately would resolve to. + ns.add_known_link("/somedir/venv/bin/python3", "/somedir/runtime/bin/python3") + ns.add_known_file("/somedir/venv/pyvenv.cfg", [ + "home = ../runtime/bin" + ]) + expected = dict( + executable="/somedir/venv/bin/python3", + prefix="/somedir/venv", + exec_prefix="/somedir/venv", + base_executable="/somedir/runtime/bin/python3", + base_prefix="/somedir/runtime", + base_exec_prefix="/somedir/runtime", + module_search_paths_set=1, + module_search_paths=[ + "/somedir/runtime/lib/python98.zip", + "/somedir/runtime/lib/python9.8", + "/somedir/runtime/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + def test_venv_changed_name_posix(self): "Test a venv layout on *nix." ns = MockPosixNamespace( diff --git a/Modules/getpath.py b/Modules/getpath.py index be2210345afbda..03a3a6b27e71ba 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -374,10 +374,13 @@ def search_up(prefix, *landmarks, test=isfile): # If PYTHONHOME was set, ignore 'home' from pyvenv.cfg. if home: break + value = value.strip() + if not isabs(value): + value = realpath(joinpath(venv_prefix, value)) # Override executable_dir/real_executable_dir with the value from 'home'. # These values may be later used to calculate prefix/base_prefix, if a more # reliable source — like the runtime library (libpython) path — isn't available. - executable_dir = real_executable_dir = value.strip() + executable_dir = real_executable_dir = value # If base_executable — which points to the Python interpreted from # the base installation — isn't set (eg. when embedded), try to find # it in 'home'.