#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOT" case "$(uname -s)" in Darwin) ;; *) echo "wrong platform: $(uname -s), iOS release requires macOS" >&2; exit 1;; esac RUST_TARGET="aarch64-apple-ios" SDK_NAME="iphoneos" SWIFT_TARGET="arm64-apple-ios17.0" CARGO_PROFILE="release" PROFILE="${YRXTALS_IOS_APPSTORE_PROFILE:-/Volumes/External/prvProfiles/Yr_Xtals.mobileprovision}" if [ ! -f "$PROFILE" ]; then echo "ERROR: App Store provisioning profile not found at $PROFILE" >&2 echo " set YRXTALS_IOS_APPSTORE_PROFILE to override" >&2 exit 1 fi if [ -n "${VER:-}" ]; then if ! [[ "$VER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "ERROR: VER must be x.y.z (got '$VER')" >&2 exit 1 fi /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VER" "$ROOT/ios/Info.plist" /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VER" "$ROOT/ios/Info.plist" echo "Pinned ios/Info.plist version to $VER" fi BUILD="$ROOT/build" APP="$BUILD/ios-release/YrXtals.app" PAYLOAD="$BUILD/ios-release/Payload" IPA="$BUILD/ios-release/YrXtals.ipa" RUST_LIB="/tmp/yr_crystals-target/$RUST_TARGET/$CARGO_PROFILE" SDK="$(xcrun --sdk "$SDK_NAME" --show-sdk-path)" export CC=/usr/bin/clang export CXX=/usr/bin/clang++ export IPHONEOS_DEPLOYMENT_TARGET=17.0 echo "Building Rust staticlib for $RUST_TARGET (profile=$CARGO_PROFILE)..." cargo build --profile "$CARGO_PROFILE" --target "$RUST_TARGET" --lib rm -f "$RUST_LIB/libyr_crystals.dylib" "$RUST_LIB/deps/libyr_crystals.dylib" if [ ! -f "$RUST_LIB/libyr_crystals.a" ]; then echo "ERROR: libyr_crystals.a not found at $RUST_LIB" >&2 exit 1 fi rm -rf "$BUILD/ios-release" mkdir -p "$APP" cp "$ROOT/ios/Info.plist" "$APP/Info.plist" bash "$ROOT/scripts/ios/generate-icons.sh" ACTOOL_PARTIAL="$BUILD/ios-release/actool-partial-info.plist" echo "Compiling asset catalog..." xcrun actool "$ROOT/ios/Assets.xcassets" \ --compile "$APP" \ --platform "$SDK_NAME" \ --minimum-deployment-target 17.0 \ --app-icon AppIcon \ --output-partial-info-plist "$ACTOOL_PARTIAL" \ --target-device ipad \ --target-device iphone \ >/dev/null if [ -f "$ACTOOL_PARTIAL" ]; then /usr/libexec/PlistBuddy -c "Merge $ACTOOL_PARTIAL" "$APP/Info.plist" 2>/dev/null || true fi RUST_FLAGS=(-import-objc-header "$ROOT/include/yr_xtals.h" -L "$RUST_LIB" -lyr_crystals) echo "Compiling Swift (release, no DEBUG)..." xcrun -sdk "$SDK_NAME" swiftc \ -target "$SWIFT_TARGET" \ -sdk "$SDK" \ "${RUST_FLAGS[@]}" \ -framework UIKit \ -framework SwiftUI \ -framework QuartzCore \ -framework Metal \ -framework MetalKit \ -framework AVFoundation \ -framework AudioToolbox \ -framework MediaPlayer \ -framework CoreGraphics \ -framework CoreFoundation \ -O \ -whole-module-optimization \ -o "$APP/YrXtals" \ "$ROOT"/ios/src/*.swift cp "$PROFILE" "$APP/embedded.mobileprovision" ENT="$BUILD/ios-release/entitlements.plist" security cms -D -i "$PROFILE" 2>/dev/null \ | plutil -extract Entitlements xml1 -o "$ENT" - \ || { echo "ERROR: could not extract entitlements from profile" >&2; exit 1; } # forces get-task-allow=false in entitlements. /usr/libexec/PlistBuddy -c "Set :get-task-allow false" "$ENT" 2>/dev/null \ || /usr/libexec/PlistBuddy -c "Add :get-task-allow bool false" "$ENT" # pins wildcard application-identifier to the concrete TEAM.bundle.id form. BUNDLE_ID="$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP/Info.plist")" TEAM_ID="$(/usr/libexec/PlistBuddy -c "Print :com.apple.developer.team-identifier" "$ENT" 2>/dev/null \ || /usr/libexec/PlistBuddy -c "Print :application-identifier" "$ENT" | cut -d. -f1)" case "$(/usr/libexec/PlistBuddy -c "Print :application-identifier" "$ENT")" in *\*) /usr/libexec/PlistBuddy -c "Set :application-identifier ${TEAM_ID}.${BUNDLE_ID}" "$ENT" ;; esac # locates the Apple Distribution identity in the keychain by SHA-matching the profile's certs. TMPDIR_PROF="$(mktemp -d)" PROFILE_PLIST="$TMPDIR_PROF/profile.plist" security cms -D -i "$PROFILE" > "$PROFILE_PLIST" 2>/dev/null PROFILE_SHAS="" for i in 0 1 2 3 4 5 6 7 8 9; do if ! plutil -extract "DeveloperCertificates.$i" raw -o "$TMPDIR_PROF/c$i.b64" "$PROFILE_PLIST" >/dev/null 2>&1; then break fi base64 -D -i "$TMPDIR_PROF/c$i.b64" -o "$TMPDIR_PROF/c$i.cer" sha=$(openssl x509 -inform der -in "$TMPDIR_PROF/c$i.cer" -fingerprint -noout 2>/dev/null \ | sed 's/.*=//;s/://g') PROFILE_SHAS="$PROFILE_SHAS $sha" done KEYCHAIN_SHAS=$(security find-identity -v -p codesigning 2>/dev/null \ | awk '/[0-9A-F]{40}/ {gsub(/[^0-9A-F]/, "", $2); print $2}') IDENTITY="" for s in $PROFILE_SHAS; do if echo "$KEYCHAIN_SHAS" | grep -qi "^$s$"; then IDENTITY="$s" break fi done rm -rf "$TMPDIR_PROF" if [ -z "$IDENTITY" ]; then echo "ERROR: no Apple Distribution identity in keychain matches the profile's certs" >&2 echo " profile certs:$PROFILE_SHAS" >&2 exit 1 fi echo "Signing with Apple Distribution $IDENTITY..." codesign --force \ --sign "$IDENTITY" \ --entitlements "$ENT" \ --options runtime \ --timestamp \ "$APP" codesign --verify --deep --strict --verbose=2 "$APP" mkdir -p "$PAYLOAD" cp -R "$APP" "$PAYLOAD/YrXtals.app" rm -f "$IPA" ( cd "$BUILD/ios-release" && zip -qry "$(basename "$IPA")" Payload ) rm -rf "$PAYLOAD" SHIPPED_VER="$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP/Info.plist")" # resolves BUMP from --bump flag, BUMP env var, or fallback default. EXPLICIT_BUMP="" for arg in "$@"; do case "$arg" in --bump) EXPLICIT_BUMP=patch ;; --bump=*) EXPLICIT_BUMP="${arg#--bump=}" ;; esac done if [ -n "$EXPLICIT_BUMP" ]; then BUMP="$EXPLICIT_BUMP" elif [ -z "${BUMP:-}" ]; then if [ -n "${VER:-}" ]; then BUMP=none else BUMP=patch fi fi case "$BUMP" in none) echo "Skipping version bump (BUMP=none)" ;; major|minor|patch) IFS='.' read -r CUR_MAJ CUR_MIN CUR_PATCH <<< "$SHIPPED_VER" CUR_MAJ="${CUR_MAJ:-0}"; CUR_MIN="${CUR_MIN:-0}"; CUR_PATCH="${CUR_PATCH:-0}" case "$BUMP" in major) NEW_VER="$((CUR_MAJ + 1)).0.0" ;; minor) NEW_VER="$CUR_MAJ.$((CUR_MIN + 1)).0" ;; patch) NEW_VER="$CUR_MAJ.$CUR_MIN.$((CUR_PATCH + 1))" ;; esac /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $NEW_VER" "$ROOT/ios/Info.plist" /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $NEW_VER" "$ROOT/ios/Info.plist" echo "Bumped ios/Info.plist: $SHIPPED_VER → $NEW_VER ($BUMP) for next release" ;; *) echo "ERROR: BUMP must be patch|minor|major|none, got '$BUMP'" >&2 exit 1 ;; esac echo "Built: $IPA ($BUNDLE_ID @ $SHIPPED_VER)"