Skip to content
Draft
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
111 changes: 110 additions & 1 deletion Lib/test/test_getpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,115 @@ def test_normal_posix(self):
actual = getpath(ns, expected)
self.assertEqual(expected, actual)

def test_shared_library_posix(self):
for os_name in ('posix', 'darwin'):
for platlibdir in ('lib', 'lib64'):
for argv0 in ('missing-python', '/application/bin/host'):
with self.subTest(os_name=os_name, platlibdir=platlibdir,
argv0=argv0):
ns = MockPosixNamespace(
os_name=os_name,
PREFIX='/configured',
EXEC_PREFIX='/configured',
PLATLIBDIR=platlibdir,
argv0=argv0,
library=f'/moved/bin/../{platlibdir}/libpython9.8.so',
)
ns.add_known_xfile('/application/bin/host')
for prefix in ('/configured', '/moved'):
ns.add_known_file(
f'{prefix}/{platlibdir}/python9.8/os.py')
ns.add_known_dir(
f'{prefix}/{platlibdir}/python9.8/lib-dynload')
expected = dict(
prefix='/moved',
exec_prefix='/moved',
base_prefix='/moved',
base_exec_prefix='/moved',
module_search_paths=[
f'/moved/{platlibdir}/python98.zip',
f'/moved/{platlibdir}/python9.8',
f'/moved/{platlibdir}/python9.8/lib-dynload',
],
)
self.assertEqual(expected, getpath(ns, expected))

def test_shared_library_home_posix(self):
ns = MockPosixNamespace(
PREFIX='/configured',
argv0='missing-python',
library='/moved/lib/libpython9.8.so',
ENV_PYTHONHOME='/pythonhome',
)
for prefix in ('/moved', '/pythonhome'):
ns.add_known_file(f'{prefix}/lib/python9.8/os.py')
ns.add_known_dir(f'{prefix}/lib/python9.8/lib-dynload')
expected = dict(
prefix='/pythonhome',
exec_prefix='/pythonhome',
base_prefix='/pythonhome',
base_exec_prefix='/pythonhome',
)
self.assertEqual(expected, getpath(ns, expected))

def test_shared_library_relative_path_posix(self):
def abspath(path):
return posixpath.normpath(posixpath.join('/foreign/work', path))

for library in ('libpython9.8.so', 'lib/libpython9.8.so'):
with self.subTest(library=library):
ns = MockPosixNamespace(
argv0='/moved/bin/python',
library=library,
abspath=abspath,
)
ns.add_known_xfile('/moved/bin/python')
for prefix in ('/moved', '/foreign'):
ns.add_known_file(f'{prefix}/lib/python9.8/os.py')
ns.add_known_dir(f'{prefix}/lib/python9.8/lib-dynload')
expected = dict(prefix='/moved', exec_prefix='/moved')
self.assertEqual(expected, getpath(ns, expected))

def test_shared_library_without_cwd_posix(self):
def abspath(path):
raise FileNotFoundError('working directory has been removed')

ns = MockPosixNamespace(
PREFIX='/configured',
argv0='python',
config={'executable': '/moved/bin/python'},
library='libpython9.8.so',
abspath=abspath,
)
ns.add_known_xfile('/moved/bin/python')
ns.add_known_file('/moved/lib/python9.8/os.py')
ns.add_known_dir('/moved/lib/python9.8/lib-dynload')
expected = dict(prefix='/moved', exec_prefix='/moved')
self.assertEqual(expected, getpath(ns, expected))

