YrXtals/scripts/ios/release.sh

241 lines
8.1 KiB
Bash
Executable File

#!/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
# injects the DT* and BuildMachineOSBuild keys App Store validation demands.
SDK_VER="$(xcrun --sdk "$SDK_NAME" --show-sdk-version)"
SDK_BUILD="$(xcrun --sdk "$SDK_NAME" --show-sdk-build-version)"
XCODE_VER_LINE="$(xcodebuild -version | head -1)"
XCODE_BUILD_LINE="$(xcodebuild -version | grep 'Build version')"
XCODE_VER="$(echo "$XCODE_VER_LINE" | awk '{print $2}')"
XCODE_BUILD="$(echo "$XCODE_BUILD_LINE" | awk '{print $3}')"
IFS='.' read -r XV_MAJ XV_MIN XV_PAT <<< "$XCODE_VER"
XV_MAJ="${XV_MAJ:-0}"; XV_MIN="${XV_MIN:-0}"; XV_PAT="${XV_PAT:-0}"
DT_XCODE="$((XV_MAJ * 100 + XV_MIN * 10 + XV_PAT))"
OS_BUILD="$(sw_vers -buildVersion)"
set_or_add() {
local key="$1" val="$2"
/usr/libexec/PlistBuddy -c "Set :$key $val" "$APP/Info.plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :$key string $val" "$APP/Info.plist"
}
plutil -replace CFBundleSupportedPlatforms -json '["iPhoneOS"]' "$APP/Info.plist"
set_or_add BuildMachineOSBuild "$OS_BUILD"
set_or_add DTCompiler com.apple.compilers.llvm.clang.1_0
set_or_add DTPlatformBuild "$SDK_BUILD"
set_or_add DTPlatformName "$SDK_NAME"
set_or_add DTPlatformVersion "$SDK_VER"
set_or_add DTSDKBuild "$SDK_BUILD"
set_or_add DTSDKName "${SDK_NAME}${SDK_VER}"
set_or_add DTXcode "$DT_XCODE"
set_or_add DTXcodeBuild "$XCODE_BUILD"
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
/usr/libexec/PlistBuddy -c "Delete :keychain-access-groups" "$ENT" 2>/dev/null || true
# 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)"