Zum Inhalt

scripts/build-native-app.sh

#!/usr/bin/env bash
# Build the standalone fullscreen Metal screensaver app (KuroMetalApp) — the
# robust macOS-26 path that avoids the legacyScreenSaver host. Xcode-free
# (swiftc); shaders compile at runtime. Signed with Developer ID + hardened
# runtime if available, else ad-hoc (runs locally either way).
#
#   scripts/build-native-app.sh            # build + sign
#   open native/macos/build/KuroMetalApp.app
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SRC="$ROOT/native/macos/KuroNativeSaver"
APPSRC="$ROOT/native/macos/KuroMetalApp"
BUILD="$ROOT/native/macos/build"
APP="$BUILD/KuroMetalApp.app"
SIGN_ID="${KURO_SIGN_ID:-Developer ID Application: Johannes Kaindl (U9X7M39R56)}"

rm -rf "$APP"; mkdir -p "$APP/Contents/MacOS"

echo "compiling app…"
core_src=("$SRC"/Core/*.swift)
app_src=("$APPSRC"/*.swift)
swiftc -O \
  -target arm64-apple-macos14.0 \
  "${core_src[@]}" "${app_src[@]}" \
  -o "$APP/Contents/MacOS/KuroMetalApp" \
  -framework Metal -framework MetalPerformanceShaders -framework AVFoundation -framework QuartzCore \
  -framework AppKit -framework CoreText -framework Foundation

cp "$APPSRC/Info.plist" "$APP/Contents/Info.plist"

# Data resources. story-content.json is the narrative SSOT shared with the web engine;
# osm-district.json is the build-time-baked OSM district the metro scene renders. Both
# are loaded via Bundle.main — never fetched (AGENTS.md § Architecture notes).
mkdir -p "$APP/Contents/Resources"
cp "$ROOT/src/engine/data/story-content.json" "$ROOT/src/engine/data/osm-district.json" \
   "$APP/Contents/Resources/"

# App icon: generate AppIcon.icns from the committed 1024² source. Mandatory for a
# labelled, distributable app — fail loudly rather than silently shipping iconless.
ICON_SRC="$APPSRC/icon-1024.png"
[ -f "$ICON_SRC" ] || { echo "✗ missing app icon: $ICON_SRC (required for a labelled, distributable app)" >&2; exit 1; }
ISET="$BUILD/AppIcon.iconset"; rm -rf "$ISET"; mkdir -p "$ISET" "$APP/Contents/Resources"
for sz in 16 32 128 256 512; do
  sips -z "$sz" "$sz" "$ICON_SRC" --out "$ISET/icon_${sz}x${sz}.png" >/dev/null 2>&1
  sips -z "$((sz*2))" "$((sz*2))" "$ICON_SRC" --out "$ISET/icon_${sz}x${sz}@2x.png" >/dev/null 2>&1
done
iconutil -c icns "$ISET" -o "$APP/Contents/Resources/AppIcon.icns" && echo "icon generated"

echo "signing…"
if security find-identity -v -p codesigning 2>/dev/null | grep -q "$SIGN_ID"; then
  codesign --force --options runtime --timestamp --sign "$SIGN_ID" "$APP"
  echo "signed with Developer ID"
else
  codesign --force --sign - "$APP"
  echo "ad-hoc signed (no Developer ID found)"
fi
codesign --verify --strict "$APP" && echo "codesign verify OK"

echo "built: $APP"
echo "run:   open \"$APP\""