#!/usr/bin/env bash
# Builds the native Physarum macOS app (apple/PhysarumApp) and a distributable .dmg.
#
# Output: dist-macos/Physarum.app and dist-macos/Physarum.dmg
# Requires: Xcode (xcodebuild), xcodegen, hdiutil. For a notarised release also a
# "Developer ID Application" identity + a notarytool keychain profile.
#
# Signing (opt-in via env):
# PHYSARUM_SIGN_IDENTITY "Developer ID Application: NAME (TEAMID)" or "auto"
# PHYSARUM_NOTARY_PROFILE notarytool keychain profile name
# Both unset -> ad-hoc (local dev). Identity only -> signed, not notarised.
# Both set -> Developer ID signed + hardened + notarised + stapled (app and dmg).
set -euo pipefail
cd "$(dirname "$0")/.."
APP_NAME="Physarum"
SCHEME="Physarum"
TEAM_ID="U9X7M39R56"
PROJ_DIR="apple/PhysarumApp"
PROJ="$PROJ_DIR/$APP_NAME.xcodeproj"
ENTITLEMENTS="$PROJ_DIR/Physarum.macOS.entitlements"
EXPORT_PLIST="scripts/ExportOptions.plist"
OUT="dist-macos"
ARCHIVE="$OUT/$APP_NAME.xcarchive"
APP="$OUT/$APP_NAME.app"
for tool in xcodebuild xcodegen hdiutil codesign; do
command -v "$tool" >/dev/null 2>&1 || { echo "error: required tool not found: $tool" >&2; exit 1; }
done
[ -f "$ENTITLEMENTS" ] || { echo "error: missing $ENTITLEMENTS" >&2; exit 1; }
[ -f "$EXPORT_PLIST" ] || { echo "error: missing $EXPORT_PLIST" >&2; exit 1; }
# xcodebuild resolves CODE_SIGN_ENTITLEMENTS relative to the project SRCROOT
# (apple/PhysarumApp), not this script's cwd — pass an absolute path so it can't double up.
ENTITLEMENTS_ABS="$(pwd)/$ENTITLEMENTS"
EXPORT_PLIST_ABS="$(pwd)/$EXPORT_PLIST"
SIGN_IDENTITY="${PHYSARUM_SIGN_IDENTITY:-}"
NOTARY_PROFILE="${PHYSARUM_NOTARY_PROFILE:-}"
SIGN_MODE="none"
if [ -n "$SIGN_IDENTITY" ] || [ -n "$NOTARY_PROFILE" ]; then
[ -n "$SIGN_IDENTITY" ] || { echo "error: PHYSARUM_NOTARY_PROFILE set but PHYSARUM_SIGN_IDENTITY missing" >&2; exit 1; }
for t in xcrun ditto spctl; do command -v "$t" >/dev/null 2>&1 || { echo "error: required tool not found: $t" >&2; exit 1; }; done
if [ "$SIGN_IDENTITY" = auto ]; then
SIGN_IDENTITY="$(security find-identity -v -p codesigning | sed -n 's/.*"\(Developer ID Application: [^"]*\)".*/\1/p' | head -1)"
[ -n "$SIGN_IDENTITY" ] || { echo "error: no 'Developer ID Application' identity in keychain (auto)" >&2; exit 1; }
echo "→ signing identity (auto): $SIGN_IDENTITY"
fi
if [ -n "$NOTARY_PROFILE" ]; then SIGN_MODE="notarize"; else SIGN_MODE="sign"; fi
fi
rm -rf "$OUT"; mkdir -p "$OUT"
echo "→ xcodegen generate"
( cd "$PROJ_DIR" && xcodegen generate >/dev/null )
echo "→ archiving (Release, arm64 + x86_64)"
ARCHIVE_ARGS=(-project "$PROJ" -scheme "$SCHEME" -configuration Release
-destination 'generic/platform=macOS' -archivePath "$ARCHIVE"
ARCHS="arm64 x86_64" ONLY_ACTIVE_ARCH=NO)
if [ "$SIGN_MODE" = none ]; then
xcodebuild "${ARCHIVE_ARGS[@]}" \
CODE_SIGN_STYLE=Manual CODE_SIGN_IDENTITY="-" CODE_SIGNING_REQUIRED=NO archive
else
xcodebuild "${ARCHIVE_ARGS[@]}" \
CODE_SIGN_STYLE=Manual "CODE_SIGN_IDENTITY=$SIGN_IDENTITY" DEVELOPMENT_TEAM="$TEAM_ID" \
CODE_SIGNING_REQUIRED=YES ENABLE_HARDENED_RUNTIME=YES \
OTHER_CODE_SIGN_FLAGS="--timestamp" CODE_SIGN_ENTITLEMENTS="$ENTITLEMENTS_ABS" archive
fi
if [ "$SIGN_MODE" = none ]; then
cp -R "$ARCHIVE/Products/Applications/$APP_NAME.app" "$APP"
else
echo "→ exporting (Developer ID)"
xcodebuild -exportArchive -archivePath "$ARCHIVE" \
-exportOptionsPlist "$EXPORT_PLIST_ABS" -exportPath "$OUT" >/dev/null
fi
[ -d "$APP" ] || { echo "error: $APP not produced" >&2; exit 1; }
echo " archs: $(lipo -archs "$APP/Contents/MacOS/$APP_NAME" 2>/dev/null || echo '?')"
if [ "$SIGN_MODE" = notarize ]; then
echo "→ notarising + stapling app (waits for Apple)"
ditto -c -k --keepParent "$APP" "$OUT/$APP_NAME.app.zip"
xcrun notarytool submit "$OUT/$APP_NAME.app.zip" -p "$NOTARY_PROFILE" --wait
xcrun stapler staple "$APP"
rm -f "$OUT/$APP_NAME.app.zip"
fi
echo "→ creating DMG"
STAGE="$OUT/.dmg-stage"; mkdir -p "$STAGE"
cp -R "$APP" "$STAGE/"
ln -s /Applications "$STAGE/Applications"
[ -f macos/INSTALL.txt ] && cp macos/INSTALL.txt "$STAGE/"
hdiutil create -volname "$APP_NAME" -srcfolder "$STAGE" -ov -format UDZO "$OUT/$APP_NAME.dmg" >/dev/null
rm -rf "$STAGE"
if [ "$SIGN_MODE" != none ]; then
echo "→ signing DMG"
codesign --force --timestamp --sign "$SIGN_IDENTITY" "$OUT/$APP_NAME.dmg"
if [ "$SIGN_MODE" = notarize ]; then
echo "→ notarising + stapling DMG"
xcrun notarytool submit "$OUT/$APP_NAME.dmg" -p "$NOTARY_PROFILE" --wait
xcrun stapler staple "$OUT/$APP_NAME.dmg"
echo "→ Gatekeeper check (ship only if accepted/valid)"
xcrun stapler validate "$OUT/$APP_NAME.dmg"
spctl -a -vvv -t open --context context:primary-signature "$OUT/$APP_NAME.dmg" || true
spctl -a -vvv -t exec "$APP" || true
fi
fi
echo; echo "✓ $APP"; echo "✓ $OUT/$APP_NAME.dmg ($(du -h "$OUT/$APP_NAME.dmg" | cut -f1))"