#!/usr/bin/env bash set -euo pipefail shopt -s nullglob # --- config (SCENE CONSUMER PLUGIN) --- NAME="FFExportScene" CLASS="FFExportScenePlugIn" SRC="${SRC:-${CLASS}.m}" PLUG="$NAME.plugin" OUT="$(pwd)/build-manual-scene" INST="$HOME/Library/Graphics/Quartz Composer Plug-Ins" XCODE_APP="${XCODE_APP:-/Applications/Xcode_9.4.1.app}" DEV="$XCODE_APP/Contents/Developer" SDKDIR="$DEV/Platforms/MacOSX.platform/Developer/SDKs" SDK="${SDK:-}" if [[ -z "${SDK}" ]]; then if [[ -d "$SDKDIR/MacOSX10.14.sdk" ]]; then SDK="$SDKDIR/MacOSX10.14.sdk" elif [[ -d "$SDKDIR/MacOSX10.13.sdk" ]]; then SDK="$SDKDIR/MacOSX10.13.sdk" else SDK="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || true)" fi fi [[ -d "$DEV" ]] || { echo "Xcode not found: $XCODE_APP"; exit 1; } [[ -f "$SRC" ]] || { echo "Source not found: $SRC"; exit 1; } [[ -n "$SDK" && -d "$SDK" ]] || { echo "macOS SDK not found."; exit 1; } export DEVELOPER_DIR="$DEV" # --- FFmpeg via MacPorts pkg-config --- PKGCFG="/opt/local/bin/pkg-config" [[ -x "$PKGCFG" ]] || { echo "pkg-config not found at $PKGCFG (install via MacPorts)"; exit 1; } PKG_LIBS=(libavformat libavcodec libavutil libswscale) CFLAGS_FFMPEG="$("$PKGCFG" --cflags "${PKG_LIBS[@]}")" LIBS_FFMPEG="$("$PKGCFG" --libs "${PKG_LIBS[@]}")" echo "Using SDK: $SDK" rm -rf "$OUT" mkdir -p "$OUT/x86_64" "$OUT/universal/$PLUG/Contents/MacOS" "$OUT/universal/$PLUG/Contents/Frameworks" FRAMEWORKS="$OUT/universal/$PLUG/Contents/Frameworks" if [[ -d "$INST/$PLUG" ]]; then echo "Removing installed $INST/$PLUG" rm -rf "$INST/$PLUG" fi COMMON_CFLAGS=( -bundle -fobjc-arc -fobjc-link-runtime -isysroot "$SDK" -mmacosx-version-min=10.9 -I . -I /opt/local/include ) COMMON_LIBS=( -framework Foundation -framework Quartz -framework OpenGL -framework CoreGraphics ) echo "Compiling x86_64 (FFmpeg scene export)…" clang -arch x86_64 \ "${COMMON_CFLAGS[@]}" \ $CFLAGS_FFMPEG \ "$SRC" \ "${COMMON_LIBS[@]}" \ $LIBS_FFMPEG \ -o "$OUT/x86_64/$NAME" # Layout bundle cp -a "$OUT/x86_64/$NAME" "$OUT/universal/$PLUG/Contents/MacOS/$NAME" # Info.plist (NOTE: separate identifier just for the scene plug-in) cat >"$OUT/universal/$PLUG/Contents/Info.plist" < CFBundleDevelopmentRegion English CFBundleExecutable ${NAME} CFBundleIdentifier com.yourdomain.${NAME} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${NAME} CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSupportedPlatforms MacOSX CFBundleVersion 1 QCPlugInClasses ${CLASS} NSPrincipalClass QCPlugIn PLIST # --- helpers for embedding FFmpeg dylibs from /opt/local/lib --- mk_short_symlink_if_needed() { local base="$1" if [[ "$base" =~ ^(lib[^.]+)\.([0-9]+)\.[0-9.]+\.dylib$ ]]; then local short="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.dylib" if [[ ! -e "$FRAMEWORKS/$short" ]]; then ( cd "$FRAMEWORKS" && ln -s "$base" "$short" ) fi fi } list_opt_local_deps() { otool -L "$1" | awk '$1 ~ /^\/opt\/local\/lib\// {print $1}' } copy_and_rewrite() { local src="$1"; [[ "$src" == /opt/local/lib/* ]] || return 0 local base dest; base="$(basename "$src")"; dest="$FRAMEWORKS/$base" if [[ ! -f "$dest" ]]; then echo " → Copy $base" rsync -aL "$src" "$dest" chmod u+w "$dest" install_name_tool -id "@loader_path/$base" "$dest" mk_short_symlink_if_needed "$base" while IFS= read -r dep; do local depbase; depbase="$(basename "$dep")" copy_and_rewrite "$dep" install_name_tool -change "$dep" "@loader_path/$depbase" "$dest" done < <(list_opt_local_deps "$dest") fi } seed_from_otool() { local bin="$1" while IFS= read -r path; do copy_and_rewrite "$path"; done < <( otool -L "$bin" | awk '$1 ~ /^\/opt\/local\/lib\/lib(avformat|avcodec|avutil|swscale).*\.dylib$/ {print $1}' ) } seed_from_pkgconfig() { for pc in "${PKG_LIBS[@]}"; do local libdir; libdir="$("$PKGCFG" --variable=libdir "$pc" 2>/dev/null || echo /opt/local/lib)" local cand for cand in "$libdir/${pc}".*.dylib "$libdir/${pc}.dylib"; do [[ -f "$cand" ]] && { copy_and_rewrite "$cand"; break; } done done } final_full_sweep() { for lib in "$FRAMEWORKS"/*.dylib; do while IFS= read -r dep; do local depbase; depbase="$(basename "$dep")" copy_and_rewrite "$dep" install_name_tool -change "$dep" "@loader_path/$depbase" "$lib" done < <(list_opt_local_deps "$lib") done } echo "Embedding FFmpeg dylibs…" BIN="$OUT/universal/$PLUG/Contents/MacOS/$NAME" seed_from_otool "$BIN" if ! compgen -G "$FRAMEWORKS/*.dylib" >/dev/null; then seed_from_pkgconfig fi while IFS= read -r dep; do base="$(basename "$dep")" if [[ ! -e "$FRAMEWORKS/$base" ]]; then stem="${base%.dylib}" stem="${stem%.*}" match=( "$FRAMEWORKS/$stem".*.dylib ) if [[ -e "${match[0]}" ]]; then mk_short_symlink_if_needed "$(basename "${match[0]}")" else copy_and_rewrite "$dep" fi fi install_name_tool -change "$dep" "@loader_path/../Frameworks/$base" "$BIN" done < <(list_opt_local_deps "$BIN") final_full_sweep echo "Codesigning bundled libs…" for lib in "$FRAMEWORKS"/*.dylib; do codesign --force -s - "$lib" >/dev/null || true done codesign --force -s - "$OUT/universal/$PLUG" >/dev/null || true echo "Installing to: $INST" mkdir -p "$INST" rsync -a "$OUT/universal/$PLUG" "$INST/" echo "Verifying install…" IBIN="$INST/$PLUG/Contents/MacOS/$NAME" leaks=0 if otool -L "$IBIN" | awk '$1 ~ /^\/opt\/local\/lib\//' | grep -q .; then echo "❌ main binary still references /opt/local/lib:" otool -L "$IBIN" | awk '$1 ~ /^\/opt\/local\/lib\// {print " " $1}' leaks=1 fi for lib in "$INST/$PLUG/Contents/Frameworks/"*.dylib; do if otool -L "$lib" | awk '$1 ~ /^\/opt\/local\/lib\//' | grep -q .; then echo "❌ $(basename "$lib") still references /opt/local/lib:" otool -L "$lib" | awk '$1 ~ /^\/opt\/local\/lib\// {print " " $1}' leaks=1 fi done if [[ $leaks -ne 0 ]]; then echo "Fixup failed; see above offending paths." exit 1 fi echo "Installed: $INST/$PLUG" echo "Embedded libs:" ls -1 "$INST/$PLUG/Contents/Frameworks" || true echo "Relaunch Quartz Composer and look for 'FFExport Scene (x86_64)'."