Skip to content

Commit 8db5e14

Browse files
committed
macos: relocate libpq to @rpath for Homebrew layout portability
The pg_client extension is linked in CI against the keg-only standalone 'libpq' Homebrew formula, which records an absolute install name of /opt/homebrew/opt/libpq/lib/libpq.5.dylib in the released binary. End users who install libpq via 'brew install postgresql@18' only (the common layout on Apple Silicon) have libpq at a different path, so dyld cannot resolve the dependency and the extension fails to load. Add scripts/relocate-macos-libpq.sh (mirroring the existing scripts/relocate-macos-openssl.sh pattern) to rewrite the install name to @rpath/libpq.5.dylib and add the common Homebrew locations as LC_RPATH fallbacks, plus scripts/verify-macos-libpq-rpaths.sh to assert the relocation in CI. Fixes #48.
1 parent 52298ee commit 8db5e14

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

scripts/relocate-macos-libpq.sh

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Relocates the libpq dependency of a macOS Mach-O binary from the
5+
# package-manager-specific absolute path recorded at build time
6+
# (e.g. /opt/homebrew/opt/libpq/lib/libpq.5.dylib) to @rpath/libpq.5.dylib,
7+
# then adds the common Homebrew locations that provide libpq as LC_RPATH
8+
# fallbacks.
9+
#
10+
# This mirrors scripts/relocate-macos-openssl.sh in the ladybug repo and
11+
# fixes LadybugDB/extensions#48: the pg_client extension is linked against
12+
# the keg-only standalone `libpq` Homebrew formula during CI, but end users
13+
# often have libpq only via `brew install postgresql@18` (which installs it
14+
# to a different location). Without relocation the released binary cannot be
15+
# loaded on such machines.
16+
#
17+
# Note: LC_RPATH entries pointing at directories that do not exist on the
18+
# target machine are silently skipped by dyld, so adding every known
19+
# Homebrew layout is safe. When a new PostgreSQL major version becomes a
20+
# common Homebrew formula, append its locations to the list below.
21+
22+
if [ "$#" -ne 1 ]; then
23+
echo "usage: $0 <mach-o-binary>" >&2
24+
exit 2
25+
fi
26+
27+
binary="$1"
28+
if [ ! -f "$binary" ]; then
29+
echo "Mach-O binary not found: $binary" >&2
30+
exit 1
31+
fi
32+
33+
LIBPQ="libpq.5.dylib"
34+
35+
dependency_for() {
36+
local library="$1"
37+
otool -L "$binary" | awk -v library="$library" \
38+
'index($1, library) && substr($1, length($1) - length(library) + 1) == library { print $1; exit }'
39+
}
40+
41+
dependency="$(dependency_for "$LIBPQ")"
42+
if [ -z "$dependency" ]; then
43+
echo "libpq dependency not found in $binary" >&2
44+
exit 1
45+
fi
46+
if [ "$dependency" != "@rpath/$LIBPQ" ]; then
47+
install_name_tool -change "$dependency" "@rpath/$LIBPQ" "$binary"
48+
fi
49+
50+
for rpath in \
51+
/opt/homebrew/opt/libpq/lib \
52+
/usr/local/opt/libpq/lib \
53+
/opt/homebrew/lib/postgresql@18 \
54+
/usr/local/lib/postgresql@18 \
55+
/opt/homebrew/opt/postgresql@18/lib \
56+
/usr/local/opt/postgresql@18/lib; do
57+
existing_rpaths="$(otool -l "$binary" | awk '/cmd LC_RPATH/{getline; getline; print $2}')"
58+
if ! grep -Fxq "$rpath" <<<"$existing_rpaths"; then
59+
install_name_tool -add_rpath "$rpath" "$binary"
60+
fi
61+
done
62+
63+
# install_name_tool invalidates the existing signature.
64+
# Ad-hoc signing ("-") is sufficient for development and CI. If this binary is
65+
# distributed to end-users (e.g., via npm), a proper Developer ID certificate
66+
# should be used instead for notarization compatibility.
67+
codesign --force --sign - "$binary"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Verifies that a macOS Mach-O binary's libpq dependency has been relocated
5+
# by scripts/relocate-macos-libpq.sh: it must reference @rpath/libpq.5.dylib,
6+
# must not reference any package-manager-specific absolute libpq path, and
7+
# must carry every known Homebrew libpq location as an LC_RPATH entry.
8+
9+
if [ "$#" -ne 1 ]; then
10+
echo "usage: $0 <mach-o-binary>" >&2
11+
exit 2
12+
fi
13+
14+
binary="$1"
15+
if [ ! -f "$binary" ]; then
16+
echo "Mach-O binary not found: $binary" >&2
17+
exit 1
18+
fi
19+
20+
dependencies="$(otool -L "$binary")"
21+
22+
if ! grep -Fq "@rpath/libpq.5.dylib" <<<"$dependencies"; then
23+
echo "missing @rpath dependency for libpq in $binary" >&2
24+
exit 1
25+
fi
26+
27+
# The leading '/' is matched by the '^[[:space:]]+/' portion of the regex, so the
28+
# alternatives within the group omit it: 'opt/homebrew', 'usr/local', 'opt/local'.
29+
if grep -Eq '^[[:space:]]+/(opt/homebrew|usr/local|opt/local)/.*libpq\.5\.dylib' \
30+
<<<"$dependencies"; then
31+
echo "package-manager-specific libpq dependency remains in $binary" >&2
32+
exit 1
33+
fi
34+
35+
rpaths="$(otool -l "$binary" | awk '/cmd LC_RPATH/{getline; getline; print $2}')"
36+
for required in \
37+
/opt/homebrew/opt/libpq/lib \
38+
/usr/local/opt/libpq/lib \
39+
/opt/homebrew/lib/postgresql@18 \
40+
/usr/local/lib/postgresql@18 \
41+
/opt/homebrew/opt/postgresql@18/lib \
42+
/usr/local/opt/postgresql@18/lib; do
43+
if ! grep -Fxq "$required" <<<"$rpaths"; then
44+
echo "missing libpq rpath $required in $binary" >&2
45+
exit 1
46+
fi
47+
done

0 commit comments

Comments
 (0)