From 34efc10391de4e07f7e82cebb00d57132712cbfc Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Wed, 21 Oct 2026 17:57:34 +0000 Subject: [PATCH 1/6] Add an opt-in relocatable CPython build mode --- cpython-unix/build-cpython.sh | 68 ++++++---- ...atch-macos-link-extension-modules-15.patch | 11 ++ cpython-unix/patch-relocatable-flag.patch | 90 +++++++++++++ cpython-unix/patch-relocatable-link.patch | 118 ++++++++++++++++++ pythonbuild/disttests/__init__.py | 63 ++++++++++ 5 files changed, 325 insertions(+), 25 deletions(-) create mode 100644 cpython-unix/patch-macos-link-extension-modules-15.patch create mode 100644 cpython-unix/patch-relocatable-flag.patch create mode 100644 cpython-unix/patch-relocatable-link.patch diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 8dd0240b8..a99215331 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -82,6 +82,13 @@ rm Lib/ensurepip/_bundled/pip-*.whl cp "${PIP_WHEEL}" Lib/ensurepip/_bundled/ sed "${sed_args[@]}" "s/^_PIP_VERSION = .*/_PIP_VERSION = \"${PIP_VERSION}\"/" Lib/ensurepip/__init__.py +# Build relative runtime library paths into CPython 3.15+. +if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_15}" ]; then + patch -p1 -i "${ROOT}/patch-relocatable-flag.patch" + patch -p1 -i "${ROOT}/patch-relocatable-link.patch" + EXTRA_CONFIGURE_FLAGS="${EXTRA_CONFIGURE_FLAGS} --enable-relocatable" +fi + # configure doesn't support cross-compiling on Apple. Teach it. if [[ "${PYBUILD_PLATFORM}" = macos* && -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_13}" ]]; then if [ "${PYTHON_MAJMIN_VERSION}" = "3.12" ]; then @@ -152,7 +159,9 @@ fi # The default build rule for the macOS dylib doesn't pick up libraries # from modules / makesetup. So patch it accordingly. -if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_13}" ]; then +if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_15}" ]; then + patch -p1 -i "${ROOT}/patch-macos-link-extension-modules-15.patch" +elif [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_13}" ]; then patch -p1 -i "${ROOT}/patch-macos-link-extension-modules-13.patch" else patch -p1 -i "${ROOT}/patch-macos-link-extension-modules.patch" @@ -818,18 +827,22 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then # Fix the Python binary to reference libpython via @rpath and add # an rpath entry so it can find the library. - install_name_tool \ - -change "/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" "@rpath/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \ - -add_rpath @executable_path/../lib \ - "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}" + if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]; then + install_name_tool \ + -change "/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" "@rpath/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \ + -add_rpath @executable_path/../lib \ + "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}" + fi # Python's build system doesn't make this file writable. chmod 755 "${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" # Set libpython's install name to @rpath so binaries linking against it # can locate it via their own rpath entries. - install_name_tool \ - -id "@rpath/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \ - "${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" + if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]; then + install_name_tool \ + -id "@rpath/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \ + "${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" + fi # We also normalize /tools/deps/lib/libz.1.dylib to the system location. install_name_tool \ @@ -839,19 +852,19 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then -change /tools/deps/lib/libz.1.dylib /usr/lib/libz.1.dylib \ "${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" - if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then + if [[ -n "${PYTHON_BINARY_SUFFIX}" && -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]]; then install_name_tool \ -change "/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" "@rpath/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \ -add_rpath @executable_path/../lib \ "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}" fi - # At the moment, python3 and libpython don't have shared-library - # dependencies, but at some point we will want to run this for - # them too. - for module in ${ROOT}/out/python/install/lib/python*/lib-dynload/*.so; do - install_name_tool -add_rpath @loader_path/../.. "$module" - done + # CPython 3.15+ supplies extension search paths at link time. + if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]; then + for module in ${ROOT}/out/python/install/lib/python*/lib-dynload/*.so; do + install_name_tool -add_rpath @loader_path/../.. "$module" + done + fi else # (not macos) LIBPYTHON_SHARED_LIBRARY_BASENAME=libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.so.1.0 LIBPYTHON_SHARED_LIBRARY=${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} @@ -917,12 +930,14 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then # empty dummy libpython.so, which allows the link to succeed but # ensures they do not use any unwanted symbols. That might be # worth doing at some point.) - patchelf --force-rpath --set-rpath "\$ORIGIN/../lib" \ - "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}" - - if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then + if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]; then patchelf --force-rpath --set-rpath "\$ORIGIN/../lib" \ - "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}" + "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}" + + if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then + patchelf --force-rpath --set-rpath "\$ORIGIN/../lib" \ + "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}" + fi fi # For libpython3.so (the ABI3 library for embedders), we do @@ -941,8 +956,9 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then # libpython3.x.so.1.0 that they want (and it's fine to use # DT_RUNPATH instead of DT_RPATH). if [ "${CC}" == "musl-clang" ]; then - # libpython3.so isn't present in debug builds. - if [ -z "${CPYTHON_DEBUG}" ]; then + # CPython 3.15+ supplies this path at link time. libpython3.so + # is absent from debug builds. + if [[ -z "${CPYTHON_DEBUG}" && -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]]; then patchelf --set-rpath "\$ORIGIN/../lib" \ "${ROOT}/out/python/install/lib/libpython3.so" fi @@ -956,9 +972,11 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then # Let _tkinter locate bundled Tcl/Tk libraries independently of the # interpreter. - for module in "${ROOT}"/out/python/install/lib/python*/lib-dynload/_tkinter*.so; do - patchelf --force-rpath --set-rpath "\$ORIGIN/../.." "$module" - done + if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]; then + for module in "${ROOT}"/out/python/install/lib/python*/lib-dynload/_tkinter*.so; do + patchelf --force-rpath --set-rpath "\$ORIGIN/../.." "$module" + done + fi fi fi diff --git a/cpython-unix/patch-macos-link-extension-modules-15.patch b/cpython-unix/patch-macos-link-extension-modules-15.patch new file mode 100644 index 000000000..49de58e1e --- /dev/null +++ b/cpython-unix/patch-macos-link-extension-modules-15.patch @@ -0,0 +1,11 @@ +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -1020,7 +1020,7 @@ + $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ + + libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) +- $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ + + + libpython$(VERSION).sl: $(LIBRARY_OBJS) diff --git a/cpython-unix/patch-relocatable-flag.patch b/cpython-unix/patch-relocatable-flag.patch new file mode 100644 index 000000000..2b2db41f7 --- /dev/null +++ b/cpython-unix/patch-relocatable-flag.patch @@ -0,0 +1,90 @@ +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 36515db..7dcfc45 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -60,6 +60,7 @@ DSYMUTIL= @DSYMUTIL@ + DSYMUTIL_PATH= @DSYMUTIL_PATH@ + + GNULD= @GNULD@ ++RELOCATABLE= @RELOCATABLE@ + + # Shell used by make (some versions default to the login shell, which is bad) + SHELL= /bin/sh -e +diff --git a/configure.ac b/configure.ac +index a8760c9..be7ad9c 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1594,6 +1594,26 @@ if test "x$enable_profiling" = xyes; then + LDFLAGS="-pg $LDFLAGS" + fi + ++AC_MSG_CHECKING([for --enable-relocatable]) ++AC_ARG_ENABLE([relocatable], ++ AS_HELP_STRING([--enable-relocatable], ++ [use relative runtime library paths (default is no)]), ++ [], [enable_relocatable=no]) ++AS_CASE([$enable_relocatable], ++ [yes|no], [], ++ [AC_MSG_ERROR([--enable-relocatable must be yes or no])]) ++AC_MSG_RESULT([$enable_relocatable]) ++AS_VAR_IF([enable_relocatable], [yes], [ ++ AS_CASE([$ac_sys_system], ++ [Linux|Darwin], [], ++ [AC_MSG_ERROR([--enable-relocatable is supported only on Linux and macOS])]) ++ AS_VAR_IF([enable_framework], [], [], ++ [AC_MSG_ERROR([--enable-relocatable does not support framework builds])]) ++ AC_DEFINE([Py_ENABLE_RELOCATABLE], [1], ++ [Define if --enable-relocatable is used.]) ++]) ++AC_SUBST([RELOCATABLE], [$enable_relocatable]) ++ + AC_MSG_CHECKING([LDLIBRARY]) + + # Apple framework builds need more magic. LDLIBRARY is the dynamic +diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst +index 0d420d8..e033256 100644 +--- a/Doc/using/configure.rst ++++ b/Doc/using/configure.rst +@@ -1073,6 +1073,28 @@ Linker options + + Enable building a shared Python library: ``libpython`` (default is no). + ++.. option:: --enable-relocatable ++ ++ Use relative runtime library paths for the interpreter, ``libpython``, ++ and standard-library extension modules (default is no). ++ ++ This option supports Linux and non-framework macOS builds. The installation ++ must use the default ``bin`` and ``lib`` directories under a common prefix. ++ :option:`--with-platlibdir` may select a different directory name for the ++ standard library, but not a nested path. ++ Move the installation as a unit, keeping these relative locations intact. ++ ++ On Linux, use ``DT_RPATH``; on glibc this gives bundled libraries precedence ++ over ``LD_LIBRARY_PATH``. On macOS, use an ``@rpath`` install name for ++ ``libpython`` and loader-relative paths for its dependencies and extension ++ modules. Applications embedding Python must ensure that the dynamic loader ++ can find ``libpython`` and that Python can find its standard library; this ++ option does not configure those paths for them. ++ ++ This option does not bundle third-party libraries or relocate their data ++ files. It does not rewrite installed script shebangs or build-time paths in ++ :mod:`sysconfig`. ++ + .. option:: --without-static-libpython + + Do not build ``libpythonMAJOR.MINOR.a`` and do not install ``python.o`` +diff --git a/pyconfig.h.in b/pyconfig.h.in +index 08306a1..4fb029a 100644 +--- a/pyconfig.h.in ++++ b/pyconfig.h.in +@@ -1880,6 +1880,9 @@ + /* Define if you want to build an interpreter with many run-time checks. */ + #undef Py_DEBUG + ++/* Define if --enable-relocatable is used. */ ++#undef Py_ENABLE_RELOCATABLE ++ + /* Defined if Python is built as a shared library. */ + #undef Py_ENABLE_SHARED + diff --git a/cpython-unix/patch-relocatable-link.patch b/cpython-unix/patch-relocatable-link.patch new file mode 100644 index 000000000..e1a70c8dc --- /dev/null +++ b/cpython-unix/patch-relocatable-link.patch @@ -0,0 +1,118 @@ +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 7dcfc45..93bc9bf 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -61,6 +61,10 @@ DSYMUTIL_PATH= @DSYMUTIL_PATH@ + + GNULD= @GNULD@ + RELOCATABLE= @RELOCATABLE@ ++RELOCATABLE_PYTHON_LDFLAGS=@RELOCATABLE_PYTHON_LDFLAGS@ ++RELOCATABLE_LIBPYTHON_LDFLAGS=@RELOCATABLE_LIBPYTHON_LDFLAGS@ ++RELOCATABLE_EXTENSION_LDFLAGS=@RELOCATABLE_EXTENSION_LDFLAGS@ ++LIBPYTHON_INSTALL_NAME=@LIBPYTHON_INSTALL_NAME@ + + # Shell used by make (some versions default to the login shell, which is bad) + SHELL= /bin/sh -e +@@ -286,7 +290,7 @@ DIST= $(DISTFILES) $(DISTDIRS) + LIBRARY= @LIBRARY@ + LDLIBRARY= @LDLIBRARY@ + BLDLIBRARY= @BLDLIBRARY@ +-MODULE_LDFLAGS_SHARED=$(if $(LIBPYTHON),$(BLDLIBRARY)) ++MODULE_LDFLAGS_SHARED=$(if $(LIBPYTHON),$(BLDLIBRARY)) $(RELOCATABLE_EXTENSION_LDFLAGS) + PY3LIBRARY= @PY3LIBRARY@ + DLLLIBRARY= @DLLLIBRARY@ + LDLIBRARYDIR= @LDLIBRARYDIR@ +@@ -972,7 +976,7 @@ clinic-tests: check-clean-src $(srcdir)/Lib/test/clinic.test.c + + # Build the interpreter + $(BUILDPYTHON): Programs/python.o $(LINK_PYTHON_DEPS) +- $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) ++ $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) $(RELOCATABLE_PYTHON_LDFLAGS) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) + + platform: $(PYTHON_FOR_BUILD_DEPS) pybuilddir.txt + $(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import sys ; from sysconfig import get_platform ; print("%s-%d.%d" % (get_platform(), *sys.version_info[:2]))' >platform +@@ -1004,7 +1008,7 @@ $(LIBRARY): $(LIBRARY_OBJS) + libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) + # AIX Linker don't support "-h" option + if test "$(MACHDEP)" != "aix"; then \ +- $(BLDSHARED) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ + else \ + $(BLDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ + fi +@@ -1013,10 +1017,10 @@ libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) + fi + + libpython3.so: libpython$(LDVERSION).so +- $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ ++ $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ + + libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) +- $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + + + libpython$(VERSION).sl: $(LIBRARY_OBJS) +@@ -1617,7 +1621,7 @@ regen-re: $(BUILDPYTHON) + $(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/generate_re_casefix.py $(srcdir)/Lib/re/_casefix.py + + Programs/_testembed: Programs/_testembed.o $(LINK_PYTHON_DEPS) +- $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) ++ $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) $(RELOCATABLE_PYTHON_LDFLAGS) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) + + ############################################################################ + # "Bootstrap Python" used to run Programs/_freeze_module.py +diff --git a/configure.ac b/configure.ac +index be7ad9c..785a41d 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -6879,6 +6879,49 @@ AC_SUBST([BINLIBDEST]) + LIBDEST='${prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' + BINLIBDEST='${exec_prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' + ++# Runtime paths are specific to the installed location of each kind of binary. ++# Do not add them to LDSHARED: third-party extensions may be installed elsewhere. ++RELOCATABLE_PYTHON_LDFLAGS= ++RELOCATABLE_LIBPYTHON_LDFLAGS= ++RELOCATABLE_EXTENSION_LDFLAGS= ++LIBPYTHON_INSTALL_NAME='$(prefix)/lib/libpython$(LDVERSION).dylib' ++AS_VAR_IF([enable_relocatable], [yes], [ ++ AS_VAR_IF([prefix], [NONE], [relocatable_prefix=$ac_default_prefix], ++ [relocatable_prefix=$prefix]) ++ AS_IF([test "$exec_prefix" != NONE && test "$exec_prefix" != '${prefix}' && test "$exec_prefix" != "$relocatable_prefix"], ++ [AC_MSG_ERROR([--enable-relocatable requires a common prefix and exec-prefix])]) ++ AS_IF([test "$bindir" != '${exec_prefix}/bin' && test "$bindir" != '${prefix}/bin' && test "$bindir" != "$relocatable_prefix/bin"], ++ [AC_MSG_ERROR([--enable-relocatable requires the default bin directory])]) ++ AS_IF([test "$libdir" != '${exec_prefix}/lib' && test "$libdir" != '${prefix}/lib' && test "$libdir" != "$relocatable_prefix/lib"], ++ [AC_MSG_ERROR([--enable-relocatable requires the default lib directory])]) ++ AS_CASE([$PLATLIBDIR], [*/*|.|..], ++ [AC_MSG_ERROR([--enable-relocatable requires a single directory name for --with-platlibdir])]) ++ AS_CASE([$ac_sys_system], ++ [Linux], [ ++ # Keep the dollar sign through configure, make, and the shell. DT_RPATH ++ # takes precedence over LD_LIBRARY_PATH on glibc and covers indirect ++ # dependencies. ++ RELOCATABLE_PYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../lib'" ++ RELOCATABLE_LIBPYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN'" ++ RELOCATABLE_EXTENSION_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../../../lib'" ++ save_LDFLAGS=$LDFLAGS ++ LDFLAGS="$LDFLAGS -Wl,--disable-new-dtags" ++ AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [], ++ [AC_MSG_ERROR([--enable-relocatable requires linker support for --disable-new-dtags])]) ++ LDFLAGS=$save_LDFLAGS ++ ], ++ [Darwin], [ ++ LIBPYTHON_INSTALL_NAME='@rpath/libpython$(LDVERSION).dylib' ++ RELOCATABLE_PYTHON_LDFLAGS='-Wl,-rpath,@executable_path/../lib' ++ RELOCATABLE_LIBPYTHON_LDFLAGS='-Wl,-rpath,@loader_path' ++ RELOCATABLE_EXTENSION_LDFLAGS='-Wl,-rpath,@loader_path/../../../lib' ++ ]) ++]) ++AC_SUBST([RELOCATABLE_PYTHON_LDFLAGS]) ++AC_SUBST([RELOCATABLE_LIBPYTHON_LDFLAGS]) ++AC_SUBST([RELOCATABLE_EXTENSION_LDFLAGS]) ++AC_SUBST([LIBPYTHON_INSTALL_NAME]) ++ + dnl define LIBPL after ABIFLAGS and LDVERSION is defined. + AC_SUBST([PY_ENABLE_SHARED]) + if test x$PLATFORM_TRIPLET = x; then diff --git a/pythonbuild/disttests/__init__.py b/pythonbuild/disttests/__init__.py index d6ad302b8..b076efaac 100644 --- a/pythonbuild/disttests/__init__.py +++ b/pythonbuild/disttests/__init__.py @@ -45,6 +45,69 @@ class TestPythonInterpreter(unittest.TestCase): + @unittest.skipUnless( + sys.platform in {"linux", "darwin"} and sys.version_info >= (3, 15), + "Relocatable build mode is enabled on Unix in Python 3.15+", + ) + def test_relocatable_build_mode(self): + import sysconfig + + self.assertEqual(sysconfig.get_config_var("RELOCATABLE"), "yes") + self.assertEqual(sysconfig.get_config_var("Py_ENABLE_RELOCATABLE"), 1) + + @unittest.skipUnless( + sys.platform in {"linux", "darwin"}, "Unix shared-library search paths" + ) + @unittest.skipIf( + "static" in os.environ["BUILD_OPTIONS"], + "Static builds have no shared libraries", + ) + def test_extension_libraries_without_executable_rpath(self): + # PBS links libpython statically into the interpreter. A copy outside the + # installation cannot supply the extension's library path through its rpath. + with tempfile.TemporaryDirectory(prefix="disttests-relocatable-") as temp_dir: + root = Path(temp_dir) + executable = root / "bin" / Path(sys.executable).name + executable.parent.mkdir() + shutil.copy2(sys.executable, executable) + env = { + key: value + for key, value in os.environ.items() + if not key.startswith(("PYTHON", "LD_", "DYLD_")) + } + env["PYTHONHOME"] = sys.base_prefix + + if "-linux-gnu" in os.environ["TARGET_TRIPLE"]: + # An incompatible ambient library must not override a bundled one. + poison = root / "poison" + poison.mkdir() + for pattern in ("libpython*.so*", "libtcl*.so*", "libtk*.so*"): + for library in (Path(sys.base_prefix) / "lib").glob(pattern): + (poison / library.name).write_bytes(b"not an ELF library") + env["LD_LIBRARY_PATH"] = str(poison) + + probe = """ +import ctypes +import pathlib +import sys +import _tkinter + +assert _tkinter.__file__ +abi3 = pathlib.Path(sys.base_prefix) / "lib" / "libpython3.so" +if abi3.exists(): + ctypes.CDLL(str(abi3)) +""" + result = subprocess.run( + [str(executable), "-S", "-c", probe], + cwd=root, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=30, + ) + self.assertEqual(result.returncode, 0, result.stdout) + @unittest.skipUnless(os.name == "nt", "Windows executable names") def test_windows_executables(self): build_options = os.environ["BUILD_OPTIONS"].split("+") From 2df763189f4fdcadebc3e6b2c3c4eae52c2958f3 Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:36:59 +0000 Subject: [PATCH 2/6] Support copied venv interpreters in relocatable mode --- cpython-unix/patch-relocatable-flag.patch | 10 +++- cpython-unix/patch-relocatable-link.patch | 61 +++++++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/cpython-unix/patch-relocatable-flag.patch b/cpython-unix/patch-relocatable-flag.patch index 2b2db41f7..7a08c8f76 100644 --- a/cpython-unix/patch-relocatable-flag.patch +++ b/cpython-unix/patch-relocatable-flag.patch @@ -42,10 +42,10 @@ index a8760c9..be7ad9c 100644 # Apple framework builds need more magic. LDLIBRARY is the dynamic diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst -index 0d420d8..e033256 100644 +index 0d420d8..41de2f8 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst -@@ -1073,6 +1073,28 @@ Linker options +@@ -1073,6 +1073,34 @@ Linker options Enable building a shared Python library: ``libpython`` (default is no). @@ -67,6 +67,12 @@ index 0d420d8..e033256 100644 + can find ``libpython`` and that Python can find its standard library; this + option does not configure those paths for them. + ++ When :mod:`venv` copies an interpreter from a shared build, it creates a ++ directory symlink to the base installation's libraries. Consequently, ++ ``--copies`` requires filesystem support for symbolic links in this mode. ++ Virtual environments still depend on the base installation and must be ++ recreated if it moves. ++ + This option does not bundle third-party libraries or relocate their data + files. It does not rewrite installed script shebangs or build-time paths in + :mod:`sysconfig`. diff --git a/cpython-unix/patch-relocatable-link.patch b/cpython-unix/patch-relocatable-link.patch index e1a70c8dc..e22de96b8 100644 --- a/cpython-unix/patch-relocatable-link.patch +++ b/cpython-unix/patch-relocatable-link.patch @@ -63,10 +63,10 @@ index 7dcfc45..93bc9bf 100644 ############################################################################ # "Bootstrap Python" used to run Programs/_freeze_module.py diff --git a/configure.ac b/configure.ac -index be7ad9c..785a41d 100644 +index be7ad9c..8588436 100644 --- a/configure.ac +++ b/configure.ac -@@ -6879,6 +6879,49 @@ AC_SUBST([BINLIBDEST]) +@@ -6879,6 +6879,51 @@ AC_SUBST([BINLIBDEST]) LIBDEST='${prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' BINLIBDEST='${exec_prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' @@ -87,12 +87,14 @@ index be7ad9c..785a41d 100644 + [AC_MSG_ERROR([--enable-relocatable requires the default lib directory])]) + AS_CASE([$PLATLIBDIR], [*/*|.|..], + [AC_MSG_ERROR([--enable-relocatable requires a single directory name for --with-platlibdir])]) ++ # Copied virtual-environment interpreters use a link from lib/python-base to ++ # the base installation's library directory. + AS_CASE([$ac_sys_system], + [Linux], [ + # Keep the dollar sign through configure, make, and the shell. DT_RPATH + # takes precedence over LD_LIBRARY_PATH on glibc and covers 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 @@ -103,7 +105,7 @@ index be7ad9c..785a41d 100644 + ], + [Darwin], [ + 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' + ]) @@ -116,3 +118,54 @@ index be7ad9c..785a41d 100644 dnl define LIBPL after ABIFLAGS and LDVERSION is defined. AC_SUBST([PY_ENABLE_SHARED]) if test x$PLATFORM_TRIPLET = x; then +diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py +index 2f30d31..5af4856 100644 +--- a/Lib/test/test_venv.py ++++ b/Lib/test/test_venv.py +@@ -1136,6 +1136,8 @@ 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": ++ 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 +1156,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 38e1bfe..16b5f8b 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')): ++ # The copied executable searches relative to this environment. ++ # Link the base library directory to keep libpython and its ++ # relative dependencies reachable. ++ 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) From 0ad35dabaf660b7e42434d56ccfa06774fd12499 Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:45:26 +0000 Subject: [PATCH 3/6] Skip Windows jobs when the selected build matrix is empty --- .github/workflows/windows.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 96dbd9e1d..f2847c405 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -18,6 +18,7 @@ permissions: { } jobs: crate-build: + if: ${{ needs.generate-matrix.outputs.any_builds == 'true' }} needs: - generate-matrix runs-on: ${{ matrix.runner }} @@ -114,6 +115,7 @@ jobs: STEPS_GET_LABELS_OUTPUTS_LABELS: ${{ steps.get-labels.outputs.labels }} build: + if: ${{ needs.generate-matrix.outputs.any_builds == 'true' }} timeout-minutes: 90 needs: - generate-matrix From 10352d72af912129348e231f3f6b35f26f55bce5 Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:53:57 +0000 Subject: [PATCH 4/6] Enable relocatable mode after initializing configure flags --- cpython-unix/build-cpython.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index a99215331..db430d73b 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -86,7 +86,6 @@ sed "${sed_args[@]}" "s/^_PIP_VERSION = .*/_PIP_VERSION = \"${PIP_VERSION}\"/" L if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_15}" ]; then patch -p1 -i "${ROOT}/patch-relocatable-flag.patch" patch -p1 -i "${ROOT}/patch-relocatable-link.patch" - EXTRA_CONFIGURE_FLAGS="${EXTRA_CONFIGURE_FLAGS} --enable-relocatable" fi # configure doesn't support cross-compiling on Apple. Teach it. @@ -342,6 +341,10 @@ fi EXTRA_CONFIGURE_FLAGS= +if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_15}" ]; then + EXTRA_CONFIGURE_FLAGS="${EXTRA_CONFIGURE_FLAGS} --enable-relocatable" +fi + if [[ "${PYBUILD_PLATFORM}" = macos* ]]; then CFLAGS="${CFLAGS} -I${TOOLS_PATH}/deps/include/uuid" From 95a8db614699023fbe2396534198b13ff8eb69ca Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:53:57 +0000 Subject: [PATCH 5/6] Use Python 3.15 line offsets for the venv backport --- cpython-unix/patch-relocatable-link.patch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpython-unix/patch-relocatable-link.patch b/cpython-unix/patch-relocatable-link.patch index e22de96b8..845003cb7 100644 --- a/cpython-unix/patch-relocatable-link.patch +++ b/cpython-unix/patch-relocatable-link.patch @@ -122,7 +122,7 @@ diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 2f30d31..5af4856 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py -@@ -1136,6 +1136,8 @@ def test_zippath_from_non_installed_posix(self): +@@ -809,6 +809,8 @@ 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") @@ -131,7 +131,7 @@ index 2f30d31..5af4856 100644 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 +1156,11 @@ def test_zippath_from_non_installed_posix(self): +@@ -827,7 +829,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)'] From b9315904bc2c6da38ecf3e3fe7c50e6ec2c014a9 Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Thu, 22 Oct 2026 19:32:07 +0000 Subject: [PATCH 6/6] Separate build-tree and installed relocatable linking --- cpython-unix/build-cpython.sh | 6 +- ...atch-macos-link-extension-modules-15.patch | 17 +- cpython-unix/patch-relocatable-flag.patch | 72 ++-- cpython-unix/patch-relocatable-link.patch | 320 +++++++++++++----- 4 files changed, 284 insertions(+), 131 deletions(-) diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index db430d73b..35009efa7 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -862,7 +862,7 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then "${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}" fi - # CPython 3.15+ supplies extension search paths at link time. + # The relocatable backport supplies extension search paths for CPython 3.15+. if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]; then for module in ${ROOT}/out/python/install/lib/python*/lib-dynload/*.so; do install_name_tool -add_rpath @loader_path/../.. "$module" @@ -959,8 +959,8 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then # libpython3.x.so.1.0 that they want (and it's fine to use # DT_RUNPATH instead of DT_RPATH). if [ "${CC}" == "musl-clang" ]; then - # CPython 3.15+ supplies this path at link time. libpython3.so - # is absent from debug builds. + # The relocatable backport supplies this path for CPython 3.15+. + # libpython3.so is absent from debug builds. if [[ -z "${CPYTHON_DEBUG}" && -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_14}" ]]; then patchelf --set-rpath "\$ORIGIN/../lib" \ "${ROOT}/out/python/install/lib/libpython3.so" diff --git a/cpython-unix/patch-macos-link-extension-modules-15.patch b/cpython-unix/patch-macos-link-extension-modules-15.patch index 49de58e1e..70993e9a6 100644 --- a/cpython-unix/patch-macos-link-extension-modules-15.patch +++ b/cpython-unix/patch-macos-link-extension-modules-15.patch @@ -1,11 +1,20 @@ --- a/Makefile.pre.in +++ b/Makefile.pre.in -@@ -1020,7 +1020,7 @@ - $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ +@@ -1053,7 +1053,7 @@ + $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) -- $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ -+ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ +- $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ + $(RELOCATABLE_DIR)/libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) +@@ -1066,7 +1066,7 @@ + + $(RELOCATABLE_DIR)/libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) + @mkdir -p $(@D) +- $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(RELOCATABLE_LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM) ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(RELOCATABLE_LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) + libpython$(VERSION).sl: $(LIBRARY_OBJS) + $(LDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) diff --git a/cpython-unix/patch-relocatable-flag.patch b/cpython-unix/patch-relocatable-flag.patch index 7a08c8f76..4e8b54f8e 100644 --- a/cpython-unix/patch-relocatable-flag.patch +++ b/cpython-unix/patch-relocatable-flag.patch @@ -1,3 +1,25 @@ +diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst +index 0d420d8..2cc8e94 100644 +--- a/Doc/using/configure.rst ++++ b/Doc/using/configure.rst +@@ -1073,6 +1073,17 @@ Linker options + + Enable building a shared Python library: ``libpython`` (default is no). + ++.. option:: --enable-relocatable ++ ++ Enable experimental support for relocatable builds (default is no). ++ This option currently has no effect on the build. ++ ++ .. warning:: ++ ++ This feature is under active development. Do not use it in production. ++ ++ .. versionadded:: 3.16 ++ + .. option:: --without-static-libpython + + Do not build ``libpythonMAJOR.MINOR.a`` and do not install ``python.o`` diff --git a/Makefile.pre.in b/Makefile.pre.in index 36515db..7dcfc45 100644 --- a/Makefile.pre.in @@ -11,28 +33,23 @@ index 36515db..7dcfc45 100644 # Shell used by make (some versions default to the login shell, which is bad) SHELL= /bin/sh -e diff --git a/configure.ac b/configure.ac -index a8760c9..be7ad9c 100644 +index a8760c9..1afbeba 100644 --- a/configure.ac +++ b/configure.ac -@@ -1594,6 +1594,26 @@ if test "x$enable_profiling" = xyes; then +@@ -1594,6 +1594,21 @@ if test "x$enable_profiling" = xyes; then LDFLAGS="-pg $LDFLAGS" fi +AC_MSG_CHECKING([for --enable-relocatable]) +AC_ARG_ENABLE([relocatable], + AS_HELP_STRING([--enable-relocatable], -+ [use relative runtime library paths (default is no)]), ++ [enable experimental relocatable build support (default is no)]), + [], [enable_relocatable=no]) +AS_CASE([$enable_relocatable], + [yes|no], [], + [AC_MSG_ERROR([--enable-relocatable must be yes or no])]) +AC_MSG_RESULT([$enable_relocatable]) +AS_VAR_IF([enable_relocatable], [yes], [ -+ AS_CASE([$ac_sys_system], -+ [Linux|Darwin], [], -+ [AC_MSG_ERROR([--enable-relocatable is supported only on Linux and macOS])]) -+ AS_VAR_IF([enable_framework], [], [], -+ [AC_MSG_ERROR([--enable-relocatable does not support framework builds])]) + AC_DEFINE([Py_ENABLE_RELOCATABLE], [1], + [Define if --enable-relocatable is used.]) +]) @@ -41,45 +58,6 @@ index a8760c9..be7ad9c 100644 AC_MSG_CHECKING([LDLIBRARY]) # Apple framework builds need more magic. LDLIBRARY is the dynamic -diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst -index 0d420d8..41de2f8 100644 ---- a/Doc/using/configure.rst -+++ b/Doc/using/configure.rst -@@ -1073,6 +1073,34 @@ Linker options - - Enable building a shared Python library: ``libpython`` (default is no). - -+.. option:: --enable-relocatable -+ -+ Use relative runtime library paths for the interpreter, ``libpython``, -+ and standard-library extension modules (default is no). -+ -+ This option supports Linux and non-framework macOS builds. The installation -+ must use the default ``bin`` and ``lib`` directories under a common prefix. -+ :option:`--with-platlibdir` may select a different directory name for the -+ standard library, but not a nested path. -+ Move the installation as a unit, keeping these relative locations intact. -+ -+ On Linux, use ``DT_RPATH``; on glibc this gives bundled libraries precedence -+ over ``LD_LIBRARY_PATH``. On macOS, use an ``@rpath`` install name for -+ ``libpython`` and loader-relative paths for its dependencies and extension -+ modules. Applications embedding Python must ensure that the dynamic loader -+ can find ``libpython`` and that Python can find its standard library; this -+ option does not configure those paths for them. -+ -+ When :mod:`venv` copies an interpreter from a shared build, it creates a -+ directory symlink to the base installation's libraries. Consequently, -+ ``--copies`` requires filesystem support for symbolic links in this mode. -+ Virtual environments still depend on the base installation and must be -+ recreated if it moves. -+ -+ This option does not bundle third-party libraries or relocate their data -+ files. It does not rewrite installed script shebangs or build-time paths in -+ :mod:`sysconfig`. -+ - .. option:: --without-static-libpython - - Do not build ``libpythonMAJOR.MINOR.a`` and do not install ``python.o`` diff --git a/pyconfig.h.in b/pyconfig.h.in index 08306a1..4fb029a 100644 --- a/pyconfig.h.in diff --git a/cpython-unix/patch-relocatable-link.patch b/cpython-unix/patch-relocatable-link.patch index 845003cb7..adfc13504 100644 --- a/cpython-unix/patch-relocatable-link.patch +++ b/cpython-unix/patch-relocatable-link.patch @@ -1,5 +1,86 @@ +diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst +index 2cc8e94..203b04e 100644 +--- a/Doc/using/configure.rst ++++ b/Doc/using/configure.rst +@@ -1076,12 +1076,31 @@ Linker options + .. option:: --enable-relocatable + + Enable experimental support for relocatable builds (default is no). +- This option currently has no effect on the build. + + .. warning:: + + This feature is under active development. Do not use it in production. + ++ Currently, this sets relative runtime library paths on Linux and for ++ non-framework macOS builds. Framework relocation is not implemented yet. ++ Combining this option with :option:`--enable-framework` is an error. ++ ++ Move the installation as a unit. It must use the default ``bin`` and ``lib`` ++ directories under a common prefix. :option:`--with-platlibdir` may select ++ 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. ++ ++ This option does not bundle dependencies, adjust data-file lookup for ++ libraries such as Tcl/Tk, or rewrite script shebangs and build-time ++ paths in :mod:`sysconfig`. ++ + .. versionadded:: 3.16 + + .. option:: --without-static-libpython +diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py +index c74ca05..791dc53 100644 +--- a/Lib/test/test_sys.py ++++ b/Lib/test/test_sys.py +@@ -2372,9 +2372,11 @@ def test_remote_exec_deleted_libpython(self): + os.symlink(instsoname, os.path.join(lib_dir, ldlibrary)) + + env = os.environ.copy() +- ld_library_path = env.get('LD_LIBRARY_PATH') +- env['LD_LIBRARY_PATH'] = lib_dir if not ld_library_path else ( +- lib_dir + os.pathsep + ld_library_path) ++ # Preload the copy even when the interpreter's RPATH takes ++ # precedence over LD_LIBRARY_PATH. ++ ld_preload = env.get('LD_PRELOAD') ++ env['LD_PRELOAD'] = copied_libpython if not ld_preload else ( ++ copied_libpython + os.pathsep + ld_preload) + + self._run_remote_exec_with_deleted_mapping(copied_libpython, + env=env) +diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py +index 38e1bfe..b4f1cbd 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/Makefile.pre.in b/Makefile.pre.in -index 7dcfc45..93bc9bf 100644 +index 7dcfc45..a090b9c 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -61,6 +61,10 @@ DSYMUTIL_PATH= @DSYMUTIL_PATH@ @@ -9,64 +90,171 @@ index 7dcfc45..93bc9bf 100644 +RELOCATABLE_PYTHON_LDFLAGS=@RELOCATABLE_PYTHON_LDFLAGS@ +RELOCATABLE_LIBPYTHON_LDFLAGS=@RELOCATABLE_LIBPYTHON_LDFLAGS@ +RELOCATABLE_EXTENSION_LDFLAGS=@RELOCATABLE_EXTENSION_LDFLAGS@ -+LIBPYTHON_INSTALL_NAME=@LIBPYTHON_INSTALL_NAME@ ++RELOCATABLE_LIBPYTHON_INSTALL_NAME=@RELOCATABLE_LIBPYTHON_INSTALL_NAME@ # Shell used by make (some versions default to the login shell, which is bad) SHELL= /bin/sh -e -@@ -286,7 +290,7 @@ DIST= $(DISTFILES) $(DISTDIRS) - LIBRARY= @LIBRARY@ - LDLIBRARY= @LDLIBRARY@ - BLDLIBRARY= @BLDLIBRARY@ --MODULE_LDFLAGS_SHARED=$(if $(LIBPYTHON),$(BLDLIBRARY)) -+MODULE_LDFLAGS_SHARED=$(if $(LIBPYTHON),$(BLDLIBRARY)) $(RELOCATABLE_EXTENSION_LDFLAGS) - PY3LIBRARY= @PY3LIBRARY@ - DLLLIBRARY= @DLLLIBRARY@ - LDLIBRARYDIR= @LDLIBRARYDIR@ -@@ -972,7 +976,7 @@ clinic-tests: check-clean-src $(srcdir)/Lib/test/clinic.test.c - - # Build the interpreter +@@ -313,6 +317,22 @@ LIBOBJS= @LIBOBJS@ + PYTHON= python$(EXE) + BUILDPYTHON= python$(BUILDEXE) + ++# Build-tree programs run with RUNSHARED where needed. Install variants need ++# runtime paths relative to their installed locations. ++RELOCATABLE_DIR=build/relocatable ++INSTALL_PYTHON=$(BUILDPYTHON) ++INSTALL_LDLIBRARY=$(LDLIBRARY) ++INSTALL_PY3LIBRARY=$(PY3LIBRARY) ++INSTALL_SHAREDMODS=$(SHAREDMODS) ++ifeq ($(RELOCATABLE),yes) ++INSTALL_PYTHON=$(RELOCATABLE_DIR)/$(BUILDPYTHON) ++INSTALL_SHAREDMODS=$(addprefix $(RELOCATABLE_DIR)/,$(SHAREDMODS)) ++ifeq ($(PY_ENABLE_SHARED),1) ++INSTALL_LDLIBRARY=$(RELOCATABLE_DIR)/$(LDLIBRARY) ++INSTALL_PY3LIBRARY=$(addprefix $(RELOCATABLE_DIR)/,$(PY3LIBRARY)) ++endif ++endif ++ + HOSTRUNNER= @HOSTRUNNER@ + + PYTHON_FOR_REGEN?=@PYTHON_FOR_REGEN@ +@@ -809,2 +829,3 @@ list-targets: + .PHONY: build_all ++build_all: $(INSTALL_PYTHON) $(INSTALL_LDLIBRARY) $(INSTALL_PY3LIBRARY) $(INSTALL_SHAREDMODS) + build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sharedmods \ +@@ -866,6 +887,10 @@ profile-opt: profile-run-stamp + + # List of binaries that BOLT runs on. + BOLT_BINARIES := @BOLT_BINARIES@ ++ifeq ($(RELOCATABLE),yes) ++# Optimize libraries before the interpreter, which depends on their timestamps. ++RELOCATABLE_BOLT_BINARIES = $(addprefix $(RELOCATABLE_DIR)/,$(filter-out $(BUILDPYTHON),$(BOLT_BINARIES)) $(BUILDPYTHON)) ++endif + + BOLT_INSTRUMENT_FLAGS := @BOLT_INSTRUMENT_FLAGS@ + BOLT_APPLY_FLAGS := @BOLT_APPLY_FLAGS@ +@@ -875,13 +900,13 @@ clean-bolt: + # Profile data. + rm -f *.fdata + # Pristine binaries before BOLT optimization. +- rm -f *.prebolt ++ rm -f *.prebolt $(RELOCATABLE_DIR)/*.prebolt + # BOLT instrumented binaries. + rm -f *.bolt_inst + + profile-bolt-stamp: $(BUILDPYTHON) + # Ensure a pristine, pre-BOLT copy of the binary and no profile data from last run. +- for bin in $(BOLT_BINARIES); do \ ++ for bin in $(BOLT_BINARIES) $(RELOCATABLE_BOLT_BINARIES); do \ + prebolt="$${bin}.prebolt"; \ + if [ -e "$${prebolt}" ]; then \ + echo "Restoring pre-BOLT binary $${prebolt}"; \ +@@ -907,6 +932,14 @@ profile-bolt-stamp: $(BUILDPYTHON) + @LLVM_BOLT@ "$${bin}.prebolt" -o "$${bin}.bolt" -data="$${bin}.fdata" $(BOLT_APPLY_FLAGS); \ + mv "$${bin}.bolt" "$${bin}"; \ + done ++ # Install variants are linked from the same objects, so reuse the function profiles. ++ for bin in $(RELOCATABLE_BOLT_BINARIES); do \ ++ @LLVM_BOLT@ "$${bin}.prebolt" -o "$${bin}.bolt" -data="$${bin##*/}.fdata" $(BOLT_APPLY_FLAGS); \ ++ mv "$${bin}.bolt" "$${bin}"; \ ++ done ++ if test "$(RELOCATABLE)" = yes && test -n "$(filter $(INSTSONAME),$(BOLT_BINARIES))"; then \ ++ $(LN) -f $(RELOCATABLE_DIR)/$(INSTSONAME) $(INSTALL_LDLIBRARY); \ ++ fi + touch $@ + + .PHONY: bolt-opt +@@ -974,6 +1007,10 @@ clinic-tests: check-clean-src $(srcdir)/Lib/test/clinic.test.c $(BUILDPYTHON): Programs/python.o $(LINK_PYTHON_DEPS) -- $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) -+ $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) $(RELOCATABLE_PYTHON_LDFLAGS) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) + $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) ++$(RELOCATABLE_DIR)/$(BUILDPYTHON): Programs/python.o $(LINK_PYTHON_DEPS) $(INSTALL_LDLIBRARY) ++ @mkdir -p $(@D) ++ $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) $(RELOCATABLE_PYTHON_LDFLAGS) -L$(RELOCATABLE_DIR) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) ++ platform: $(PYTHON_FOR_BUILD_DEPS) pybuilddir.txt $(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import sys ; from sysconfig import get_platform ; print("%s-%d.%d" % (get_platform(), *sys.version_info[:2]))' >platform -@@ -1004,7 +1008,7 @@ $(LIBRARY): $(LIBRARY_OBJS) - libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) - # AIX Linker don't support "-h" option - if test "$(MACHDEP)" != "aix"; then \ -- $(BLDSHARED) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ -+ $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ - else \ - $(BLDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ - fi -@@ -1013,10 +1017,10 @@ libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) - fi - libpython3.so: libpython$(LDVERSION).so -- $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ -+ $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ - - libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) -- $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ -+ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ +@@ -1019,6 +1056,18 @@ libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) + $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++$(RELOCATABLE_DIR)/libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) ++ @mkdir -p $(@D) ++ $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -Wl,-h$(INSTSONAME) -o $(RELOCATABLE_DIR)/$(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) ++ $(LN) -f $(RELOCATABLE_DIR)/$(INSTSONAME) $@ ++ ++$(RELOCATABLE_DIR)/libpython3.so: $(RELOCATABLE_DIR)/libpython$(LDVERSION).so ++ $(BLDSHARED) $(RELOCATABLE_LIBPYTHON_LDFLAGS) $(NO_AS_NEEDED) -o $@ -Wl,-hlibpython3.so $^ ++ ++$(RELOCATABLE_DIR)/libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) ++ @mkdir -p $(@D) ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) $(RELOCATABLE_LIBPYTHON_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(RELOCATABLE_LIBPYTHON_INSTALL_NAME) -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM) ++ libpython$(VERSION).sl: $(LIBRARY_OBJS) -@@ -1617,7 +1621,7 @@ regen-re: $(BUILDPYTHON) - $(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/generate_re_casefix.py $(srcdir)/Lib/re/_casefix.py + $(LDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) - Programs/_testembed: Programs/_testembed.o $(LINK_PYTHON_DEPS) -- $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) -+ $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) $(RELOCATABLE_PYTHON_LDFLAGS) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) +@@ -2394,7 +2443,7 @@ sharedinstall: all + else true; \ + fi; \ + done +- @for i in X $(SHAREDMODS); do \ ++ @for i in X $(INSTALL_SHAREDMODS); do \ + if test $$i != X; then \ + echo $(INSTALL_SHARED) $$i $(DESTSHARED)/`basename $$i`; \ + $(INSTALL_SHARED) $$i $(DESTDIR)$(DESTSHARED)/`basename $$i`; \ +@@ -2408,7 +2457,7 @@ sharedinstall: all + # Install the interpreter with $(VERSION) affixed + # This goes into $(exec_prefix) + .PHONY: altbininstall +-altbininstall: $(BUILDPYTHON) @FRAMEWORKPYTHONW@ ++altbininstall: all @FRAMEWORKPYTHONW@ + @for i in $(BINDIR) $(LIBDIR); \ + do \ + if test ! -d $(DESTDIR)$$i; then \ +@@ -2418,7 +2467,7 @@ altbininstall: $(BUILDPYTHON) @FRAMEWORKPYTHONW@ + fi; \ + done + if test "$(PYTHONFRAMEWORKDIR)" = "no-framework" ; then \ +- $(INSTALL_PROGRAM) $(BUILDPYTHON) $(DESTDIR)$(BINDIR)/python$(LDVERSION)$(EXE); \ ++ $(INSTALL_PROGRAM) $(INSTALL_PYTHON) $(DESTDIR)$(BINDIR)/python$(LDVERSION)$(EXE); \ + else \ + $(INSTALL_PROGRAM) $(STRIPFLAG) Mac/pythonw $(DESTDIR)$(BINDIR)/python$(LDVERSION)$(EXE); \ + fi +@@ -2433,13 +2482,13 @@ altbininstall: $(BUILDPYTHON) @FRAMEWORKPYTHONW@ + if test -n "$(DLLLIBRARY)" ; then \ + $(INSTALL_SHARED) $(DLLLIBRARY) $(DESTDIR)$(BINDIR); \ + else \ +- $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(LIBDIR)/$(INSTSONAME); \ ++ $(INSTALL_SHARED) $(INSTALL_LDLIBRARY) $(DESTDIR)$(LIBDIR)/$(INSTSONAME); \ + if test $(LDLIBRARY) != $(INSTSONAME); then \ + (cd $(DESTDIR)$(LIBDIR); $(LN) -sf $(INSTSONAME) $(LDLIBRARY)) \ + fi \ + fi; \ + if test -n "$(PY3LIBRARY)"; then \ +- $(INSTALL_SHARED) $(PY3LIBRARY) $(DESTDIR)$(LIBDIR)/$(PY3LIBRARY); \ ++ $(INSTALL_SHARED) $(INSTALL_PY3LIBRARY) $(DESTDIR)$(LIBDIR)/$(PY3LIBRARY); \ + fi; \ + else true; \ + fi; \ +diff --git a/Modules/makesetup b/Modules/makesetup +index 8b28946..88c01c6 100755 +--- a/Modules/makesetup ++++ b/Modules/makesetup +@@ -279,6 +279,9 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' | + rule="$file: $objs \$(MODULE_${mods_upper}_LDEPS)" + rule="$rule; \$(BLDSHARED) $objs $libs \$(MODULE_LDFLAGS_SHARED) -o $file" + echo "$rule" >>$rulesf ++ rule="\$(RELOCATABLE_DIR)/$file: $objs \$(MODULE_${mods_upper}_LDEPS)" ++ rule="$rule; @mkdir -p \$(@D); \$(BLDSHARED) $objs $libs \$(MODULE_LDFLAGS_SHARED) \$(RELOCATABLE_EXTENSION_LDFLAGS) -o \$@" ++ echo "$rule" >>$rulesf + done + done - ############################################################################ - # "Bootstrap Python" used to run Programs/_freeze_module.py diff --git a/configure.ac b/configure.ac -index be7ad9c..8588436 100644 +index 1afbeba..47741e6 100644 --- a/configure.ac +++ b/configure.ac -@@ -6879,6 +6879,51 @@ AC_SUBST([BINLIBDEST]) +@@ -6874,6 +6874,54 @@ AC_SUBST([BINLIBDEST]) LIBDEST='${prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' BINLIBDEST='${exec_prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' @@ -75,8 +263,14 @@ index be7ad9c..8588436 100644 +RELOCATABLE_PYTHON_LDFLAGS= +RELOCATABLE_LIBPYTHON_LDFLAGS= +RELOCATABLE_EXTENSION_LDFLAGS= -+LIBPYTHON_INSTALL_NAME='$(prefix)/lib/libpython$(LDVERSION).dylib' -+AS_VAR_IF([enable_relocatable], [yes], [ ++RELOCATABLE_LIBPYTHON_INSTALL_NAME= ++AS_IF([test "$enable_relocatable" = yes && test -n "$enable_framework"], [ ++ AC_MSG_ERROR([--enable-relocatable does not support framework builds yet]) ++]) ++AS_IF([test "$enable_relocatable" = yes && test -z "$enable_framework"], [ ++ AS_CASE([$ac_sys_system], ++ [Linux|Darwin], [], ++ [AC_MSG_ERROR([relocatable builds are not implemented on $ac_sys_system yet])]) + AS_VAR_IF([prefix], [NONE], [relocatable_prefix=$ac_default_prefix], + [relocatable_prefix=$prefix]) + AS_IF([test "$exec_prefix" != NONE && test "$exec_prefix" != '${prefix}' && test "$exec_prefix" != "$relocatable_prefix"], @@ -87,13 +281,10 @@ index be7ad9c..8588436 100644 + [AC_MSG_ERROR([--enable-relocatable requires the default lib directory])]) + AS_CASE([$PLATLIBDIR], [*/*|.|..], + [AC_MSG_ERROR([--enable-relocatable requires a single directory name for --with-platlibdir])]) -+ # Copied virtual-environment interpreters use a link from lib/python-base to -+ # the base installation's library directory. + AS_CASE([$ac_sys_system], + [Linux], [ -+ # Keep the dollar sign through configure, make, and the shell. DT_RPATH -+ # takes precedence over LD_LIBRARY_PATH on glibc and covers indirect -+ # dependencies. ++ # 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:\$\$ORIGIN/../lib/python-base'" + RELOCATABLE_LIBPYTHON_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN'" + RELOCATABLE_EXTENSION_LDFLAGS="-Wl,--disable-new-dtags,-rpath,'\$\$ORIGIN/../../../lib'" @@ -104,7 +295,7 @@ index be7ad9c..8588436 100644 + LDFLAGS=$save_LDFLAGS + ], + [Darwin], [ -+ LIBPYTHON_INSTALL_NAME='@rpath/libpython$(LDVERSION).dylib' ++ RELOCATABLE_LIBPYTHON_INSTALL_NAME='@rpath/libpython$(LDVERSION).dylib' + 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' @@ -113,7 +304,7 @@ index be7ad9c..8588436 100644 +AC_SUBST([RELOCATABLE_PYTHON_LDFLAGS]) +AC_SUBST([RELOCATABLE_LIBPYTHON_LDFLAGS]) +AC_SUBST([RELOCATABLE_EXTENSION_LDFLAGS]) -+AC_SUBST([LIBPYTHON_INSTALL_NAME]) ++AC_SUBST([RELOCATABLE_LIBPYTHON_INSTALL_NAME]) + dnl define LIBPL after ABIFLAGS and LDVERSION is defined. AC_SUBST([PY_ENABLE_SHARED]) @@ -144,28 +335,3 @@ index 2f30d31..5af4856 100644 self.assertTrue(zip_landmark.encode() in out) @requireVenvCreate -diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py -index 38e1bfe..16b5f8b 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')): -+ # The copied executable searches relative to this environment. -+ # Link the base library directory to keep libpython and its -+ # relative dependencies reachable. -+ 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)