From fbee3715e97a09f2676e5b6bee4eb0637472010f Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 30 Aug 2022 16:15:05 -0700
Subject: [PATCH v13 14/20] meson: Add support for relative rpaths, fixing
 tests on MacOS w/ SIP

---
 meson.build                                   | 47 ++++++++--
 .../relativize_shared_library_references      | 88 +++++++++++++++++++
 src/tools/relpath.py                          |  6 ++
 3 files changed, 136 insertions(+), 5 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 11ce27c60ee..31c7dbe0ea0 100644
--- a/meson.build
+++ b/meson.build
@@ -159,6 +159,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
@@ -209,6 +210,7 @@ elif host_system == 'cygwin'
 elif host_system == 'darwin'
   dlsuffix = '.dylib'
   ld_library_path_var = 'DYLD_LIBRARY_PATH'
+  rpath_origin = '@loader_path'
 
   export_file_format = 'darwin'
   export_fmt = '-exported_symbols_list=@0@'
@@ -241,8 +243,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'
@@ -254,6 +264,7 @@ elif host_system == 'windows'
   exesuffix = '.exe'
   dlsuffix = '.dll'
   ld_library_path_var = ''
+  rpath_origin = ''
 
   export_file_format = 'win'
   export_file_suffix = 'def'
@@ -2481,6 +2492,25 @@ if host_system != 'darwin'
   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
+  test_use_library_path_var = true
+endif
+
 
 # Define arguments for default targets
 
@@ -2773,6 +2803,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
@@ -2838,10 +2876,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

