Files
chanora/docs/release/ios-build.md
T
EdisonJwa 1324f478fe docs(release): add iOS build instructions + shell helper
The development host is Linux x86_64; the iOS toolchain (Xcode, xcrun,
codesign, iPhoneOS SDK) is macOS-only under Apple licence and cannot
be cross-compiled from Linux. This commit adds the instructions for
producing the iOS v0.2.0-beta.1 build on a macOS host plus a bash
helper that automates the build itself.

docs/release/ios-build.md (v0.1.0):
  - Toolchain pin table (macOS 14+, Xcode 26+ per DEC-021, iOS SDK
    26+, iOS deployment target 13.0 per DEC-003, Flutter 3.41.9,
    Rust 1.95 stable with aarch64-apple-ios / aarch64-apple-ios-sim /
    x86_64-apple-ios targets, CocoaPods 1.16+, FRB 2.12.0).
  - macOS host options: owned hardware vs rental (MacStadium,
    MacinCloud, Scaleway Apple silicon, AWS EC2 Mac) vs borrowed
    Mac. Realistic cost ranges per option.
  - Step-by-step Homebrew + Rust + Flutter + CocoaPods install.
  - Pre-built libopus.a per arch via a CMake invocation that
    targets the iOS SDK explicitly. Mirrors the Android build's
    LIBOPUS_LIB_DIR wrap-dir trick.
  - flutter create --platforms=ios to scaffold the ios/ folder
    (the product Flutter app was created with only linux + android).
  - Edits required to ios/Podfile and ios/Runner/Info.plist:
    iOS 13 deployment target (DEC-003), NSMicrophoneUsageDescription
    for the audio engine, UIBackgroundModes=audio for screen-locked
    playback.
  - Three cargo build --target invocations for device + both
    simulator slices.
  - lipo merge of the two simulator slices into one .a.
  - xcodebuild -create-xcframework to produce
    target/ChanoraBridge.xcframework with the right slices.
  - flutter build ios --release --no-codesign or
    flutter build ipa --release --export-method development for
    a signed .ipa.
  - Install paths: xcrun devicectl for wired install, altool for
    TestFlight upload.
  - Smoke-test instructions with the same hostname-resolution
    caveat that affects the Android Beta (hickory-resolver does
    not work on iOS; use the literal IP).
  - Packaging into chanora-v0.2.0-beta.1-ios.ipa.
  - Known-issue table covering: audiopus_sys cmake build failures
    on iOS, microphone permission prompt prerequisites,
    AVAudioSession category quirks for voice transmission, code-
    signing failure modes, TestFlight rejection causes.
  - Reproducibility note (build is not bit-reproducible).

tools/build-ios.sh:
  - Parameter switches: --version, --no-codesign,
    --regenerate-bindings, --export-method.
  - Verifies xcodebuild, xcrun, cargo, rustc, flutter, pod, lipo
    on PATH.
  - Adds the three rustup iOS targets if missing.
  - Verifies each pre-built libopus.a exists at the expected wrap
    dir before starting.
  - Optionally regenerates FRB bindings.
  - Three cargo build runs (device + Apple-silicon sim + Intel
    sim), each with LIBOPUS_LIB_DIR pointed at its arch's wrap dir
    and the audiopus_sys build-cache wiped per target.
  - lipo + xcodebuild -create-xcframework.
  - flutter pub get + pod install + flutter build {ios,ipa}.
  - Copies the .ipa to a versioned path under $HOME and prints
    SHA-256.

This is documentation + helper only; no actual iOS binaries are
produced by this commit. The Linux development host cannot run
Xcode. To produce the binaries, follow §3-§14 of
docs/release/ios-build.md on a macOS host, or run
tools/build-ios.sh there.

DEC-011.1 iOS audio status remains Deferred; the doc notes that
cpal's iOS backend has not been empirically verified and the
AVAudioSession category likely needs configuration for voice
transmission. Both are Beta+ items.
2026-05-14 23:49:58 +08:00

23 KiB
Raw Blame History

Building Chanora v0.2.0-beta.1 on iOS via macOS host

Document type: Release / Platform Build Instructions Version: 0.1.0 Status: Draft Language: English Product: Chanora Repo path: docs/release/ios-build.md


1. Purpose

This document gives exact step-by-step instructions for building the Chanora v0.2.0-beta.1 Internal Beta on an Apple macOS host. It exists because the development host is Linux x86_64; the entire iOS toolchain (Xcode, xcrun, codesign, xcodebuild, the iPhoneOS SDK) is macOS-only under Apple licence and cannot be cross-compiled from Linux. Even projects like xcbuild or cctools-port cannot produce a signed .ipa that an iOS device's installd will accept.

