Zum Inhalt

apple/scripts/build-viewer-app.sh

#!/usr/bin/env bash
# Builds the native Physarum Ambiance Viewer macOS .app from the SwiftPM package.
#
# Output: apple/dist/Physarum Ambiance.app  (+ optional .dmg with DMG=1)
# Requires: swift (CLT), sips, iconutil, hdiutil. No full Xcode needed.
#
# NOTE: this produces an UNSIGNED app. SwiftPM resource bundles are placed at the
# .app root (where Bundle.module's generated accessor looks). That runs locally but
# is NOT code-signing-safe — before a notarised DMG release, resource loading must
# move to Contents/Resources (see docs). Signing/notarisation is a release step.
set -euo pipefail
cd "$(dirname "$0")/.."   # apple/

APP_NAME="Physarum Ambiance"
BUNDLE_ID="lab.physarum.viewer"
PKG="PhysarumEngine"
EXE="PhysarumViewer"
ICON_SVG="../assets/icon-macos.svg"
OUT="dist"
APP="$OUT/$APP_NAME.app"

for tool in swift sips iconutil; do
  command -v "$tool" >/dev/null 2>&1 || { echo "error: missing tool: $tool" >&2; exit 1; }
done

echo "→ swift build -c release"
( cd "$PKG" && swift build -c release )
BINDIR="$(cd "$PKG" && swift build -c release --show-bin-path)"

echo "→ assembling $APP_NAME.app"
rm -rf "$OUT"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"

cp "$BINDIR/$EXE" "$APP/Contents/MacOS/$EXE"
chmod +x "$APP/Contents/MacOS/$EXE"

# Resource bundles where Bundle.module's accessor looks (.app root).
for b in "$BINDIR"/*.bundle; do
  [ -e "$b" ] && cp -R "$b" "$APP/$(basename "$b")"
done

cat > "$APP/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleName</key><string>$APP_NAME</string>
  <key>CFBundleDisplayName</key><string>$APP_NAME</string>
  <key>CFBundleIdentifier</key><string>$BUNDLE_ID</string>
  <key>CFBundleExecutable</key><string>$EXE</string>
  <key>CFBundleIconFile</key><string>AppIcon</string>
  <key>CFBundlePackageType</key><string>APPL</string>
  <key>CFBundleShortVersionString</key><string>0.1.0</string>
  <key>CFBundleVersion</key><string>1</string>
  <key>LSMinimumSystemVersion</key><string>14.0</string>
  <key>LSApplicationCategoryType</key><string>public.app-category.graphics-design</string>
  <key>NSHighResolutionCapable</key><true/>
  <key>NSPrincipalClass</key><string>NSApplication</string>
  <key>NSHumanReadableCopyright</key><string>AGPL-3.0-or-later</string>
</dict>
</plist>
PLIST

if [ -f "$ICON_SVG" ]; then
  echo "→ rendering icon"
  ICONSET="$OUT/AppIcon.iconset"
  mkdir -p "$ICONSET"
  r() { sips -s format png "$ICON_SVG" --out "$ICONSET/$1" -Z "$2" >/dev/null; }
  r icon_16x16.png 16; r icon_16x16@2x.png 32; r icon_32x32.png 32; r icon_32x32@2x.png 64
  r icon_128x128.png 128; r icon_128x128@2x.png 256; r icon_256x256.png 256
  r icon_256x256@2x.png 512; r icon_512x512.png 512; r icon_512x512@2x.png 1024
  iconutil -c icns "$ICONSET" -o "$APP/Contents/Resources/AppIcon.icns"
  rm -rf "$ICONSET"
fi

# Ad-hoc sign so it is self-consistent locally (not notarised).
codesign --force --deep --sign - "$APP" >/dev/null 2>&1 || echo "  (codesign skipped)"

echo "✓ $APP"

if [ "${DMG:-0}" = 1 ]; then
  echo "→ creating DMG"
  STAGE="$OUT/.stage"; mkdir -p "$STAGE"
  cp -R "$APP" "$STAGE/"; ln -s /Applications "$STAGE/Applications"
  hdiutil create -volname "$APP_NAME" -srcfolder "$STAGE" -ov -format UDZO "$OUT/$APP_NAME.dmg" >/dev/null
  rm -rf "$STAGE"
  echo "✓ $OUT/$APP_NAME.dmg"
fi