diff --git a/other/create_bundle_app_macos.sh b/other/create_bundle_app_macos.sh index 729d0c8..c24ba76 100755 --- a/other/create_bundle_app_macos.sh +++ b/other/create_bundle_app_macos.sh @@ -14,6 +14,15 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # Project root holds bin/, icon/, dist/. This script lives in other/, so go up one. PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +# ── Optional runtime libraries ──────────────────────────────────────────────── +# These are loaded dynamically (dlopen) rather than linked, so they never show +# up in the executable's otool -L. They are only needed for optional hardware +# (PlutoSDR / AD936x). Bundle them — together with their dependency trees — only +# if they are present on the build machine; otherwise the bundle builds fine +# without them. +OPTIONAL_LIB_NAMES=(libiio libad9361) +OPTIONAL_LIB_DIRS=(/usr/local/lib /opt/homebrew/lib /opt/local/lib) + # ── Locate source .app ──────────────────────────────────────────────────────── if [[ $# -ge 1 ]]; then SRC_APP="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" @@ -171,9 +180,65 @@ build_bundle() { done <<< "$deps" } + # Seed an optional, dlopen'd dylib into the bundle (with its dependency tree). + # Arg: path to a dylib (possibly a symlink, e.g. /usr/local/lib/libiio.dylib). + seed_optional_lib() { + local link="$1" + + # Follow the symlink chain to the real file. + local real + real="$(cd "$(dirname "$link")" && pwd)/$(basename "$link")" + while [[ -L "$real" ]]; do + local target + target="$(readlink "$real")" + [[ "$target" == /* ]] || target="$(dirname "$real")/$target" + real="$target" + done + [[ -f "$real" ]] || { echo " (optional $(basename "$link") not found)"; return; } + + # Bundle the real file under the basename advertised by its install id, + # so dependents referencing that name (and our alias symlinks) resolve. + local idname + idname="$(basename "$(otool -D "$real" | tail -n +2 | head -1)")" + [[ -n "$idname" ]] || idname="$(basename "$real")" + local dst="$FWK_DIR/$idname" + + if [[ -z "${PROCESSED[$idname]:-}" ]]; then + echo " Bundling optional $idname" + cp "$real" "$dst" + chmod 755 "$dst" + PROCESSED[$idname]=1 + install_name_tool -id "@executable_path/../Frameworks/$idname" "$dst" + collect_and_copy "$dst" + fi + + # Recreate the symlink aliases (libfoo.dylib, libfoo.N.dylib) alongside + # it, so a dlopen() by any of those names finds the bundled copy. + local srcdir base an + srcdir="$(dirname "$link")" + base="${idname%%.*}" + for alias in "$srcdir/$base".dylib "$srcdir/$base".*.dylib; do + [[ -e "$alias" ]] || continue + an="$(basename "$alias")" + [[ "$an" == "$idname" ]] && continue + [[ -e "$FWK_DIR/$an" ]] && continue + ln -s "$idname" "$FWK_DIR/$an" + done + } + echo " Scanning dependencies..." collect_and_copy "$EXEC_PATH" + echo " Seeding optional dynamically-loaded libs..." + for optname in "${OPTIONAL_LIB_NAMES[@]}"; do + for optdir in "${OPTIONAL_LIB_DIRS[@]}"; do + if [[ -e "$optdir/$optname.dylib" ]]; then + seed_optional_lib "$optdir/$optname.dylib" + break + fi + done + done + while IFS= read -r existing; do local libname libname="$(basename "$existing")"