From 1f729284b302fdfb7090bb61f5836bb8eacde7a8 Mon Sep 17 00:00:00 2001 From: EdisonJwa Date: Sat, 16 May 2026 15:57:36 +0900 Subject: [PATCH] feat(flutter,ios): vendor chanora_bridge as a CocoaPods framework iOS rejects loose .dylib loads (`dlopen` of any path outside the app bundle is sandboxed), so a Flutter-Rust bridge has to ship inside the .app as an Embed-and-Sign framework that flutter_rust_bridge's runtime loader can dlopen via its default `chanora_bridge.framework/chanora_bridge` lookup path. This commit wires the bridge into the iOS build via a CocoaPods podspec. Same role Cargokit plays for other Flutter+Rust setups, done by hand against this repo's layout to avoid the Cargokit vendoring footprint that was previously dropped. apps/chanora_flutter/ios/chanora_bridge.podspec New file. `prepare_command` invokes `cargo build --release --target aarch64-apple-ios -p chanora_bridge` with IPHONEOS_DEPLOYMENT_TARGET=13.0 + CMAKE_POLICY_VERSION_MINIMUM=3.5 (satisfies audiopus_sys's cmake invocation on modern CMake 4.x), then wraps the produced libchanora_bridge.dylib into chanora_bridge.framework with an Info.plist that declares iPhoneOS / MinimumOSVersion=13.0, and rewrites LC_ID_DYLIB to @rpath/chanora_bridge.framework/chanora_bridge. `vendored_frameworks` exposes the result to CocoaPods, which integrates it into Runner.xcodeproj with Embed & Sign automatically. No Xcode UI edits required. apps/chanora_flutter/ios/Podfile Add `pod 'chanora_bridge', :path => '.'` to the Runner target. apps/chanora_flutter/ios/Podfile.lock Generated by `pod install` after the pod was added. Pins chanora_bridge 1.0.0 + checksum so iOS builds on other developer machines pull the same framework version. .gitignore Add `/apps/chanora_flutter/ios/Frameworks/` so the ~13 MB built framework (regenerated on every pod install) is not committed. Verified end-to-end on the M1 Mac (coder@100.118.130.73): pod install ok chanora_bridge.framework generated at apps/chanora_flutter/ios/Frameworks/chanora_bridge.framework Install name: @rpath/chanora_bridge.framework/chanora_bridge flutter build ios --release --no-codesign 12.7 s -> Runner.app 29.9 MB (was 16.9 MB without the bridge) -> Runner.app/Frameworks/chanora_bridge.framework present alongside Flutter.framework, App.framework, connectivity_plus.framework, objective_c.framework. DEC-025: iOS / iPad officially in scope for P0. Notes for owners installing on physical iOS devices: The Personal Apple Team in Xcode requires a unique PRODUCT_BUNDLE_IDENTIFIER (the default 'app.chanora.chanoraFlutter' may already be claimed in the App Store registry). Set this locally in Xcode -> Runner target -> Signing & Capabilities -> Bundle Identifier (e.g. yourname.chanora.chanoraFlutter); do NOT commit that change back since it is user-specific. --- .gitignore | 3 + apps/chanora_flutter/ios/Podfile | 10 ++ apps/chanora_flutter/ios/Podfile.lock | 7 +- .../ios/chanora_bridge.podspec | 110 ++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 apps/chanora_flutter/ios/chanora_bridge.podspec diff --git a/.gitignore b/.gitignore index 4ff8de3..825858c 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,6 @@ chanora-docs-repo-format-*.zip # opencode agent local config opencode.json + +# iOS framework build artifacts produced by chanora_bridge.podspec +/apps/chanora_flutter/ios/Frameworks/ diff --git a/apps/chanora_flutter/ios/Podfile b/apps/chanora_flutter/ios/Podfile index 2dbf7d7..fe7a2ff 100644 --- a/apps/chanora_flutter/ios/Podfile +++ b/apps/chanora_flutter/ios/Podfile @@ -30,6 +30,16 @@ flutter_ios_podfile_setup target 'Runner' do use_frameworks! + # Chanora Rust bridge as a vendored framework. The podspec runs + # `cargo build --release --target aarch64-apple-ios` and wraps the + # produced dylib into chanora_bridge.framework. CocoaPods then + # integrates the framework into Runner.xcodeproj with the + # appropriate Embed & Sign build phase, so the Runner app ships + # with the bridge inside its Frameworks/ directory and + # flutter_rust_bridge can dlopen() it at runtime via FRB's + # default `chanora_bridge.framework/chanora_bridge` lookup path. + pod 'chanora_bridge', :path => '.' + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths diff --git a/apps/chanora_flutter/ios/Podfile.lock b/apps/chanora_flutter/ios/Podfile.lock index 59d9a1b..4c59049 100644 --- a/apps/chanora_flutter/ios/Podfile.lock +++ b/apps/chanora_flutter/ios/Podfile.lock @@ -1,22 +1,27 @@ PODS: + - chanora_bridge (1.0.0) - connectivity_plus (0.0.1): - Flutter - Flutter (1.0.0) DEPENDENCIES: + - chanora_bridge (from `.`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - Flutter (from `Flutter`) EXTERNAL SOURCES: + chanora_bridge: + :path: "." connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" Flutter: :path: Flutter SPEC CHECKSUMS: + chanora_bridge: 3346a0a77a8d5707cdcd9e5d9f490e281e1f428b connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 -PODFILE CHECKSUM: 251cb053df7158f337c0712f2ab29f4e0fa474ce +PODFILE CHECKSUM: 15f58b0363434f244766f3301e00b9b1cdee096a COCOAPODS: 1.16.2 diff --git a/apps/chanora_flutter/ios/chanora_bridge.podspec b/apps/chanora_flutter/ios/chanora_bridge.podspec new file mode 100644 index 0000000..b547c30 --- /dev/null +++ b/apps/chanora_flutter/ios/chanora_bridge.podspec @@ -0,0 +1,110 @@ +# +# 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, '13.0' + + # 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)" + BRIDGE="$REPO_ROOT/target/aarch64-apple-ios/release/libchanora_bridge.dylib" + + echo "[chanora_bridge.podspec] cargo build aarch64-apple-ios" + cd "$REPO_ROOT" + PATH="$HOME/.cargo/bin:$PATH" \\ + IPHONEOS_DEPLOYMENT_TARGET=13.0 \\ + CMAKE_POLICY_VERSION_MINIMUM=3.5 \\ + CMAKE_OSX_DEPLOYMENT_TARGET=13.0 \\ + cargo 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 + MinimumOSVersion13.0 + + +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' + + # 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