Skip to content

Commit 620ac1e

Browse files
itamaroclaude
andcommitted
gh-106045: Keep searching from venv home when the resolved base executable has no stdlib
GH-156483 searches for a venv's base installation relative to the resolved base executable. When that executable lives outside the installation tree that 'home' in pyvenv.cfg points into (e.g. a launcher binary symlinked into it), nothing is found there and discovery falls back to the compiled-in PREFIX. Only prefer the resolved executable's directory when a standard library landmark is found from there; otherwise keep searching from 'home'. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A93JzpSGH27i6PNX8re4YV
1 parent 0a86a34 commit 620ac1e

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

‎Lib/test/test_getpath.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,39 @@ def test_venv_symlink_with_misleading_landmarks_posix(self):
475475
actual = getpath(ns, expected)
476476
self.assertEqual(expected, actual)
477477

478+
def test_venv_symlink_to_launcher_outside_runtime_posix(self):
479+
"Test a venv whose base executable resolves away from its stdlib."
480+
ns = MockPosixNamespace(
481+
argv0="/venv/bin/python",
482+
PREFIX="/usr",
483+
)
484+
ns.add_known_xfile("/out/launcher")
485+
ns.add_known_xfile("/runtime/bin/python")
486+
ns.add_known_link("/runtime/bin/python", "/out/launcher")
487+
ns.add_known_xfile("/venv/bin/python")
488+
ns.add_known_link("/venv/bin/python", "/runtime/bin/python")
489+
ns.add_known_file("/runtime/lib/python9.8/os.py")
490+
ns.add_known_dir("/runtime/lib/python9.8/lib-dynload")
491+
ns.add_known_file("/venv/pyvenv.cfg", [
492+
"home = /runtime/bin",
493+
])
494+
expected = dict(
495+
executable="/venv/bin/python",
496+
prefix="/venv",
497+
exec_prefix="/venv",
498+
base_executable="/out/launcher",
499+
base_prefix="/runtime",
500+
base_exec_prefix="/runtime",
501+
module_search_paths_set=1,
502+
module_search_paths=[
503+
"/runtime/lib/python98.zip",
504+
"/runtime/lib/python9.8",
505+
"/runtime/lib/python9.8/lib-dynload",
506+
],
507+
)
508+
actual = getpath(ns, expected)
509+
self.assertEqual(expected, actual)
510+
478511
def test_venv_non_installed_zip_path_posix(self):
479512
"Test a venv created from non-installed python has correct zip path."""
480513
ns = MockPosixNamespace(

‎Modules/getpath.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,15 @@ def search_up(prefix, *landmarks, test=isfile):
412412
if base_executable and isfile(base_executable):
413413
# Search relative to the resolved base executable rather
414414
# than the potentially external symlink in pyvenv.cfg.
415-
executable_dir = real_executable_dir = dirname(base_executable)
415+
# If nothing there locates a standard library (e.g. a
416+
# launcher binary stored outside the installation that
417+
# 'home' points into), keep searching from 'home'.
418+
base_executable_dir = dirname(base_executable)
419+
if (
420+
isfile(joinpath(base_executable_dir, BUILDDIR_TXT))
421+
or search_up(base_executable_dir, ZIP_LANDMARK, *STDLIB_LANDMARKS)
422+
):
423+
executable_dir = real_executable_dir = base_executable_dir
416424
# home key found; stop iterating over lines
417425
break
418426

0 commit comments

Comments
 (0)