From 19f248fae1e67354683ef31c1497bacc5c7df25f Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
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

