From 791ea1919ed4bf57be535050f032e07ffd476320 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 3 Apr 2026 15:58:29 -0700 Subject: [PATCH] fix(ci): add dylib verification step to macOS builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After bundling dylibs, verify with otool -L that every non-system dylib referenced by wowee_bin is present in the app bundle. Fails the build if any are missing — prevents silent repeat of #36/#41. Added to both build.yml and release.yml. --- .github/workflows/build.yml | 37 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 29 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca3b6206..6576844b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -253,6 +253,43 @@ jobs: # Ad-hoc codesign (allows running on the local machine) codesign --force --deep --sign - Wowee.app + - name: Verify bundled dylibs + run: | + set -euo pipefail + echo "=== dylib references for wowee_bin ===" + otool -L Wowee.app/Contents/MacOS/wowee_bin + + # Every non-system dylib referenced by the binary must resolve inside + # the bundle. System paths (/usr/lib, /System) are always present on + # macOS and don't need bundling. + missing=0 + while IFS= read -r dep; do + # Strip leading whitespace and version info: " /path/to/lib.dylib (compat ...)" + path=$(echo "$dep" | sed 's/^[[:space:]]*//;s/ (compat.*//') + case "$path" in + /usr/lib/*|/System/*|@executable_path/*|@rpath/*) continue ;; + esac + # Resolve @loader_path relative to the binary + resolved="${path/#@loader_path/Wowee.app/Contents/MacOS}" + if [ ! -f "$resolved" ]; then + basename=$(basename "$path") + if [ ! -f "Wowee.app/Contents/Frameworks/$basename" ]; then + echo "ERROR: unbundled dylib: $path" >&2 + missing=$((missing + 1)) + fi + fi + done < <(otool -L Wowee.app/Contents/MacOS/wowee_bin | tail -n +2) + + if [ "$missing" -gt 0 ]; then + echo "" + echo "=== Frameworks directory ===" + ls -la Wowee.app/Contents/Frameworks/ + echo "" + echo "FAIL: $missing dylib(s) missing from app bundle" >&2 + exit 1 + fi + echo "All non-system dylibs are bundled." + - name: Create DMG run: | set -euo pipefail diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92ca33b5..c0b9ec23 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -228,6 +228,35 @@ jobs: # Ad-hoc codesign codesign --force --deep --sign - Wowee.app + - name: Verify bundled dylibs + run: | + set -euo pipefail + echo "=== dylib references for wowee_bin ===" + otool -L Wowee.app/Contents/MacOS/wowee_bin + + missing=0 + while IFS= read -r dep; do + path=$(echo "$dep" | sed 's/^[[:space:]]*//;s/ (compat.*//') + case "$path" in + /usr/lib/*|/System/*|@executable_path/*|@rpath/*) continue ;; + esac + resolved="${path/#@loader_path/Wowee.app/Contents/MacOS}" + if [ ! -f "$resolved" ]; then + basename=$(basename "$path") + if [ ! -f "Wowee.app/Contents/Frameworks/$basename" ]; then + echo "ERROR: unbundled dylib: $path" >&2 + missing=$((missing + 1)) + fi + fi + done < <(otool -L Wowee.app/Contents/MacOS/wowee_bin | tail -n +2) + + if [ "$missing" -gt 0 ]; then + ls -la Wowee.app/Contents/Frameworks/ + echo "FAIL: $missing dylib(s) missing from app bundle" >&2 + exit 1 + fi + echo "All non-system dylibs are bundled." + - name: Create DMG run: | TAG="${GITHUB_REF_NAME}"