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.
665 lines
23 KiB
Markdown
665 lines
23 KiB
Markdown
# 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:
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
/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:
|
||
|
||
```bash
|
||
# 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`:
|
||
|
||
```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.
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
# 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
|
||
```
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ruby
|
||
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:
|
||
|
||
```xml
|
||
<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.
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
### 7.3 Link the XCFramework from the Flutter iOS app
|
||
|
||
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:
|
||
|
||
```bash
|
||
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.
|
||
|
||
```bash
|
||
cd ~/chanora
|
||
flutter_rust_bridge_codegen generate
|
||
```
|
||
|
||
Takes ~5–10 minutes on first run (cargo expand).
|
||
|
||
---
|
||
|
||
## 9. Build the iOS app via Flutter
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
# 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)
|
||
|
||
```bash
|
||
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 (10–30 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.
|
||
|
||
4. Once connected, the channel tree appears.
|
||
5. **Start audio** → grant microphone permission when prompted.
|
||
6. **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:
|
||
|
||
```bash
|
||
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: 60–90 MiB (includes Flutter engine + Dart AOT +
|
||
Rust static lib + bundled libopus + iOS resources).
|
||
|
||
---
|
||
|
||
## 13. Download to your local host
|
||
|
||
If on a rental Mac:
|
||
|
||
```bash
|
||
# 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. |
|