From b444cd7d1a7375e6477fb21578eac9ce239de989 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: Set relative library paths for relocatable builds --- Doc/using/configure.rst | 18 ++- Makefile.pre.in | 51 ++++++++- ...-10-22-18-00-00.gh-issue-119696.r3Flag.rst | 5 +- Modules/makesetup | 3 + configure | 106 ++++++++++++++++++ configure.ac | 52 +++++++++ 6 files changed, 227 insertions(+), 8 deletions(-) diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 2cc8e94ebf075ca..0e4e35ffcdb7db7 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -1076,12 +1076,28 @@ 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. + Combining it with :option:`--enable-bolt` 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``. + + 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/Makefile.pre.in b/Makefile.pre.in index 7dcfc4557976966..87a0c2cbcb8295e 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@ +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 @@ -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@ @@ -788,7 +808,8 @@ list-targets: .PHONY: build_all build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sharedmods \ - gdbhooks Programs/_testembed scripts checksharedmods rundsymutil $(BUILD_DETAILS) + gdbhooks Programs/_testembed scripts checksharedmods rundsymutil $(BUILD_DETAILS) \ + $(INSTALL_PYTHON) $(INSTALL_LDLIBRARY) $(INSTALL_PY3LIBRARY) $(INSTALL_SHAREDMODS) .PHONY: build_wasm build_wasm: check-clean-src $(BUILDPYTHON) platform sharedmods \ @@ -974,6 +995,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) +$(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 @@ -1019,6 +1044,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) $(LDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) @@ -2394,7 +2431,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 +2445,11 @@ sharedinstall: all # Install the interpreter with $(VERSION) affixed # This goes into $(exec_prefix) .PHONY: altbininstall +ifeq ($(RELOCATABLE),yes) +altbininstall: all @FRAMEWORKPYTHONW@ +else altbininstall: $(BUILDPYTHON) @FRAMEWORKPYTHONW@ +endif @for i in $(BINDIR) $(LIBDIR); \ do \ if test ! -d $(DESTDIR)$$i; then \ @@ -2418,7 +2459,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 +2474,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/Misc/NEWS.d/next/Build/2026-10-22-18-00-00.gh-issue-119696.r3Flag.rst b/Misc/NEWS.d/next/Build/2026-10-22-18-00-00.gh-issue-119696.r3Flag.rst index 328d94c1094e4c5..6d6f48bcb1915d1 100644 --- a/Misc/NEWS.d/next/Build/2026-10-22-18-00-00.gh-issue-119696.r3Flag.rst +++ b/Misc/NEWS.d/next/Build/2026-10-22-18-00-00.gh-issue-119696.r3Flag.rst @@ -1,2 +1,3 @@ -Add the experimental :option:`--enable-relocatable` configure option. -The option currently has no effect on the build. +Add the experimental :option:`--enable-relocatable` configure option +to set relative runtime library paths on Linux and non-framework macOS +builds. The feature is under active development. diff --git a/Modules/makesetup b/Modules/makesetup index 8b28946608b5762..88c01c6957eda46 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 diff --git a/configure b/configure index 9ab251a0fe22ee6..72ea026f5c591c7 100755 --- a/configure +++ b/configure @@ -848,6 +848,10 @@ LIBREADLINE_CFLAGS WHEEL_PKG_DIR LIBPL PY_ENABLE_SHARED +RELOCATABLE_LIBPYTHON_INSTALL_NAME +RELOCATABLE_EXTENSION_LDFLAGS +RELOCATABLE_LIBPYTHON_LDFLAGS +RELOCATABLE_PYTHON_LDFLAGS BINLIBDEST LIBDEST PLATLIBDIR @@ -9477,6 +9481,13 @@ esac fi +if test "$enable_relocatable" = yes && test "$Py_BOLT" = true +then : + + as_fn_error $? "--enable-relocatable does not support --enable-bolt yet" "$LINENO" 5 + +fi + if test "$Py_BOLT" = 'true' ; then PREBOLT_RULE="${DEF_MAKE_ALL_RULE}" @@ -28787,6 +28798,101 @@ fi 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= +RELOCATABLE_LIBPYTHON_INSTALL_NAME= +if test "$enable_relocatable" = yes && test -n "$enable_framework" +then : + + as_fn_error $? "--enable-relocatable does not support framework builds yet" "$LINENO" 5 + +fi +if test "$enable_relocatable" = yes && test -z "$enable_framework" +then : + + case $ac_sys_system in #( + Linux|Darwin) : + ;; #( + *) : + as_fn_error $? "relocatable builds are not implemented on $ac_sys_system yet" "$LINENO" 5 ;; +esac + if test "x$prefix" = xNONE +then : + relocatable_prefix=$ac_default_prefix +else case e in #( + e) relocatable_prefix=$prefix ;; +esac +fi + if test "$exec_prefix" != NONE && test "$exec_prefix" != '${prefix}' && test "$exec_prefix" != "$relocatable_prefix" +then : + as_fn_error $? "--enable-relocatable requires a common prefix and exec-prefix" "$LINENO" 5 +fi + if test "$bindir" != '${exec_prefix}/bin' && test "$bindir" != '${prefix}/bin' && test "$bindir" != "$relocatable_prefix/bin" +then : + as_fn_error $? "--enable-relocatable requires the default bin directory" "$LINENO" 5 +fi + if test "$libdir" != '${exec_prefix}/lib' && test "$libdir" != '${prefix}/lib' && test "$libdir" != "$relocatable_prefix/lib" +then : + as_fn_error $? "--enable-relocatable requires the default lib directory" "$LINENO" 5 +fi + case $PLATLIBDIR in #( + */*|.|..) : + as_fn_error $? "--enable-relocatable requires a single directory name for --with-platlibdir" "$LINENO" 5 ;; #( + *) : + ;; +esac + case $ac_sys_system in #( + 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_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" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + +else case e in #( + e) as_fn_error $? "--enable-relocatable requires linker support for --disable-new-dtags" "$LINENO" 5 ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + ;; #( + Darwin) : + + RELOCATABLE_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' + ;; #( + *) : + ;; +esac + +fi + + + + + if test x$PLATFORM_TRIPLET = x; then LIBPL='$(LIBDEST)'"/config-${LDVERSION}" diff --git a/configure.ac b/configure.ac index 1afbeba002bbbf1..b30fcd00252111c 100644 --- a/configure.ac +++ b/configure.ac @@ -2215,6 +2215,10 @@ else fi], [AC_MSG_RESULT([no])]) +AS_IF([test "$enable_relocatable" = yes && test "$Py_BOLT" = true], [ + AC_MSG_ERROR([--enable-relocatable does not support --enable-bolt yet]) +]) + AC_SUBST([PREBOLT_RULE]) if test "$Py_BOLT" = 'true' ; then PREBOLT_RULE="${DEF_MAKE_ALL_RULE}" @@ -6874,6 +6878,54 @@ 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= +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"], + [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 entries can also resolve 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], [ + RELOCATABLE_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([RELOCATABLE_LIBPYTHON_INSTALL_NAME]) + dnl define LIBPL after ABIFLAGS and LDVERSION is defined. AC_SUBST([PY_ENABLE_SHARED]) if test x$PLATFORM_TRIPLET = x; then