Skip to content

Commit 012edd0

Browse files
committed
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.
1 parent 98bd716 commit 012edd0

2 files changed

Lines changed: 72 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(

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)