Skip to content

Commit 1b720ea

Browse files
authored
Merge pull request #44 from TableProApp/fix/bundle-dylibs-for-distribution
fix: bundle libpq and transitive dylibs into app for distribution
2 parents e16e21b + 4f06078 commit 1b720ea

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

.github/workflows/build.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,23 @@ jobs:
153153
exit 1
154154
fi
155155
156+
# Verify bundled dylibs
157+
FRAMEWORKS_DIR="build/Release/TablePro-arm64.app/Contents/Frameworks"
158+
if [ -d "$FRAMEWORKS_DIR" ]; then
159+
echo "Bundled dynamic libraries:"
160+
ls -lh "$FRAMEWORKS_DIR"/*.dylib 2>/dev/null || echo " (none)"
161+
162+
# Verify no Homebrew paths remain in the binary
163+
if otool -L "$BINARY_PATH" | grep -q '/opt/homebrew/\|/usr/local/opt/'; then
164+
echo "❌ ERROR: Binary still references Homebrew paths:"
165+
otool -L "$BINARY_PATH" | grep '/opt/homebrew/\|/usr/local/opt/'
166+
exit 1
167+
fi
168+
echo "✅ No Homebrew path references in binary"
169+
else
170+
echo "⚠️ WARNING: No Frameworks directory found — dylibs may not be bundled"
171+
fi
172+
156173
# Display info
157174
echo "✅ Build verified successfully"
158175
echo "Binary size: $(ls -lh "$BINARY_PATH" | awk '{print $5}')"
@@ -383,6 +400,23 @@ jobs:
383400
exit 1
384401
fi
385402
403+
# Verify bundled dylibs
404+
FRAMEWORKS_DIR="build/Release/TablePro-x86_64.app/Contents/Frameworks"
405+
if [ -d "$FRAMEWORKS_DIR" ]; then
406+
echo "Bundled dynamic libraries:"
407+
ls -lh "$FRAMEWORKS_DIR"/*.dylib 2>/dev/null || echo " (none)"
408+
409+
# Verify no Homebrew paths remain in the binary
410+
if otool -L "$BINARY_PATH" | grep -q '/opt/homebrew/\|/usr/local/opt/'; then
411+
echo "❌ ERROR: Binary still references Homebrew paths:"
412+
otool -L "$BINARY_PATH" | grep '/opt/homebrew/\|/usr/local/opt/'
413+
exit 1
414+
fi
415+
echo "✅ No Homebrew path references in binary"
416+
else
417+
echo "⚠️ WARNING: No Frameworks directory found — dylibs may not be bundled"
418+
fi
419+
386420
# Display info
387421
echo "✅ Build verified successfully"
388422
echo "Binary size: $(ls -lh "$BINARY_PATH" | awk '{print $5}')"

scripts/build-release.sh

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,96 @@ prepare_mariadb() {
6868
cd - > /dev/null || exit 1
6969
}
7070

71+
# Bundle non-system dynamic libraries into the app bundle
72+
# so the app runs without Homebrew on end-user machines.
73+
bundle_dylibs() {
74+
local app_path=$1
75+
local binary="$app_path/Contents/MacOS/TablePro"
76+
local frameworks_dir="$app_path/Contents/Frameworks"
77+
78+
echo "📦 Bundling dynamic libraries into app bundle..."
79+
mkdir -p "$frameworks_dir"
80+
81+
# Iteratively discover and copy all non-system dylibs.
82+
# Each pass scans the main binary + already-copied dylibs;
83+
# repeat until no new dylibs are found (handles transitive deps).
84+
local changed=1
85+
while [ "$changed" -eq 1 ]; do
86+
changed=0
87+
for target in "$binary" "$frameworks_dir"/*.dylib; do
88+
[ -f "$target" ] || continue
89+
90+
while IFS= read -r dep; do
91+
# Keep only non-system, non-rewritten absolute paths
92+
case "$dep" in
93+
/usr/lib/*|/System/*|@*|"") continue ;;
94+
esac
95+
96+
local name
97+
name=$(basename "$dep")
98+
99+
# Already bundled
100+
[ -f "$frameworks_dir/$name" ] && continue
101+
102+
if [ -f "$dep" ]; then
103+
echo " Copying $name"
104+
cp "$dep" "$frameworks_dir/$name"
105+
chmod 644 "$frameworks_dir/$name"
106+
changed=1
107+
else
108+
echo " ⚠️ WARNING: $dep not found on disk, skipping"
109+
fi
110+
done < <(otool -L "$target" 2>/dev/null | awk 'NR>1 {print $1}')
111+
done
112+
done
113+
114+
# Count bundled dylibs
115+
local count
116+
count=$(find "$frameworks_dir" -name '*.dylib' 2>/dev/null | wc -l | tr -d ' ')
117+
118+
if [ "$count" -eq 0 ]; then
119+
echo " No non-system dylibs to bundle"
120+
return 0
121+
fi
122+
123+
# Rewrite each dylib's own install name
124+
for fw in "$frameworks_dir"/*.dylib; do
125+
[ -f "$fw" ] || continue
126+
local name
127+
name=$(basename "$fw")
128+
install_name_tool -id "@executable_path/../Frameworks/$name" "$fw"
129+
done
130+
131+
# Rewrite all references in the main binary and every bundled dylib
132+
for target in "$binary" "$frameworks_dir"/*.dylib; do
133+
[ -f "$target" ] || continue
134+
135+
while IFS= read -r dep; do
136+
case "$dep" in
137+
/usr/lib/*|/System/*|@*|"") continue ;;
138+
esac
139+
140+
local name
141+
name=$(basename "$dep")
142+
143+
if [ -f "$frameworks_dir/$name" ]; then
144+
install_name_tool -change "$dep" "@executable_path/../Frameworks/$name" "$target"
145+
fi
146+
done < <(otool -L "$target" 2>/dev/null | awk 'NR>1 {print $1}')
147+
done
148+
149+
# Ad-hoc sign everything (required on Apple Silicon)
150+
echo " Signing bundled libraries..."
151+
for fw in "$frameworks_dir"/*.dylib; do
152+
[ -f "$fw" ] || continue
153+
codesign -fs - "$fw" 2>/dev/null || true
154+
done
155+
codesign -fs - "$binary" 2>/dev/null || true
156+
157+
echo "✅ Bundled $count dynamic libraries into Frameworks/"
158+
ls -lh "$frameworks_dir"/*.dylib 2>/dev/null
159+
}
160+
71161
build_for_arch() {
72162
local arch=$1
73163
echo ""
@@ -166,6 +256,9 @@ build_for_arch() {
166256
echo " ⚠️ WARNING: Source icon not found at $SOURCE_ICON"
167257
fi
168258

259+
# Bundle non-system dynamic libraries (libpq, OpenSSL, etc.)
260+
bundle_dylibs "$BUILD_DIR/$OUTPUT_NAME"
261+
169262
# Verify binary exists inside the copied bundle
170263
BINARY_PATH="$BUILD_DIR/$OUTPUT_NAME/Contents/MacOS/TablePro"
171264
if [ ! -f "$BINARY_PATH" ]; then

0 commit comments

Comments
 (0)