From 19f248fae1e67354683ef31c1497bacc5c7df25f Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Mon, 26 Sep 2022 17:24:47 -0700 Subject: [PATCH v17 22/23] meson: Add support for relative rpaths, fixing tests on MacOS w/ SIP --- meson.build | 68 ++++++++++---- .../relativize_shared_library_references | 88 +++++++++++++++++++ src/tools/relpath.py | 6 ++ 3 files changed, 145 insertions(+), 17 deletions(-) create mode 100755 src/tools/relativize_shared_library_references create mode 100755 src/tools/relpath.py diff --git a/meson.build b/meson.build index d24e04d1544..3bbcd78d319 100644 --- a/meson.build +++ b/meson.build @@ -160,6 +160,7 @@ portname = host_system exesuffix = '' # overridden below where necessary dlsuffix = '.so' # overridden below where necessary +rpath_origin = '$ORIGIN' library_path_var = 'LD_LIBRARY_PATH' # Format of file to control exports from libraries, and how to pass them to @@ -210,6 +211,7 @@ elif host_system == 'cygwin' elif host_system == 'darwin' dlsuffix = '.dylib' library_path_var = 'DYLD_LIBRARY_PATH' + rpath_origin = '@loader_path' export_file_format = 'darwin' export_fmt = '-exported_symbols_list=@0@' @@ -242,8 +244,16 @@ elif host_system == 'netbsd' # LDFLAGS. ldflags += ['-Wl,-z,now', '-Wl,-z,relro'] + # netbsd patched their meson in a broken way: + # https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=56959 + # until there's a way out of that, disable rpath_origin + rpath_origin = '' + elif host_system == 'openbsd' - # you're ok + # openbsd's $ORIGIN doesn't use an absolute path to the binary, but argv[0] + # (i.e. absolute when invoked with an absolute name, but e.g. not absolute + # when invoked via PATH search). + rpath_origin = '' elif host_system == 'sunos' portname = 'solaris' @@ -255,6 +265,7 @@ elif host_system == 'windows' exesuffix = '.exe' dlsuffix = '.dll' library_path_var = '' + rpath_origin = '' export_file_format = 'win' export_file_suffix = 'def' @@ -2479,25 +2490,41 @@ bin_install_rpaths = [] lib_install_rpaths = [] mod_install_rpaths = [] - -# Don't add rpaths on darwin for now - as long as only absolute references to -# libraries are needed, absolute LC_ID_DYLIB ensures libraries can be found in -# their final destination. +# Add extra_lib_dirs to rpath. This ensures we find libraries we depend on. +# +# Not needed on darwin, even if we use relative rpaths for our own libraries, +# as the install_name of libraries in extra_lib_dirs will point to their +# location anyway. if host_system != 'darwin' + bin_install_rpaths += postgres_lib_d + lib_install_rpaths += postgres_lib_d + mod_install_rpaths += postgres_lib_d +endif + +# If the host can form relative rpaths, use that to make the installation +# properly relocatable +if rpath_origin != '' + # PG binaries might need to link to libpq, use relative path to reference + bin_to_lib = run_command(python, files('src/tools/relpath.py'), + dir_bin, dir_lib, check: true).stdout().strip() + bin_install_rpaths += rpath_origin / bin_to_lib + + # PG extensions might need to link to libpq, use relative path to reference + # (often just .) + mod_to_lib = run_command(python, files('src/tools/relpath.py'), + dir_lib_pkg, dir_lib, check: true).stdout().strip() + mod_install_rpaths += rpath_origin / mod_to_lib + + test_use_library_path_var = false +else + # Add absolute path to libdir to rpath. This ensures installed binaries / # libraries find our libraries (mainly libpq). bin_install_rpaths += dir_prefix / dir_lib lib_install_rpaths += dir_prefix / dir_lib mod_install_rpaths += dir_prefix / dir_lib - # Add extra_lib_dirs to rpath. This ensures we find libraries we depend on. - # - # Not needed on darwin even if we use relative rpaths for our own libraries, - # as the install_name of libraries in extra_lib_dirs will point to their - # location anyway. - bin_install_rpaths += postgres_lib_d - lib_install_rpaths += postgres_lib_d - mod_install_rpaths += postgres_lib_d + test_use_library_path_var = true endif @@ -2790,6 +2817,14 @@ above, or by running configure and then make maintainer-clean. endif +# To make MacOS installation work without a prior make install, even with SIP +# enabled, make rpaths relative after installation. This also makes the +# installation relocatable. +if host_system == 'darwin' + meson.add_install_script('src/tools/relativize_shared_library_references') +endif + + ############################################################### # Test prep @@ -2855,10 +2890,9 @@ test_env.set('REGRESS_SHLIB', regress_module.full_path()) # Export PG_TEST_EXTRA so it can be checked in individual tap tests. test_env.set('PG_TEST_EXTRA', get_option('PG_TEST_EXTRA')) -# Add the temporary installation to the library search path on platforms where -# that works (everything but windows, basically). On windows everything -# library-like gets installed into bindir, solving that issue. -if library_path_var != '' +# On platforms without $ORIGIN support we need to add the temporary +# installation to the library search path. +if test_use_library_path_var and library_path_var != '' test_env.prepend(library_path_var, test_install_location / get_option('libdir')) endif diff --git a/src/tools/relativize_shared_library_references b/src/tools/relativize_shared_library_references new file mode 100755 index 00000000000..280bd48fd32 --- /dev/null +++ b/src/tools/relativize_shared_library_references @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# -*-python-*- + +# This script updates a macos postgres installation to reference all internal +# shared libraries using rpaths, leaving absolute install_names in the +# libraries themselves intact. + +import os +import sys +import json +import subprocess +import shutil + + +def installed_path(destdir, path): + if destdir is not None: + return f'{destdir}{path}' + else: + return path + + +def collect_information(): + shared_libraries = [] + executables = [] + shared_modules = [] + + meson_info_p = os.path.join(build_root, 'meson-info') + targets = json.load( + open(os.path.join(meson_info_p, 'intro-targets.json'))) + installed = json.load( + open(os.path.join(meson_info_p, 'intro-installed.json'))) + + for target in targets: + if not target['installed']: + continue + + filenames = target['filename'] + + if target['type'] == 'shared library': + assert(len(filenames) == 1) + filename = filenames[0] + + shared_libraries.append(installed[filename]) + + if target['type'] == 'executable': + assert(len(filenames) == 1) + filename = filenames[0] + executables.append(installed[filename]) + + if target['type'] == 'shared module': + assert(len(filenames) == 1) + filename = filenames[0] + shared_modules.append(installed[filename]) + + return shared_libraries, executables, shared_modules + + +def patch_references(destdir, shared_libraries, executables, shared_modules): + install_name_tool = [shutil.which('install_name_tool')] + + for lib in shared_libraries: + libname = os.path.basename(lib) + libpath = installed_path(destdir, lib) + newref = f'@rpath/{libname}' + + for patch in shared_modules + executables: + patchpath = installed_path(destdir, patch) + + # print(f'in {patchpath} replace reference to {libpath} with {newref}') + if not os.path.exists(patchpath): + print(f"path {patchpath} doesn't exist", file=sys.stderr) + sys.exit(1) + + cmd = install_name_tool + ['-change', lib, newref, patchpath] + subprocess.check_call(cmd) + + +if __name__ == '__main__': + build_root = os.environ['MESON_BUILD_ROOT'] + destdir = os.environ.get('DESTDIR', None) + + print(f'making references to shared libraries relative, destdir is {destdir}', + file=sys.stderr) + + shared_libraries, executables, shared_modules = collect_information() + patch_references(destdir, shared_libraries, executables, shared_modules) + + sys.exit(0) diff --git a/src/tools/relpath.py b/src/tools/relpath.py new file mode 100755 index 00000000000..87bcb496ab5 --- /dev/null +++ b/src/tools/relpath.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import os +import sys + +print(os.path.relpath(sys.argv[2], start=sys.argv[1])) -- 2.37.3.542.gdd3f6c4cae