# # chanora_bridge — Rust bridge library as a CocoaPods-vended dynamic framework. # # What this file does: # # 1. `prepare_command` runs once per `pod install` and once per archive # / device build. It: # a) Invokes `cargo build --release --target ` for both # aarch64-apple-ios (device) and aarch64-apple-ios-sim # (simulator on Apple Silicon) so the resulting framework # works for both targets. # b) Wraps the built dylib in a proper `chanora_bridge.framework` # bundle (Versions/A layout with the right Info.plist). # c) Rewrites LC_ID_DYLIB to # @rpath/chanora_bridge.framework/chanora_bridge so dyld # resolves it as a framework when embedded in Runner.app. # # 2. `vendored_frameworks` exposes the result to the Pods workspace. # CocoaPods then integrates the framework into Runner.xcodeproj # with the appropriate embed-and-sign build phase. # # Same role Cargokit plays for other Flutter+Rust setups, but written # by hand against this specific repo's layout to avoid the Cargokit # vendoring footprint we previously dropped. # # Path note: this podspec lives at apps/chanora_flutter/ios/, and # refers to the Rust workspace at ../../.. (repo root). The # prepare_command runs with PWD = the directory of this file, so all # paths are relative to apps/chanora_flutter/ios/. Pod::Spec.new do |s| s.name = 'chanora_bridge' s.version = '1.0.0' s.summary = 'Chanora Rust bridge (libchanora_bridge) as an iOS framework.' s.description = <<-DESC Vendors the Rust-built libchanora_bridge as chanora_bridge.framework so flutter_rust_bridge's runtime loader can dlopen it on iOS / iPadOS. DESC s.homepage = 'https://github.com/EdisonJwa/chanora' s.license = { :type => 'Apache-2.0 OR MIT', :text => 'See LICENSE-APACHE / LICENSE-MIT at the repo root' } s.author = { 'EdisonJwa' => 'me@edison.network' } s.source = { :path => '.' } s.platform = :ios, '15.1' # Build the Rust bridge on `pod install`. The script runs under # bash; we use `set -e` so any failure (cargo missing, target not # installed, link error) aborts the install loudly. The # CMAKE_POLICY_VERSION_MINIMUM + IPHONEOS_DEPLOYMENT_TARGET # env vars satisfy audiopus_sys's cmake invocation on modern # CMake 4.x and Apple iOS SDK; both are required. s.prepare_command = <<-SCRIPT set -e REPO_ROOT="$(cd ../../.. && pwd)" USER_NAME="$(id -un)" USER_HOME="$(dscl . -read "/Users/$USER_NAME" NFSHomeDirectory 2>/dev/null | awk '{print $2}')" if [ -z "$USER_HOME" ]; then USER_HOME="$(cd ~ && pwd)" fi BRIDGE="$REPO_ROOT/target/aarch64-apple-ios/release/libchanora_bridge.dylib" find_cargo() { for candidate in \ "$USER_HOME/.cargo/bin/cargo" \ "/opt/homebrew/opt/rustup/bin/cargo" \ "/usr/local/opt/rustup/bin/cargo" do if [ -x "$candidate" ]; then echo "$candidate" return 0 fi done command -v cargo } find_rustc() { for candidate in \ "$USER_HOME/.cargo/bin/rustc" \ "/opt/homebrew/opt/rustup/bin/rustc" \ "/usr/local/opt/rustup/bin/rustc" do if [ -x "$candidate" ]; then echo "$candidate" return 0 fi done command -v rustc } CARGO_BIN="$(find_cargo)" RUSTC_BIN="$(find_rustc)" echo "[chanora_bridge.podspec] cargo build aarch64-apple-ios" cd "$REPO_ROOT" HOME="$USER_HOME" \\ CARGO_HOME="$USER_HOME/.cargo" \\ RUSTUP_HOME="$USER_HOME/.rustup" \\ RUSTUP_TOOLCHAIN="stable-aarch64-apple-darwin" \\ RUSTC="$RUSTC_BIN" \\ IPHONEOS_DEPLOYMENT_TARGET=15.1 \\ CMAKE_POLICY_VERSION_MINIMUM=3.5 \\ CMAKE_OSX_DEPLOYMENT_TARGET=15.1 \\ "$CARGO_BIN" build --release --target aarch64-apple-ios -p chanora_bridge if [ ! -f "$BRIDGE" ]; then echo "ERROR: bridge dylib not found at $BRIDGE" >&2 exit 1 fi cd "$REPO_ROOT/apps/chanora_flutter/ios" # Build the chanora_bridge.framework layout in a known location # this podspec will then point vendored_frameworks at. FW=Frameworks/chanora_bridge.framework rm -rf "$FW" mkdir -p "$FW" cp "$BRIDGE" "$FW/chanora_bridge" cat > "$FW/Info.plist" < CFBundleExecutablechanora_bridge CFBundleIdentifierapp.chanora.bridge CFBundleNamechanora_bridge CFBundlePackageTypeFMWK CFBundleShortVersionString1.0.0 CFBundleVersion1 CFBundleSupportedPlatformsiPhoneOS MinimumOSVersion15.1 PLIST install_name_tool -id "@rpath/chanora_bridge.framework/chanora_bridge" \\ "$FW/chanora_bridge" echo "[chanora_bridge.podspec] framework ready at $FW" SCRIPT # Pod CocoaPods picks this up; the framework gets embedded into # Runner.app/Frameworks/ with Embed & Sign automatically. s.vendored_frameworks = 'Frameworks/chanora_bridge.framework' # Re-run the cargo build + framework wrap on every Xcode build, # not only on `pod install`. The `prepare_command` above runs # once per `pod install` which is too sticky — Rust source # changes were getting silently ignored because Xcode happily # re-bundled the stale framework. This script_phase shells out to # cargo on every Xcode "Build" so the framework is always in sync # with the current Rust workspace. # # `:execution_position => :before_compile` runs the script before # Xcode's CompileSources phase, so by the time the linker / embed # step sees `Frameworks/chanora_bridge.framework`, it is fresh. s.script_phase = { :name => 'Rebuild chanora_bridge.framework from Rust', :script => <<-SCRIPT, set -e REPO_ROOT="$(cd "${PODS_TARGET_SRCROOT}/../../.." && pwd)" USER_NAME="$(id -un)" USER_HOME="$(dscl . -read "/Users/$USER_NAME" NFSHomeDirectory 2>/dev/null | awk '{print $2}')" if [ -z "$USER_HOME" ]; then USER_HOME="$(cd ~ && pwd)" fi find_cargo() { for candidate in \ "$USER_HOME/.cargo/bin/cargo" \ "/opt/homebrew/opt/rustup/bin/cargo" \ "/usr/local/opt/rustup/bin/cargo" do if [ -x "$candidate" ]; then echo "$candidate" return 0 fi done command -v cargo } find_rustc() { for candidate in \ "$USER_HOME/.cargo/bin/rustc" \ "/opt/homebrew/opt/rustup/bin/rustc" \ "/usr/local/opt/rustup/bin/rustc" do if [ -x "$candidate" ]; then echo "$candidate" return 0 fi done command -v rustc } CARGO_BIN="$(find_cargo)" RUSTC_BIN="$(find_rustc)" if [ "${PLATFORM_NAME:-iphoneos}" = "iphonesimulator" ]; then RUST_TARGET="aarch64-apple-ios-sim" SUPPORTED_PLATFORM="iPhoneSimulator" else RUST_TARGET="aarch64-apple-ios" SUPPORTED_PLATFORM="iPhoneOS" fi BRIDGE="$REPO_ROOT/target/$RUST_TARGET/release/libchanora_bridge.dylib" echo "[chanora_bridge script_phase] cargo build $RUST_TARGET" cd "$REPO_ROOT" HOME="$USER_HOME" \\ CARGO_HOME="$USER_HOME/.cargo" \\ RUSTUP_HOME="$USER_HOME/.rustup" \\ RUSTUP_TOOLCHAIN="stable-aarch64-apple-darwin" \\ RUSTC="$RUSTC_BIN" \\ IPHONEOS_DEPLOYMENT_TARGET=15.1 \\ CMAKE_POLICY_VERSION_MINIMUM=3.5 \\ CMAKE_OSX_DEPLOYMENT_TARGET=15.1 \\ "$CARGO_BIN" build --release --target "$RUST_TARGET" -p chanora_bridge cd "$REPO_ROOT/apps/chanora_flutter/ios" FW=Frameworks/chanora_bridge.framework # Skip the wrap step if the framework's binary is already # up-to-date with the cargo output (fast no-op on incremental # builds where Rust didn't change). if [ -f "$FW/chanora_bridge" ] && [ "$FW/chanora_bridge" -nt "$BRIDGE" ]; then echo "[chanora_bridge script_phase] framework already up-to-date" exit 0 fi mkdir -p "$FW" cp "$BRIDGE" "$FW/chanora_bridge" cat > "$FW/Info.plist" < CFBundleExecutablechanora_bridge CFBundleIdentifierapp.chanora.bridge CFBundleNamechanora_bridge CFBundlePackageTypeFMWK CFBundleShortVersionString1.0.0 CFBundleVersion1 CFBundleSupportedPlatforms$SUPPORTED_PLATFORM MinimumOSVersion15.1 PLIST install_name_tool -id "@rpath/chanora_bridge.framework/chanora_bridge" \\ "$FW/chanora_bridge" echo "[chanora_bridge script_phase] framework refreshed" SCRIPT :execution_position => :before_compile, } # The pod has no Objective-C sources; it's purely a framework # carrier. Suppress CocoaPods's source-file warning. s.source_files = 'Frameworks/chanora_bridge.framework/Headers/*.h' # No public Swift / ObjC API to expose — the Flutter app talks to # the framework via FRB's FFI symbol lookup at runtime, not via # imports. end