#!/bin/bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" source "$ROOT/scripts/common.sh" PROVISIONING=() case "${1:-}" in --allow-provisioning-updates) PROVISIONING=(-allowProvisioningUpdates) ;; '') ;; -h|--help) echo 'Usage: appstore/tools/archive.sh [--allow-provisioning-updates]'; echo 'Creates an App Store archive and locally exports an IPA. Never uploads.'; exit 0 ;; *) echo "Unknown option: $1" >&2; exit 2 ;; esac [[ $# -le 1 ]] || { echo 'Too many arguments' >&2; exit 2; } OUTPUT="$(mktemp -d "$ROOT/appstore/build/$(date +%Y%m%d-%H%M%S).XXXXXX")" DERIVED="$(mktemp -d "$TARNAX_SCRATCH_ROOT/appstore-derived.XXXXXX")" echo "Output: $OUTPUT" xcodebuild -project "$ROOT/Tarnax.xcodeproj" -scheme Tarnax -configuration Release \ -destination 'generic/platform=iOS' -derivedDataPath "$DERIVED" \ -archivePath "$OUTPUT/Tarnax.xcarchive" \ ${PROVISIONING[@]+"${PROVISIONING[@]}"} archive 2>&1 | tee "$OUTPUT/archive.log" APP="$OUTPUT/Tarnax.xcarchive/Products/Applications/Tarnax.app" [[ -f "$APP/PrivacyInfo.xcprivacy" ]] || { echo 'Privacy manifest missing from archive' >&2; exit 1; } plutil -lint "$APP/PrivacyInfo.xcprivacy" xcrun nm -m "$APP/Tarnax" > "$DERIVED/symbols.txt" if grep -E 'weak external.*Foundation4DateV2(ge|le)oiy' "$DERIVED/symbols.txt"; then echo 'Incompatible Foundation Date comparison entry point detected' >&2; exit 1 fi if grep -E 'StoreCapture|CaptureFixture' "$DERIVED/symbols.txt"; then echo 'Screenshot fixture leaked into production archive' >&2; exit 1 fi xcodebuild -exportArchive -archivePath "$OUTPUT/Tarnax.xcarchive" \ -exportPath "$OUTPUT/export" -exportOptionsPlist "$ROOT/appstore/build/ExportOptions-AppStore.plist" \ ${PROVISIONING[@]+"${PROVISIONING[@]}"} 2>&1 | tee "$OUTPUT/export.log" [[ -f "$OUTPUT/export/Tarnax.ipa" ]] || { echo 'IPA export missing' >&2; exit 1; } VERIFY="$(mktemp -d "$TARNAX_SCRATCH_ROOT/appstore-verify.XXXXXX")" trap 'rm -rf "$VERIFY"' EXIT unzip -q "$OUTPUT/export/Tarnax.ipa" -d "$VERIFY" codesign --verify --deep --strict "$VERIFY/Payload/Tarnax.app" security cms -D -i "$VERIFY/Payload/Tarnax.app/embedded.mobileprovision" > "$VERIFY/profile.plist" python3 - "$VERIFY" "$ROOT/appstore/build/latest.json" "$OUTPUT" <<'PY' from pathlib import Path import json,plistlib,sys,hashlib,datetime root=Path(sys.argv[1]);app=root/'Payload/Tarnax.app' with (app/'Info.plist').open('rb') as f:info=plistlib.load(f) with (root/'profile.plist').open('rb') as f:profile=plistlib.load(f) assert info['CFBundleIdentifier']=='net.absdev.Tarnax' assert not profile.get('ProvisionedDevices'), 'Device-limited profile is not App Store distribution' assert not profile.get('ProvisionsAllDevices'), 'Enterprise profile is not App Store distribution' assert not profile['Entitlements'].get('get-task-allow',False), 'Debugging enabled' assert profile['ExpirationDate']>datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) output=Path(sys.argv[3]);ipa=output/'export/Tarnax.ipa' checksum=hashlib.sha256(ipa.read_bytes()).hexdigest() (output/'export/Tarnax.ipa.sha256').write_text(checksum+' Tarnax.ipa\n') Path(sys.argv[2]).write_text(json.dumps({'version':info['CFBundleShortVersionString'],'build':info['CFBundleVersion'],'archive':str(output/'Tarnax.xcarchive'),'ipa':str(ipa),'sha256':checksum,'distribution':'app-store-connect','uploaded':False},indent=2)+'\n') print('Verified App Store IPA:',ipa) PY