After following this doc you will have:

  • Runner.app — the iOS app bundle.
  • libchanora_bridge.a and/or an XCFramework containing both device (arm64) and simulator (arm64+x86_64) slices of the Rust cdylib.
  • A Runner.ipa archive suitable for ad-hoc / TestFlight / App Store distribution depending on the provisioning profile you sign with.

The doc assumes you have:

  • A macOS host (physical Mac or rental — see §3 for options).
  • An Apple Developer account ($99/year). A free Apple ID can build for personal devices via Xcode automatic signing, but profiles expire in 7 days; the paid account is strongly recommended.
  • Permission to enrol a test iPhone or iPad in the developer team.
  • The Chanora source code (this repository), reachable from the Mac (git remote, scp, or AirDrop).

2. Toolchain versions pinned for this build

Tool Version Why
macOS 14 Sonoma or 15 Sequoia Xcode 26 requires macOS 14+ at minimum
Xcode 26 or newer (DEC-021) Required to upload to App Store Connect on or after 2026-04-28. Beta builds for internal devices also pin to 26+ for parity.
iOS SDK 26 or newer (DEC-021) Bundled with Xcode 26+.
iOS deployment target iOS 13 (DEC-003) Matches Flutter 3.41.9's baseline support. The Podfile and Runner.xcodeproj must set this.
Command Line Tools Latest stable (xcode-select --install or via Xcode) Provides xcrun, xcodebuild, codesign.
Flutter SDK 3.41.9 stable Same pin as Linux/Android/Windows builds.
Dart SDK 3.11.5 Bundled with Flutter.
Rust toolchain stable 1.95 (or newer) Workspace rust-version = "1.95".
Rust targets aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios Device + Apple-silicon simulator + Intel-Mac simulator.
cbindgen (optional) 0.27+ Only if you wire a C header for the bridge; FRB does not require it.
cargo-lipo (legacy) not required cargo-lipo was deprecated for multi-arch iOS in favor of cargo build --target <triple> + xcodebuild -create-xcframework.
CocoaPods 1.16+ Required by Flutter iOS builds (Podfile in apps/chanora_flutter/ios/).
Opus library bundled via audiopus_sys build script with iOS-specific CMake toolchain Apple does not ship libopus; we cross-compile it per Rust target.
Apple Developer ID + certificate one-time Required to sign the .ipa. Free Apple ID OK for personal-device install (7-day profile).

Why not Xcode 15 or 16? Apple's App Store Connect upload gate (DEC-021) requires the iOS 26 SDK on or after 2026-04-28. Even for internal Beta where you don't upload, we pin Xcode 26+ so the same archive can be re-signed for TestFlight without re-building.


3. macOS host options

3.1 Owned Apple hardware (cheapest long-term)

Hardware Min spec Notes
Mac mini (M2, 2023+) 16 GiB RAM, 256 GiB SSD Best price/perf; ~USD $799 new
MacBook Air / Pro (M2+) 16 GiB RAM Portable; ~USD $1099+ new
Used Intel Mac (2018+) 16 GiB RAM Will need macOS 14+; check Apple's compatibility list

Apple's licence forbids running macOS on non-Apple hardware ("Hackintoshes") for any commercial purpose. Don't.

3.2 macOS rental (no hardware purchase)

Provider Cost Notes
MacStadium USD ~$79+/month for an M-series Mac mini Dedicated host; supports Xcode + signing
MacinCloud USD ~$30+/month "Pay-As-You-Go" or "Dedicated Server" plans Shared options exist; choose Dedicated for full Xcode access
Scaleway Apple silicon (EU) EUR ~0.13/hour for Mac mini M2 Hourly billing; cheap for short builds
AWS EC2 Mac (mac1.metal, mac2.metal) USD ~$1.08/hour with a 24h minimum allocation Most expensive; use only if you need AWS integration

Apple's licence permits cloud hosts that comply with their terms. All of the above explicitly meet that requirement.

3.3 Borrowed Mac

Find a friend or coworker with a Mac that can run Xcode 26. Install your dev certificate to their Keychain locally for the build session only, then remove it. This is the cheapest option if you have access.


4. Install the toolchain (one-time per Mac)

All commands assume a normal user account; only the Xcode install needs an Administrator password.

4.1 Xcode + Command Line Tools

