Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions scripts/relocate-macos-libpq.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail

# Relocates the libpq dependency of a macOS Mach-O binary from the
# package-manager-specific absolute path recorded at build time
# (e.g. /opt/homebrew/opt/libpq/lib/libpq.5.dylib) to @rpath/libpq.5.dylib,
# then adds the common Homebrew locations that provide libpq as LC_RPATH
# fallbacks.
#
# This mirrors scripts/relocate-macos-openssl.sh in the ladybug repo and
# fixes LadybugDB/extensions#48: the pg_client extension is linked against
# the keg-only standalone `libpq` Homebrew formula during CI, but end users
# often have libpq only via `brew install postgresql@18` (which installs it
# to a different location). Without relocation the released binary cannot be
# loaded on such machines.
#
# Note: LC_RPATH entries pointing at directories that do not exist on the
# target machine are silently skipped by dyld, so adding every known
# Homebrew layout is safe. When a new PostgreSQL major version becomes a
# common Homebrew formula, append its locations to the list below.

if [ "$#" -ne 1 ]; then
echo "usage: $0 <mach-o-binary>" >&2
exit 2
fi

binary="$1"
if [ ! -f "$binary" ]; then
echo "Mach-O binary not found: $binary" >&2
exit 1
fi

LIBPQ="libpq.5.dylib"

dependency_for() {
local library="$1"
otool -L "$binary" | awk -v library="$library" \
'index($1, library) && substr($1, length($1) - length(library) + 1) == library { print $1; exit }'
}

dependency="$(dependency_for "$LIBPQ")"
if [ -z "$dependency" ]; then
echo "libpq dependency not found in $binary" >&2
exit 1
fi
if [ "$dependency" != "@rpath/$LIBPQ" ]; then
install_name_tool -change "$dependency" "@rpath/$LIBPQ" "$binary"
fi

for rpath in \
/opt/homebrew/opt/libpq/lib \
/usr/local/opt/libpq/lib \
/opt/homebrew/lib/postgresql@18 \
/usr/local/lib/postgresql@18 \
/opt/homebrew/opt/postgresql@18/lib \
/usr/local/opt/postgresql@18/lib; do
existing_rpaths="$(otool -l "$binary" | awk '/cmd LC_RPATH/{getline; getline; print $2}')"
if ! grep -Fxq "$rpath" <<<"$existing_rpaths"; then
install_name_tool -add_rpath "$rpath" "$binary"
fi
done

# install_name_tool invalidates the existing signature.
# Ad-hoc signing ("-") is sufficient for development and CI. If this binary is
# distributed to end-users (e.g., via npm), a proper Developer ID certificate
# should be used instead for notarization compatibility.
codesign --force --sign - "$binary"
47 changes: 47 additions & 0 deletions scripts/verify-macos-libpq-rpaths.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail

# Verifies that a macOS Mach-O binary's libpq dependency has been relocated
# by scripts/relocate-macos-libpq.sh: it must reference @rpath/libpq.5.dylib,
# must not reference any package-manager-specific absolute libpq path, and
# must carry every known Homebrew libpq location as an LC_RPATH entry.

if [ "$#" -ne 1 ]; then
echo "usage: $0 <mach-o-binary>" >&2
exit 2
fi

binary="$1"
if [ ! -f "$binary" ]; then
echo "Mach-O binary not found: $binary" >&2
exit 1
fi

dependencies="$(otool -L "$binary")"

if ! grep -Fq "@rpath/libpq.5.dylib" <<<"$dependencies"; then
echo "missing @rpath dependency for libpq in $binary" >&2
exit 1
fi

# The leading '/' is matched by the '^[[:space:]]+/' portion of the regex, so the
# alternatives within the group omit it: 'opt/homebrew', 'usr/local', 'opt/local'.
if grep -Eq '^[[:space:]]+/(opt/homebrew|usr/local|opt/local)/.*libpq\.5\.dylib' \
<<<"$dependencies"; then
echo "package-manager-specific libpq dependency remains in $binary" >&2
exit 1
fi

rpaths="$(otool -l "$binary" | awk '/cmd LC_RPATH/{getline; getline; print $2}')"
for required in \
/opt/homebrew/opt/libpq/lib \
/usr/local/opt/libpq/lib \
/opt/homebrew/lib/postgresql@18 \
/usr/local/lib/postgresql@18 \
/opt/homebrew/opt/postgresql@18/lib \
/usr/local/opt/postgresql@18/lib; do
if ! grep -Fxq "$required" <<<"$rpaths"; then
echo "missing libpq rpath $required in $binary" >&2
exit 1
fi
done
Loading