fix(ios): rebuild chanora_bridge.framework on every Xcode build, not just pod install

The podspec's prepare_command only fires on `pod install`. Once a
framework was generated, Rust source changes were silently ignored
because Xcode kept re-bundling the stale framework into Runner.app.
Manifested today as: ran `cargo build --release --target
aarch64-apple-ios` to pick up the lenient voice_join fix, ran
`flutter build ios`, but Runner.app/Frameworks/chanora_bridge.
framework/chanora_bridge was still the framework from the previous
pod install (16:10) not the just-built 18:30 dylib.

Add an explicit `script_phase` to the podspec that re-runs:
  1. cargo build --release --target aarch64-apple-ios -p chanora_bridge
  2. cp dylib into Frameworks/chanora_bridge.framework/chanora_bridge
  3. install_name_tool -id @rpath/...

on every Xcode 'Build', not just on pod install. The script short-
circuits when the framework's binary mtime is newer than the cargo
output (fast no-op on incremental builds where Rust didn't change).

Side effect: every Xcode build now invokes cargo, which can take
~5 s on a warm cache and ~1 min cold. This is the right trade-off
because the previous behavior silently shipped stale Rust code.
This commit is contained in:
EdisonJwa
2026-05-16 17:31:43 +08:00
parent f1f81a3d7e
commit a93109ac37
@@ -100,6 +100,52 @@ PLIST
# 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)"
BRIDGE="$REPO_ROOT/target/aarch64-apple-ios/release/libchanora_bridge.dylib"
echo "[chanora_bridge script_phase] 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
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"
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'