Either:

# Easiest: install Xcode from the Mac App Store.
# (Or download the .xip from https://developer.apple.com/download/all/)
open "https://apps.apple.com/app/xcode/id497799835"

# Then accept the licence and install Command Line Tools.
sudo xcodebuild -license accept
xcode-select --install   # idempotent; skip if already done

# Verify.
xcodebuild -version       # expect Xcode 26.x
xcrun --version
xcrun simctl list runtimes | head

If you already have Xcode 16 or 17 installed, add Xcode 26 side-by-side (don't replace) and switch via sudo xcode-select -s /Applications/Xcode-26.app.

4.2 Homebrew + Rust + Flutter + CocoaPods

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Rust (managed by rustup, not brew)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
source "$HOME/.cargo/env"

# Add the three iOS targets.
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios

# Flutter — pinned to 3.41.9 stable to match the Linux / Android
# builds.
brew install --cask flutter
flutter --version | head -3
flutter precache --ios

# CocoaPods.
brew install cocoapods
pod --version  # expect 1.16 or newer

If your Mac is Apple-silicon and Homebrew installs to /opt/homebrew, make sure that's on PATH for new shells: eval "$(/opt/homebrew/bin/brew shellenv)".

4.3 Opus

audiopus_sys ships an Opus source tree and tries to build it via CMake. On iOS the cmake-crate's generated build sometimes confuses the iOS toolchain. We use the same pre-built libopus strategy that the Android build uses:

# Get the audiopus_sys-bundled libopus source after a first cargo
# fetch (it gets unpacked into the registry).
cd ~/chanora
cargo fetch
OPUS_SRC=$(find ~/.cargo/registry/src -path '*audiopus_sys-*/opus' -type d | head -1)
mkdir -p /tmp/opus-ios
cp -R "$OPUS_SRC"/* /tmp/opus-ios/

# Helper: build libopus.a for one iOS arch.
build_opus_ios() {
    local ARCH=$1                  # arm64 | x86_64 | arm64-simulator
    local SDK=$2                   # iphoneos | iphonesimulator
    local OUT=/tmp/opus-ios-${ARCH}
    rm -rf /tmp/opus-ios/build && mkdir /tmp/opus-ios/build
    cd /tmp/opus-ios/build
    cmake .. \
        -DCMAKE_SYSTEM_NAME=iOS \
        -DCMAKE_OSX_SYSROOT=$(xcrun --sdk "$SDK" --show-sdk-path) \
        -DCMAKE_OSX_ARCHITECTURES="${ARCH%-simulator}" \
        -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \
        -DBUILD_SHARED_LIBS=OFF \
        -DOPUS_BUILD_PROGRAMS=OFF \
        -DOPUS_BUILD_TESTING=OFF \
        -DCMAKE_INSTALL_PREFIX="$OUT" \
        -G "Unix Makefiles"
    make -j8 install
    # Wrap so LIBOPUS_LIB_DIR appends /lib correctly.
    mkdir -p "$OUT-wrap/lib"
    cp "$OUT/lib/libopus.a" "$OUT-wrap/lib/"
    cd /tmp/opus-ios
}

build_opus_ios arm64 iphoneos                # device
build_opus_ios arm64-simulator iphonesimulator   # Apple-silicon Mac sim
build_opus_ios x86_64 iphonesimulator        # Intel Mac sim

You should end up with three wrap dirs each containing lib/libopus.a:

/tmp/opus-ios-arm64-wrap/lib/libopus.a
/tmp/opus-ios-arm64-simulator-wrap/lib/libopus.a
/tmp/opus-ios-x86_64-wrap/lib/libopus.a

4.4 Cargo target linker config

iOS targets need explicit linker config to find the SDK. Create ~/.cargo/config.toml:

[target.aarch64-apple-ios]
rustflags = ["-C", "link-arg=-fembed-bitcode"]

[target.aarch64-apple-ios-sim]
rustflags = ["-C", "link-arg=-fembed-bitcode"]

[target.x86_64-apple-ios]
rustflags = ["-C", "link-arg=-fembed-bitcode"]

Rust's built-in iOS targets already drive the right linker via xcrun, so usually no further config is needed. If you hit a "-platform_version not supported" error, ensure Xcode CLI tools are fresh: sudo xcode-select --reset.

4.5 (Optional) flutter_rust_bridge_codegen

Only needed if you modify crates/chanora_bridge/src/api.rs on the Mac. The bindings shipped in the repo are platform-agnostic Dart + Rust source and do not need regenerating per platform.

cargo install flutter_rust_bridge_codegen --version "=2.12.0"

5. Get the source onto the Mac

Same two options as the Windows doc:

Option A — git remote

# On Linux dev host: push to a private remote.
cd /home/milkice/chanora
git remote add origin git@github.com:<your-user>/chanora.git
git push --all
git push --tags
# On the Mac:
cd ~
git clone https://github.com/<your-user>/chanora.git
cd chanora
git checkout v0.2.0-beta.1

Option B — zip / AirDrop

# On Linux:
git -C /home/milkice/chanora archive --format=zip \
    --output=/tmp/chanora-v0.2.0-beta.1.zip v0.2.0-beta.1

# Transfer the zip to the Mac (AirDrop, scp, USB), then:
cd ~
unzip /Volumes/.../chanora-v0.2.0-beta.1.zip -d chanora
cd chanora

6. Add the iOS platform to the Flutter app

The product Flutter app was scaffolded with --platforms=linux,android, so the ios/ folder does not yet exist. Add it once on the Mac:

cd ~/chanora/apps/chanora_flutter
flutter create --platforms=ios .

This creates apps/chanora_flutter/ios/ containing:

ios/
  Runner/
    AppDelegate.swift
    Info.plist
    Runner-Bridging-Header.h
    Assets.xcassets/
  Runner.xcodeproj/
  Runner.xcworkspace/
  Podfile
  Flutter/

Important: Do not commit this ios/ folder back to the repo from the Mac unless you also do the corresponding edits the source-of-truth host uses (icons, bundle ID, signing config). For Beta it is fine to keep it Mac-only.

6.1 Set the iOS deployment target

Edit apps/chanora_flutter/ios/Podfile's first line to:

platform :ios, '13.0'   # DEC-003

And in Xcode (open Runner.xcworkspace):

  • Project → Runner → General → Minimum Deployments → iOS 13.0
  • Project → Runner → Signing & Capabilities → set your Team

6.2 Add iOS-required entitlements + Info.plist keys

Chanora's audio engine needs the microphone. Edit apps/chanora_flutter/ios/Runner/Info.plist to include:

<key>NSMicrophoneUsageDescription</key>
<string>Chanora uses the microphone for voice chat with TeamSpeak-compatible servers.</string>

<!-- Background-mode audio so playback continues when the screen
     locks. Optional for Beta; required for a real release. -->
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

<!-- Allow plain HTTP only for development. Remove before release.
     The protocol layer itself uses UDP, not HTTP, but Flutter's hot
     reload talks HTTP. -->
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

The bundle identifier should be set to a reverse-DNS string under your developer team, e.g. app.chanora.chanora_flutter. Update it in Xcode → Runner → General → Identity → Bundle Identifier.


7. Build the Rust libraries for iOS

We build three flavors and combine them into an XCFramework that Xcode can link against from both device and simulator slices.

cd ~/chanora
export LIBOPUS_STATIC=1
export LIBOPUS_NO_PKG=1

build_bridge_for_ios() {
    local TARGET=$1            # aarch64-apple-ios | aarch64-apple-ios-sim | x86_64-apple-ios
    local OPUS_DIR=$2          # /tmp/opus-ios-*-wrap
    export LIBOPUS_LIB_DIR="$OPUS_DIR"

    # Wipe audiopus_sys build cache so LIBOPUS_LIB_DIR is re-read.
    rm -rf target/$TARGET/release/build/audiopus_sys-*

    cargo build --release --target "$TARGET" -p chanora_bridge
}

build_bridge_for_ios aarch64-apple-ios           /tmp/opus-ios-arm64-wrap
build_bridge_for_ios aarch64-apple-ios-sim       /tmp/opus-ios-arm64-simulator-wrap
build_bridge_for_ios x86_64-apple-ios            /tmp/opus-ios-x86_64-wrap

Expected static-lib outputs (the [lib] section in crates/chanora_bridge/Cargo.toml already declares staticlib):

target/aarch64-apple-ios/release/libchanora_bridge.a       (~25 MiB, device)
target/aarch64-apple-ios-sim/release/libchanora_bridge.a   (~25 MiB, M-series simulator)
target/x86_64-apple-ios/release/libchanora_bridge.a        (~25 MiB, Intel simulator)

7.1 Build the simulator universal binary with lipo

The simulator side needs both arches in one .a for Xcode to pick the host architecture automatically:

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
file target/universal-ios-sim/libchanora_bridge.a

7.2 Build the XCFramework

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

Result:

target/ChanoraBridge.xcframework/
  Info.plist
  ios-arm64/libchanora_bridge.a
  ios-arm64_x86_64-simulator/libchanora_bridge.a

Open apps/chanora_flutter/ios/Runner.xcworkspace in Xcode.

  1. Drag target/ChanoraBridge.xcframework into the Runner project (Embed: "Do Not Embed" — it's a static lib).
  2. Project → Runner → Build Phases → Link Binary with Libraries → ensure ChanoraBridge.xcframework is listed.
  3. The FRB Dart loader expects to find the symbols in the main app binary on iOS (static linkage). The generated apps/chanora_flutter/lib/src/rust/frb_generated.io.dart should look up via DynamicLibrary.process() on iOS — this is the default for FRB v2 when the Rust crate type is staticlib.

If the dart-side loader expects a separate dylib, regenerate bindings with the iOS target hint:

flutter_rust_bridge_codegen generate

(The repo's bindings already target this style; no change usually needed.)


8. Regenerate the FRB bindings (optional)

Skip unless you've modified crates/chanora_bridge/src/api.rs on the Mac. The bindings are platform-agnostic Dart + Rust source.

cd ~/chanora
flutter_rust_bridge_codegen generate

Takes ~510 minutes on first run (cargo expand).


9. Build the iOS app via Flutter

cd ~/chanora/apps/chanora_flutter
flutter pub get
cd ios && pod install && cd ..
flutter build ios --release --no-codesign

flutter build ios --no-codesign produces an unsigned Runner.app. For signed .ipa for TestFlight or device install, use:

flutter build ipa --release --export-method development
# or: --export-method ad-hoc | enterprise | app-store

This requires:

  • A configured Team and Bundle Identifier (set in Xcode in §6.1).
  • A matching provisioning profile in your Keychain (Xcode's Automatic Signing usually handles this if you've signed into your Apple ID in Xcode → Settings → Accounts).

Expected output locations:

Variant Path
Unsigned app bundle apps/chanora_flutter/build/ios/iphoneos/Runner.app/
Signed .ipa apps/chanora_flutter/build/ios/ipa/chanora_flutter.ipa

10. Install on a physical iPhone/iPad

10.1 Wired install (USB or USB-C)

# List paired devices.
xcrun devicectl list devices

# Install the ipa.
xcrun devicectl device install app --device <UDID> \
    apps/chanora_flutter/build/ios/ipa/chanora_flutter.ipa

# Launch it.
xcrun devicectl device process launch \
    --device <UDID> --terminate-existing \
    app.chanora.chanora_flutter

10.2 TestFlight (wireless to many testers)

xcrun altool --upload-app \
    --type ios \
    --file apps/chanora_flutter/build/ios/ipa/chanora_flutter.ipa \
    --username "<your-apple-id>" \
    --password "<app-specific-password>"

Then in App Store Connect:

  1. Wait for processing (1030 minutes).
  2. Add the build to a TestFlight test group.
  3. Invite testers by Apple ID email.

Testers install via the TestFlight app on their device.


11. Smoke test on the iPhone/iPad

Open the installed Chanora app:

  1. Banner: "Beta build — voice in/out wired; not production ready."
  2. Form: pre-populated with cn.teamspeak.app + ChanoraBeta.
  3. Connect.

Known limitation (same as Android): the protocol layer's hickory-resolver cannot resolve hostnames on iOS by default. If Connect times out, enter the literal IP. For cn.teamspeak.app that is currently 175.178.125.23. A proper fix wires the iOS system resolver into hickory; Beta+ work.

  1. Once connected, the channel tree appears.
  2. Start audio → grant microphone permission when prompted.
  3. Hold to talk → frame counter increments under PTT.

If audio fails to start, check:

  • Microphone permission granted in Settings → Chanora → Microphone.
  • Device not in silent mode (output side only).
  • iOS Audio Session category — by default Flutter uses AVAudioSessionCategoryPlayback. Voice transmission needs PlayAndRecord with voiceChat mode. Beta+ work.

12. Package the release artefact

flutter build ipa already produces a single .ipa. Stamp it with the build identifier:

cd ~/chanora/apps/chanora_flutter
cp build/ios/ipa/chanora_flutter.ipa \
   "$HOME/chanora-v0.2.0-beta.1-ios.ipa"
shasum -a 256 "$HOME/chanora-v0.2.0-beta.1-ios.ipa"

Typical size: 6090 MiB (includes Flutter engine + Dart AOT + Rust static lib + bundled libopus + iOS resources).


13. Download to your local host

If on a rental Mac:

# From the Mac, copy back to Linux dev host.
scp "$HOME/chanora-v0.2.0-beta.1-ios.ipa" \
    user@your-linux-host:/home/milkice/

Or pull from the rental's web file manager. Keep the .ipa re-installable indefinitely if the provisioning profile is ad-hoc/enterprise; for development profiles it expires in 7 days.


14. Final artefact paths (what you actually produce)

On the Mac, after a successful build:

Artefact Path
Rust device static lib ~/chanora/target/aarch64-apple-ios/release/libchanora_bridge.a
Rust Apple-silicon sim lib ~/chanora/target/aarch64-apple-ios-sim/release/libchanora_bridge.a
Rust Intel sim lib ~/chanora/target/x86_64-apple-ios/release/libchanora_bridge.a
Lipo'd simulator lib ~/chanora/target/universal-ios-sim/libchanora_bridge.a
XCFramework ~/chanora/target/ChanoraBridge.xcframework/
Unsigned app bundle ~/chanora/apps/chanora_flutter/build/ios/iphoneos/Runner.app/
Signed .ipa ~/chanora/apps/chanora_flutter/build/ios/ipa/chanora_flutter.ipa
Renamed release .ipa ~/chanora-v0.2.0-beta.1-ios.ipa

15. After the build — deallocate (rental hosts)

Provider Deallocate command
MacStadium Suspend / shut down via control panel
MacinCloud "Stop" the session in account dashboard
Scaleway scw instance server stop <id>
AWS EC2 Mac Stop the host; release the dedicated host if at end of 24h minimum

Always verify the billing dashboard shows the instance as stopped, not just shut down at the OS level.


16. Known issues and caveats

Issue Workaround
audiopus_sys cmake-build fails with "no member named '__int128_t'" Re-export LIBOPUS_LIB_DIR to a pre-built .a directory (§4.3); never let cargo's cmake-build attempt to compile Opus on iOS.
hickory-resolver cannot resolve hostnames on iOS Use literal IP. Fix: wire dns-via-system resolver into chanora_protocol. Beta+ work.
Microphone permission prompt never appears The NSMicrophoneUsageDescription is missing from Info.plist. See §6.2.
Audio plays but no transmission iOS AVAudioSession category likely set to Playback not PlayAndRecord. Configure the session in Swift before Flutter loads, or via a small plugin. Beta+ work.
Code signing fails with "No signing certificate" Open Xcode → Settings → Accounts → add your Apple ID, then close and reopen Runner.xcworkspace to refresh.
flutter build ipa complains about missing entitlements Make sure the provisioning profile in Apple Developer console includes the App ID with microphone capability enabled.
TestFlight upload rejected: "Invalid bundle. Apple silicon support" Build with flutter build ipa --release (not --debug); debug AOT is JIT-only and not allowed in TestFlight.
App crashes on launch with dyld: Library not loaded: libchanora_bridge.dylib The bridge is built as staticlib, not cdylib, on iOS — FRB's loader uses DynamicLibrary.process(). If you accidentally built a cdylib, the loader path won't match. Stick to staticlib.
First flutter build ios takes > 30 minutes Normal — first Pod install + CocoaPods cache.

17. Reproducibility note

Like the Windows build, the iOS build is not bit-reproducible:

  • xcodebuild embeds a creation timestamp in the .ipa.
  • audiopus_sys picks the cmake-discovered Xcode toolchain version.
  • Dart AOT compilation embeds a Dart kernel hash.
  • Code-signing inherently makes every signed .ipa unique.

If true reproducibility is required for a future External Beta or MVP Public release, that is Beta+ work; the build above is sufficient for an Internal Beta milestone per DEC-001.


18. Change History

Version Date Description
0.1.0 2026-05-14 Initial iOS build instructions for Chanora v0.2.0-beta.1. Targets a macOS 14+ host running Xcode 26+ (DEC-021), Rust 1.95 stable with aarch64-apple-ios + aarch64-apple-ios-sim + x86_64-apple-ios targets, Flutter 3.41.9 stable, flutter_rust_bridge 2.12.0. Produces Runner.app + chanora_flutter.ipa via flutter build ipa --release. iOS deployment target pinned to 13.0 (DEC-003). Surface caveat: DEC-011.1 iOS audio remains Deferred — cpal's iOS backend has not been empirically verified, and AVAudioSession category configuration is required for voice transmission.