Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions Lib/test/test_getpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,109 @@ def test_venv_symlink_with_misleading_landmarks_posix(self):
actual = getpath(ns, expected)
self.assertEqual(expected, actual)

def test_venv_symlink_to_launcher_outside_runtime_posix(self):
"Test a venv whose base executable resolves away from its stdlib."
ns = MockPosixNamespace(
argv0="/venv/bin/python",
PREFIX="/usr",
)
ns.add_known_xfile("/out/launcher")
ns.add_known_xfile("/runtime/bin/python")
ns.add_known_link("/runtime/bin/python", "/out/launcher")
ns.add_known_xfile("/venv/bin/python")
ns.add_known_link("/venv/bin/python", "/runtime/bin/python")
ns.add_known_file("/runtime/lib/python9.8/os.py")
ns.add_known_dir("/runtime/lib/python9.8/lib-dynload")
ns.add_known_file("/venv/pyvenv.cfg", [
"home = /runtime/bin",
])
expected = dict(
executable="/venv/bin/python",
prefix="/venv",
exec_prefix="/venv",
base_executable="/out/launcher",
base_prefix="/runtime",
base_exec_prefix="/runtime",
module_search_paths_set=1,
module_search_paths=[
"/runtime/lib/python98.zip",
"/runtime/lib/python9.8",
"/runtime/lib/python9.8/lib-dynload",
],
)
actual = getpath(ns, expected)
self.assertEqual(expected, actual)

def test_venv_symlink_to_separate_exec_prefix_posix(self):
"Test a venv whose base executable lives under a separate exec_prefix."
ns = MockPosixNamespace(
argv0="/venv/bin/python",
PREFIX="/usr",
EXEC_PREFIX="/usr",
)
ns.add_known_xfile("/exec/bin/python")
ns.add_known_xfile("/runtime/bin/python")
ns.add_known_link("/runtime/bin/python", "/exec/bin/python")
ns.add_known_xfile("/venv/bin/python")
ns.add_known_link("/venv/bin/python", "/runtime/bin/python")
ns.add_known_file("/runtime/lib/python9.8/os.py")
ns.add_known_dir("/exec/lib/python9.8/lib-dynload")
ns.add_known_file("/venv/pyvenv.cfg", [
"home = /runtime/bin",
])
expected = dict(
executable="/venv/bin/python",
prefix="/venv",
exec_prefix="/venv",
base_executable="/exec/bin/python",
base_prefix="/runtime",
base_exec_prefix="/exec",
module_search_paths_set=1,
module_search_paths=[
"/runtime/lib/python98.zip",
"/runtime/lib/python9.8",
"/exec/lib/python9.8/lib-dynload",
],
)
actual = getpath(ns, expected)
self.assertEqual(expected, actual)

def test_venv_separate_exec_prefix_with_misleading_home_posix(self):
"Test that a stale lib-dynload near home does not override the base executable."
ns = MockPosixNamespace(
argv0="/venv/bin/python",
PREFIX="/stdlib",
EXEC_PREFIX="/build/exec",
)
ns.add_known_xfile("/exec/bin/python")
ns.add_known_xfile("/public/bin/python")
ns.add_known_link("/public/bin/python", "/exec/bin/python")
ns.add_known_xfile("/venv/bin/python")
ns.add_known_link("/venv/bin/python", "/public/bin/python")
ns.add_known_file("/stdlib/lib/python9.8/os.py")
ns.add_known_dir("/exec/lib/python9.8/lib-dynload")
# An empty directory near the symlink must not win.
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="/exec/bin/python",
base_prefix="/stdlib",
base_exec_prefix="/exec",
module_search_paths_set=1,
module_search_paths=[
"/stdlib/lib/python98.zip",
"/stdlib/lib/python9.8",
"/exec/lib/python9.8/lib-dynload",
],
)
actual = getpath(ns, expected)
self.assertEqual(expected, actual)

Comment thread
itamaro marked this conversation as resolved.
def test_venv_non_installed_zip_path_posix(self):
"Test a venv created from non-installed python has correct zip path."""
ns = MockPosixNamespace(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix :mod:`venv` path discovery when the base executable resolves to a
location outside the installation pointed to by the ``home`` key in
``pyvenv.cfg``. The standard library is now found from ``home``, and
extension modules from the resolved executable's installation, instead of
falling back to the compiled-in prefix.
18 changes: 17 additions & 1 deletion Modules/getpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ def search_up(prefix, *landmarks, test=isfile):
stdlib_dir_was_set_in_config = bool(stdlib_dir)

real_executable_dir = None
base_executable_dir = None
platstdlib_dir = None
stdlib_zip = None

Expand Down Expand Up @@ -412,7 +413,17 @@ def search_up(prefix, *landmarks, test=isfile):
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)
# If nothing there locates a standard library (e.g. a
# launcher binary stored outside the installation that
# 'home' points into), keep searching from 'home'.
# base_executable_dir is still used to find platform
# dependent libraries (exec_prefix) in that case.
base_executable_dir = dirname(base_executable)
if (
isfile(joinpath(base_executable_dir, BUILDDIR_TXT))
Comment thread
ambv marked this conversation as resolved.
or search_up(base_executable_dir, ZIP_LANDMARK, *STDLIB_LANDMARKS)
):
executable_dir = real_executable_dir = base_executable_dir
# home key found; stop iterating over lines
break

Expand Down Expand Up @@ -622,6 +633,11 @@ def search_up(prefix, *landmarks, test=isfile):
exec_prefix = prefix
if not exec_prefix and prefix and isdir(joinpath(prefix, PLATSTDLIB_LANDMARK)):
exec_prefix = prefix
if not exec_prefix and base_executable_dir and base_executable_dir != executable_dir:
# The venv's resolved base executable may live under a separate
# exec_prefix (e.g. ./configure --exec-prefix) even when the
# stdlib was found from 'home'.
exec_prefix = search_up(base_executable_dir, PLATSTDLIB_LANDMARK, test=isdir)
if not exec_prefix and executable_dir:
exec_prefix = search_up(executable_dir, PLATSTDLIB_LANDMARK, test=isdir)
if not exec_prefix and EXEC_PREFIX:
Expand Down
Loading