def test_shared_library_venv_posix(self):
ns = MockPosixNamespace(
PREFIX='/configured',
argv0='python',
ENV_PATH='/venv/bin:/moved/bin',
library='/moved/lib/libpython9.8.so',
)
ns.add_known_xfile('/moved/bin/python')
ns.add_known_xfile('/venv/bin/python')
ns.add_known_link('/venv/bin/python', '/moved/bin/python')
ns.add_known_file('/moved/lib/python9.8/os.py')
ns.add_known_dir('/moved/lib/python9.8/lib-dynload')
ns.add_known_file('/venv/pyvenv.cfg', ['home = /moved/bin'])
expected = dict(
executable='/venv/bin/python',
base_executable='/moved/bin/python',
prefix='/venv',
exec_prefix='/venv',
base_prefix='/moved',
base_exec_prefix='/moved',
)
self.assertEqual(expected, getpath(ns, expected))

def test_buildpath_posix(self):
"""Test an in-build-tree layout on POSIX.

Expand Down Expand Up @@ -1328,7 +1437,7 @@ def __missing__(self, key):

def abspath(self, path):
if self.isabs(path):
return path
return posixpath.normpath(path)
return self.joinpath("/Absolute", path)

def basename(self, path):
Expand Down
15 changes: 14 additions & 1 deletion Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,17 @@ def test_zippath_from_non_installed_posix(self):
ld_library_path = sysconfig.get_config_var("LIBDIR")
if not ld_library_path or sysconfig.is_python_build():
ld_library_path = os.path.abspath(os.path.dirname(sys.executable))
if (sysconfig.get_config_var('Py_ENABLE_SHARED')
and sys.platform != 'cygwin'):
# Startup can search relative to libpython before the executable.
# Load a copy from the temporary installation to select its
# standard library.
shared_libdir = os.path.join(non_installed_dir, 'lib')
os.makedirs(shared_libdir, exist_ok=True)
instsoname = sysconfig.get_config_var('INSTSONAME')
shutil.copy2(os.path.join(ld_library_path, instsoname),
os.path.join(shared_libdir, instsoname))
ld_library_path = os.pathsep.join((shared_libdir, ld_library_path))
if sys.platform == 'darwin':
ld_library_path_env = "DYLD_LIBRARY_PATH"
else:
Expand All @@ -1154,7 +1165,9 @@ def test_zippath_from_non_installed_posix(self):
# correct zip path in pythonpath.
target_python = os.path.join(self.env_dir, self.bindir, python_exe)
cmd = [target_python, '-S', '-c', 'import sys; print(sys.path)']
out, err = check_output(cmd)
with EnvironmentVarGuard() as env:
env[ld_library_path_env] = ld_library_path
out, err = check_output(cmd)
self.assertTrue(zip_landmark.encode() in out)

@requireVenvCreate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
A relocated POSIX shared build can now find its unpacked standard library at
startup when a launcher's ``argv[0]`` points outside the installation,
provided the loader reports an absolute path to ``libpython``.
12 changes: 8 additions & 4 deletions Modules/getpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ def search_up(prefix, *landmarks, test=isfile):
stdlib_dir = None


# First try to detect prefix by looking alongside our runtime library, if known
# First try to detect prefix by searching from our runtime library, if known.
if library and not prefix:
library_dir = dirname(library)
if ZIP_LANDMARK:
Expand All @@ -588,10 +588,14 @@ def search_up(prefix, *landmarks, test=isfile):
else:
prefix = search_up(library_dir, ZIP_LANDMARK)
if STDLIB_SUBDIR and STDLIB_LANDMARKS and not prefix:
if any(isfile(joinpath(library_dir, f)) for f in STDLIB_LANDMARKS):
# A relative loader path may have been resolved against an earlier
# working directory.
if os_name != 'nt' and isabs(library):
prefix = search_up(dirname(abspath(library)), *STDLIB_LANDMARKS)
elif any(isfile(joinpath(library_dir, f)) for f in STDLIB_LANDMARKS):
prefix = library_dir
if not stdlib_dir_was_set_in_config:
stdlib_dir = joinpath(prefix, STDLIB_SUBDIR)
if prefix and not stdlib_dir_was_set_in_config:
stdlib_dir = joinpath(prefix, STDLIB_SUBDIR)


# Detect prefix by looking for zip file
Expand Down
Loading