From c72b9be118c6b5ac2c1c768389da4936d05fe504 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 27 Aug 2022 10:23:01 -0700
Subject: [PATCH v12 10/15] meson: Add support for relative rpaths, fixing
 tests on MacOS w/ SIP

---
 meson.build                                   | 50 +++++++++--
 .../relativize_shared_library_references      | 84 +++++++++++++++++++
 src/tools/relpath.py                          |  6 ++
 3 files changed, 135 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 f1ecf685f14..e64054b1c56 100644
--- a/meson.build
+++ b/meson.build
@@ -152,6 +152,7 @@ portname = host_system
 
 exesuffix = '' # overridden below where necessary
 dlsuffix = '.so' # overridden below where necessary
+rpath_origin = '$ORIGIN'
 library_path_var = 'LD_LIBRARY_PATH'
 
 export_file_format = 'gnu'
@@ -192,6 +193,7 @@ if host_system == 'aix'
 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@'
@@ -211,6 +213,7 @@ elif host_system == 'windows'
   exesuffix = '.exe'
   dlsuffix = '.dll'
   ld_library_path_var = ''
+  rpath_origin = ''
 
   export_file_format = 'win'
   export_file_suffix = 'def'
@@ -261,7 +264,17 @@ elif host_system == 'linux'
 elif host_system == 'cygwin'
   cppflags += '-D_GNU_SOURCE'
 
-elif host_system in ['freebsd', 'netbsd', 'openbsd']
+elif host_system == 'openbsd'
+  # 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 == 'netbsd'
+  # 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 == 'freebsd'
   # you're ok
 else
   # XXX: Should we add an option to override the host_system as an escape
@@ -2394,6 +2407,26 @@ bin_install_rpaths = []
 lib_install_rpaths = []
 mod_install_rpaths = []
 
+
+# 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
+
 # add extra_lib_dirs to rpath
 bin_install_rpaths += postgres_lib_d
 lib_install_rpaths += postgres_lib_d
@@ -2650,6 +2683,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
@@ -2715,10 +2756,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..db6431639f1
--- /dev/null
+++ b/src/tools/relativize_shared_library_references
@@ -0,0 +1,84 @@
+#!/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 shlex
+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 = []
+
+    targets = json.load(open(os.path.join(build_root, 'meson-info', 'intro-targets.json')))
+    installed = json.load(open(os.path.join(build_root, 'meson-info', '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)
+
+            subprocess.check_call(install_name_tool + ['-change', lib, newref, patchpath])
+
+
+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.0.3.g30cc8d0f14

