Skip to content

Commit a16f183

Browse files
[3.15] gh-106045: Fix venv path discovery for symlinked executables (GH-156483) (#160002)
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. (cherry picked from commit e07817d) Co-authored-by: Jonathan J. Helmus <jjhelmus@gmail.com>
1 parent f005765 commit a16f183

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

‎Lib/test/test_getpath.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,74 @@ def test_venv_changed_name_posix(self):
407407
actual = getpath(ns, expected)
408408
self.assertEqual(expected, actual)
409409

410+
def test_venv_relocated_base_installation_posix(self):
411+
"Test a venv whose base installation was relocated from PREFIX."
412+
ns = MockPosixNamespace(
413+
argv0="/venv/bin/python",
414+
PREFIX="/build/install",
415+
)
416+
ns.add_known_xfile("/relocated/bin/python3")
417+
ns.add_known_xfile("/public/bin/python")
418+
ns.add_known_link("/public/bin/python", "/relocated/bin/python3")
419+
ns.add_known_xfile("/venv/bin/python")
420+
ns.add_known_link("/venv/bin/python", "/public/bin/python")
421+
ns.add_known_file("/relocated/lib/python9.8/os.py")
422+
ns.add_known_dir("/relocated/lib/python9.8/lib-dynload")
423+
ns.add_known_file("/venv/pyvenv.cfg", [
424+
"home = /public/bin",
425+
])
426+
expected = dict(
427+
executable="/venv/bin/python",
428+
prefix="/venv",
429+
exec_prefix="/venv",
430+
base_executable="/relocated/bin/python3",
431+
base_prefix="/relocated",
432+
base_exec_prefix="/relocated",
433+
module_search_paths_set=1,
434+
module_search_paths=[
435+
"/relocated/lib/python98.zip",
436+
"/relocated/lib/python9.8",
437+
"/relocated/lib/python9.8/lib-dynload",
438+
],
439+
)
440+
actual = getpath(ns, expected)
441+
self.assertEqual(expected, actual)
442+
443+
def test_venv_symlink_with_misleading_landmarks_posix(self):
444+
"Test that landmarks near an external symlink do effect resolution."
445+
ns = MockPosixNamespace(
446+
argv0="/venv/bin/python",
447+
PREFIX="/opt/python",
448+
)
449+
ns.add_known_xfile("/opt/python/bin/python3")
450+
ns.add_known_xfile("/public/bin/python")
451+
ns.add_known_link("/public/bin/python", "/opt/python/bin/python3")
452+
ns.add_known_xfile("/venv/bin/python")
453+
ns.add_known_link("/venv/bin/python", "/public/bin/python")
454+
ns.add_known_file("/opt/python/lib/python9.8/os.py")
455+
ns.add_known_dir("/opt/python/lib/python9.8/lib-dynload")
456+
ns.add_known_file("/public/lib/python9.8/os.py")
457+
ns.add_known_dir("/public/lib/python9.8/lib-dynload")
458+
ns.add_known_file("/venv/pyvenv.cfg", [
459+
"home = /public/bin",
460+
])
461+
expected = dict(
462+
executable="/venv/bin/python",
463+
prefix="/venv",
464+
exec_prefix="/venv",
465+
base_executable="/opt/python/bin/python3",
466+
base_prefix="/opt/python",
467+
base_exec_prefix="/opt/python",
468+
module_search_paths_set=1,
469+
module_search_paths=[
470+
"/opt/python/lib/python98.zip",
471+
"/opt/python/lib/python9.8",
472+
"/opt/python/lib/python9.8/lib-dynload",
473+
],
474+
)
475+
actual = getpath(ns, expected)
476+
self.assertEqual(expected, actual)
477+
410478
def test_venv_non_installed_zip_path_posix(self):
411479
"Test a venv created from non-installed python has correct zip path."""
412480
ns = MockPosixNamespace(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix standard library discovery in virtual environments created from a Python executable symlink located outside its base installation.

‎Modules/getpath.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,10 @@ def search_up(prefix, *landmarks, test=isfile):
409409
if isfile(candidate):
410410
base_executable = candidate
411411
break
412+
if base_executable and isfile(base_executable):
413+
# Search relative to the resolved base executable rather
414+
# than the potentially external symlink in pyvenv.cfg.
415+
executable_dir = real_executable_dir = dirname(base_executable)
412416
# home key found; stop iterating over lines
413417
break
414418

0 commit comments

Comments
 (0)