From 776991b44cc7d6b125937a17cd521be725f09a09 Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Thu, 22 Oct 2026 22:51:05 +0000 Subject: [PATCH] gh-119696: Support copied virtual environments in relocatable builds --- Doc/using/configure.rst | 4 ++++ Lib/test/test_venv.py | 9 ++++++++- Lib/venv/__init__.py | 14 ++++++++++++++ .../2026-10-21-20-23-00.gh-issue-119696.v3NvCp.rst | 4 ++++ configure | 4 ++-- configure.ac | 4 ++-- 6 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-21-20-23-00.gh-issue-119696.v3NvCp.rst diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index ba07babe6f1271b..203b04e3b0e1943 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -1090,6 +1090,10 @@ Linker options a different directory name for the standard library, but not a nested path. On Linux, these paths use ``DT_RPATH``. + Copied virtual environments from non-framework shared builds require + directory symlinks to the base installation's libraries. Virtual environments + depend on their base installation and must be recreated if it moves. + Dependencies stored in other directories, such as ``lib/tcltk``, may need additional loader search paths. diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 2f30d3108021dc6..f484dfdc29653d7 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -1136,6 +1136,9 @@ def test_zippath_from_non_installed_posix(self): # LD_LIBRARY_PATH so the non-installed python can find and link # libpython.so ld_library_path = sysconfig.get_config_var("LIBDIR") + if (sysconfig.get_config_var("RELOCATABLE") == "yes" + and not sysconfig.get_config_var("PYTHONFRAMEWORK")): + ld_library_path = os.path.join(sys.base_exec_prefix, "lib") if not ld_library_path or sysconfig.is_python_build(): ld_library_path = os.path.abspath(os.path.dirname(sys.executable)) if sys.platform == 'darwin': @@ -1154,7 +1157,11 @@ 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) + # This interpreter also depends on libraries outside the incomplete + # installation used by this test. + 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 diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 38e1bfe0c5fdb9d..b4f1cbd6e1e55e8 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -310,6 +310,20 @@ def setup_python(self, context): copier(context.executable, path) if not os.path.islink(path): os.chmod(path, 0o755) + if (sysconfig.get_config_var('RELOCATABLE') == 'yes' + and sysconfig.get_config_var('Py_ENABLE_SHARED') + and not sysconfig.get_config_var('PYTHONFRAMEWORK')): + # The copied executable searches relative to this environment. + # Link the base library directory so it can find libpython. + if sysconfig.is_python_build(): + library_dir = context.python_dir + else: + library_dir = os.path.join(sys.base_exec_prefix, 'lib') + library_link = os.path.join( + context.env_dir, 'lib', 'python-base') + if os.path.islink(library_link): + os.unlink(library_link) + os.symlink(library_dir, library_link, target_is_directory=True) for suffix in ('python', 'python3', f'python3.{sys.version_info[1]}'): path = os.path.join(binpath, suffix) diff --git a/Misc/NEWS.d/next/Library/2026-10-21-20-23-00.gh-issue-119696.v3NvCp.rst b/Misc/NEWS.d/next/Library/2026-10-21-20-23-00.gh-issue-119696.v3NvCp.rst new file mode 100644 index 000000000000000..5d7d1eae4a93967 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-20-23-00.gh-issue-119696.v3NvCp.rst @@ -0,0 +1,4 @@ +Support copied virtual environments from non-framework shared builds configured +with :option:`--enable-relocatable` by linking the base library directory into +the environment. Copied environments require filesystem support for directory +symlinks. diff --git a/configure b/configure index 391bae97f280319..e21f637fbfa2e68 100755 --- a/configure +++ b/configure @@ -28842,7 +28842,7 @@ esac # Keep the dollar sign through configure, make, and the shell. # DT_RPATH entries can also resolve indirect dependencies. - RELOCATABLE_PYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../lib'" + RELOCATABLE_PYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../lib:\$\$ORIGIN/../lib/python-base'" RELOCATABLE_LIBPYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN'" RELOCATABLE_EXTENSION_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../../../lib'" save_LDFLAGS=$LDFLAGS @@ -28872,7 +28872,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam \ Darwin) : RELOCATABLE_LIBPYTHON_INSTALL_NAME='@rpath/libpython$(LDVERSION).dylib' - RELOCATABLE_PYTHON_LDFLAGS='-Wl,-rpath,@executable_path/../lib' + RELOCATABLE_PYTHON_LDFLAGS='-Wl,-rpath,@executable_path/../lib -Wl,-rpath,@executable_path/../lib/python-base' RELOCATABLE_LIBPYTHON_LDFLAGS='-Wl,-rpath,@loader_path' RELOCATABLE_EXTENSION_LDFLAGS='-Wl,-rpath,@loader_path/../../../lib' ;; #( diff --git a/configure.ac b/configure.ac index 5a7fc0017a55635..47741e6e003ec2f 100644 --- a/configure.ac +++ b/configure.ac @@ -6901,7 +6901,7 @@ AS_IF([test "$enable_relocatable" = yes && test -z "$enable_framework"], [ [Linux], [ # Keep the dollar sign through configure, make, and the shell. # DT_RPATH entries can also resolve indirect dependencies. - RELOCATABLE_PYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../lib'" + RELOCATABLE_PYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../lib:\$\$ORIGIN/../lib/python-base'" RELOCATABLE_LIBPYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN'" RELOCATABLE_EXTENSION_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../../../lib'" save_LDFLAGS=$LDFLAGS @@ -6912,7 +6912,7 @@ AS_IF([test "$enable_relocatable" = yes && test -z "$enable_framework"], [ ], [Darwin], [ RELOCATABLE_LIBPYTHON_INSTALL_NAME='@rpath/libpython$(LDVERSION).dylib' - RELOCATABLE_PYTHON_LDFLAGS='-Wl,-rpath,@executable_path/../lib' + RELOCATABLE_PYTHON_LDFLAGS='-Wl,-rpath,@executable_path/../lib -Wl,-rpath,@executable_path/../lib/python-base' RELOCATABLE_LIBPYTHON_LDFLAGS='-Wl,-rpath,@loader_path' RELOCATABLE_EXTENSION_LDFLAGS='-Wl,-rpath,@loader_path/../../../lib' ])