#!/usr/bin/env bash # Chanora iOS build helper. # # Run this on a macOS host that has already completed §4 of # docs/release/ios-build.md: # - Xcode 26+ with Command Line Tools # - Rust stable with aarch64-apple-ios, aarch64-apple-ios-sim, # x86_64-apple-ios targets # - Flutter 3.41.9 stable on PATH # - CocoaPods on PATH # - Pre-built libopus.a for each iOS arch (the script in §4.3 of the # docs leaves them in /tmp/opus-ios--wrap/lib/libopus.a) # # Usage (from the repo root ~/chanora): # ./tools/build-ios.sh # ./tools/build-ios.sh --no-codesign # unsigned Runner.app only # ./tools/build-ios.sh --regenerate-bindings # rerun FRB codegen first # ./tools/build-ios.sh --version v0.2.0-beta.1 # # Produces: # target/aarch64-apple-ios/release/libchanora_bridge.a # target/aarch64-apple-ios-sim/release/libchanora_bridge.a # target/x86_64-apple-ios/release/libchanora_bridge.a # target/universal-ios-sim/libchanora_bridge.a # target/ChanoraBridge.xcframework/ # apps/chanora_flutter/build/ios/iphoneos/Runner.app/ # apps/chanora_flutter/build/ios/ipa/chanora_flutter.ipa (if signed) # chanora--ios.ipa (renamed copy) set -euo pipefail VERSION="v0.2.0-beta.1" SKIP_CODESIGN=0 REGEN=0 EXPORT_METHOD="development" EXPORT_OPTIONS_PLIST="" while [[ $# -gt 0 ]]; do case "$1" in --version) VERSION="$2"; shift 2;; --no-codesign) SKIP_CODESIGN=1; shift;; --regenerate-bindings) REGEN=1; shift;; --export-method) EXPORT_METHOD="$2"; shift 2;; --export-options-plist) EXPORT_OPTIONS_PLIST="$2"; shift 2;; *) echo "unknown arg: $1" >&2; exit 2;; esac done cd "$(dirname "$0")/.." REPO_ROOT="$(pwd)" bar() { printf '%s\n' '========================================================================'; } bar echo "Chanora iOS build" echo "Repo root : $REPO_ROOT" echo "Version : $VERSION" echo "Sign : $([ "$SKIP_CODESIGN" = 1 ] && echo no || echo "yes (export-method=$EXPORT_METHOD)")" bar # ---------- 1. Verify toolchain ---------- echo "[1/7] Verify toolchain" for cmd in xcodebuild xcrun cargo rustc flutter pod lipo; do if ! command -v "$cmd" >/dev/null; then echo " ERROR: '$cmd' not on PATH. See docs/release/ios-build.md §4." >&2 exit 1 fi printf " %-10s : %s\n" "$cmd" "$($cmd --version 2>&1 | head -1)" done # Verify the three Rust iOS targets are installed. for tgt in aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios; do if ! rustup target list --installed | grep -q "^$tgt$"; then echo " Adding rustup target $tgt..." rustup target add "$tgt" fi done # Verify the pre-built libopus.a for each arch is available. for arch in arm64 arm64-simulator x86_64; do if [[ ! -f "/tmp/opus-ios-${arch}-wrap/lib/libopus.a" ]]; then echo " ERROR: /tmp/opus-ios-${arch}-wrap/lib/libopus.a not found." >&2 echo " See §4.3 of docs/release/ios-build.md to build libopus first." >&2 exit 1 fi done # ---------- 2. (Optional) regenerate FRB bindings ---------- if [[ $REGEN -eq 1 ]]; then echo "[2/7] Regenerate flutter_rust_bridge bindings" if ! command -v flutter_rust_bridge_codegen >/dev/null; then echo " flutter_rust_bridge_codegen not on PATH; cargo install it first." >&2 exit 1 fi flutter_rust_bridge_codegen generate else echo "[2/7] Skipping FRB codegen (use --regenerate-bindings if api.rs changed)" fi # ---------- 3. Add ios/ platform folder if missing ---------- FLUTTER_APP="$REPO_ROOT/apps/chanora_flutter" if [[ ! -d "$FLUTTER_APP/ios" ]]; then echo "[3/7] Add iOS platform to apps/chanora_flutter" (cd "$FLUTTER_APP" && flutter create --platforms=ios .) echo " NOTE: edit ios/Podfile to pin platform :ios, '13.0' (DEC-003)" echo " NOTE: edit ios/Runner/Info.plist to add NSMicrophoneUsageDescription" echo " NOTE: set Team + Bundle Identifier in Xcode Signing & Capabilities" else echo "[3/7] iOS platform already present in apps/chanora_flutter" fi # ---------- 4. Build Rust libs for the three iOS triples ---------- echo "[4/7] Build Rust libs for iOS" export LIBOPUS_STATIC=1 export LIBOPUS_NO_PKG=1 build_one() { local TARGET=$1 local OPUS_DIR=$2 echo " -> $TARGET" export LIBOPUS_LIB_DIR="$OPUS_DIR" export CMAKE_POLICY_VERSION_MINIMUM=3.5 export IPHONEOS_DEPLOYMENT_TARGET=13.0 rm -rf "target/$TARGET/release/build/audiopus_sys-"* cargo build --release --target "$TARGET" -p chanora_bridge } build_one aarch64-apple-ios /tmp/opus-ios-arm64-wrap build_one aarch64-apple-ios-sim /tmp/opus-ios-arm64-simulator-wrap build_one x86_64-apple-ios /tmp/opus-ios-x86_64-wrap # ---------- 5. lipo + xcodebuild -create-xcframework ---------- echo "[5/7] lipo simulator libs + create XCFramework" mkdir -p target/universal-ios-sim lipo -create \ target/aarch64-apple-ios-sim/release/libchanora_bridge.a \ target/x86_64-apple-ios/release/libchanora_bridge.a \ -output target/universal-ios-sim/libchanora_bridge.a echo " Universal sim: $(file target/universal-ios-sim/libchanora_bridge.a)" rm -rf target/ChanoraBridge.xcframework xcodebuild -create-xcframework \ -library target/aarch64-apple-ios/release/libchanora_bridge.a \ -library target/universal-ios-sim/libchanora_bridge.a \ -output target/ChanoraBridge.xcframework echo " XCFramework at: $REPO_ROOT/target/ChanoraBridge.xcframework" # ---------- 6. flutter build ios / ipa ---------- echo "[6/7] flutter pub get + pod install + flutter build" cd "$FLUTTER_APP" flutter pub get (cd ios && pod install) if [[ $SKIP_CODESIGN -eq 1 ]]; then flutter build ios --release --no-codesign APP_PATH="$FLUTTER_APP/build/ios/iphoneos/Runner.app" echo " Unsigned .app: $APP_PATH" else if [[ -n "$EXPORT_OPTIONS_PLIST" ]]; then if [[ -f "$REPO_ROOT/$EXPORT_OPTIONS_PLIST" ]]; then EXPORT_OPTIONS_PLIST="$REPO_ROOT/$EXPORT_OPTIONS_PLIST" elif [[ ! -f "$EXPORT_OPTIONS_PLIST" ]]; then echo " ERROR: export options plist not found: $EXPORT_OPTIONS_PLIST" >&2 exit 1 fi flutter build ipa --release --export-method "$EXPORT_METHOD" --export-options-plist "$EXPORT_OPTIONS_PLIST" else flutter build ipa --release --export-method "$EXPORT_METHOD" fi IPA_PATH="$FLUTTER_APP/build/ios/ipa/chanora_flutter.ipa" if [[ ! -f "$IPA_PATH" ]]; then echo " ERROR: expected $IPA_PATH, did not find one." >&2 ls -la "$FLUTTER_APP/build/ios/ipa/" 2>/dev/null || true exit 1 fi fi # ---------- 7. Rename signed ipa, print final paths ---------- echo "[7/7] Package release artefact" cd "$REPO_ROOT" FINAL_IPA="$HOME/chanora-$VERSION-ios.ipa" if [[ $SKIP_CODESIGN -ne 1 ]]; then cp "$FLUTTER_APP/build/ios/ipa/chanora_flutter.ipa" "$FINAL_IPA" shasum -a 256 "$FINAL_IPA" fi bar echo "iOS build complete." bar echo "Artefacts:" echo " Rust libs:" echo " target/aarch64-apple-ios/release/libchanora_bridge.a" echo " target/aarch64-apple-ios-sim/release/libchanora_bridge.a" echo " target/x86_64-apple-ios/release/libchanora_bridge.a" echo " target/universal-ios-sim/libchanora_bridge.a" echo " XCFramework:" echo " target/ChanoraBridge.xcframework/" if [[ $SKIP_CODESIGN -eq 1 ]]; then echo " Unsigned app bundle:" echo " apps/chanora_flutter/build/ios/iphoneos/Runner.app/" else echo " Signed .ipa:" echo " apps/chanora_flutter/build/ios/ipa/chanora_flutter.ipa" echo " $FINAL_IPA" fi