From 9d8a1f8fd1e337d34949ca439c6fb5ec5793d503 Mon Sep 17 00:00:00 2001 From: Edison Jwa Date: Sun, 7 Jun 2026 13:02:05 +0900 Subject: [PATCH] fix(ios,macos): address PR #26 review findings - ios/Runner.xcodeproj/project.pbxproj: Update RunnerTests TEST_HOST paths from Runner.app/Runner to Chanora.app/Chanora (target was renamed in prior commit but test config still pointed at old paths, breaking xcodebuild test). - Cargo.toml: Move release DWARF flags from workspace [profile.release] into Apple-only podspec CARGO_PROFILE_RELEASE_* env vars so Android, Linux, Windows release builds stay lean (~10MB DWARF avoided). - ios/Runner/Info.plist + macos/Runner/Info.plist: Flip ITSAppUsesNonExemptEncryption from false to true (Chanora ships ChaCha20-Poly1305 local storage + tsclientlib ECDH/AES-EAX voice channel encryption, not exempt under Apple export-compliance rules). - scripts/verify_silero_exports.sh: Make slice-aware via lipo -archs loop + per-arch nm -arch invocation so universal macOS builds verify every architecture slice, not just whichever slice nm picks. - .gitignore: Drop .omo/ and .playwright-mcp/ entries (scope leak; unrelated tooling state, not part of PR #26 archive-symbol concern). --- .gitignore | 3 - Cargo.toml | 32 ++++------- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- apps/chanora_flutter/ios/Runner/Info.plist | 2 +- .../ios/chanora_bridge.podspec | 6 ++ apps/chanora_flutter/macos/Runner/Info.plist | 2 +- .../macos/chanora_bridge.podspec | 12 ++++ .../scripts/verify_silero_exports.sh | 55 +++++++++++++------ 8 files changed, 73 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 0bb7142..bbfa71e 100644 --- a/.gitignore +++ b/.gitignore @@ -136,6 +136,3 @@ Screenshot 2026-05-17 at 22.23.07.png # macOS release zip bundles produced by local release scripts /chanora-v*.zip -# Local agent / tooling state -.omo/ -.playwright-mcp/ diff --git a/Cargo.toml b/Cargo.toml index e74e83a..bcae294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,26 +83,16 @@ tsproto-types = { git = "https://github.com/EdisonJwa/tsclientlib.git", branch = [patch.crates-io] cmake = { git = "https://github.com/pr2502/cmake-rs", rev = "bdad5edc569d82151922c5c6c4685b1563f12aa1" } -# Emit DWARF debug info in release builds. +# Apple-only DWARF emission for archive validation lives in the iOS and +# macOS chanora_bridge podspecs (apps/chanora_flutter/{ios,macos}/ +# chanora_bridge.podspec) as per-build environment overrides: # -# Apple's archive validator (and App Store Connect upload) require a -# matching `.dSYM` for every embedded framework, keyed by the framework -# binary's LC_UUID. The CocoaPods bridge podspecs copy the cargo-built -# `libchanora_bridge.dylib` into `chanora_bridge.framework/chanora_bridge` -# and then run `dsymutil` on it (see apps/chanora_flutter/ios and -# apps/chanora_flutter/macos podspecs). `dsymutil` needs DWARF in the -# input binary to produce a useful dSYM with symbol records; without -# `debug = true` here we'd ship a stripped binary and an empty/unusable -# dSYM and TestFlight crash reports would be unsymbolicated. +# CARGO_PROFILE_RELEASE_DEBUG=true +# CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off +# CARGO_PROFILE_RELEASE_STRIP=false # -# `split-debuginfo = "off"` keeps DWARF inside the dylib so install_name_tool -# rewriting and the dsymutil pass both work without chasing per-object -# `.o` files cargo would otherwise scatter under target//release/deps. -# -# `strip = false` ensures cargo does not pre-strip the dylib before -# dsymutil sees it. We strip the in-framework binary ourselves after the -# dSYM is produced (handled in the podspec script phases). -[profile.release] -debug = true -split-debuginfo = "off" -strip = false +# This keeps Android, Linux, and Windows release binaries on the cargo +# default release profile (no DWARF, no extra ~10MB symbol payload). +# Apple builds need the DWARF so dsymutil can emit a usable +# chanora_bridge.framework.dSYM that the archive validator accepts. + diff --git a/apps/chanora_flutter/ios/Runner.xcodeproj/project.pbxproj b/apps/chanora_flutter/ios/Runner.xcodeproj/project.pbxproj index 7191472..4312196 100644 --- a/apps/chanora_flutter/ios/Runner.xcodeproj/project.pbxproj +++ b/apps/chanora_flutter/ios/Runner.xcodeproj/project.pbxproj @@ -555,7 +555,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Chanora.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Chanora"; }; name = Debug; }; @@ -571,7 +571,7 @@ PRODUCT_BUNDLE_IDENTIFIER = app.chanora.chanoraFlutter.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Chanora.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Chanora"; }; name = Release; }; @@ -587,7 +587,7 @@ PRODUCT_BUNDLE_IDENTIFIER = app.chanora.chanoraFlutter.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Chanora.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Chanora"; }; name = Profile; }; diff --git a/apps/chanora_flutter/ios/Runner/Info.plist b/apps/chanora_flutter/ios/Runner/Info.plist index 057971e..b97e12f 100644 --- a/apps/chanora_flutter/ios/Runner/Info.plist +++ b/apps/chanora_flutter/ios/Runner/Info.plist @@ -25,7 +25,7 @@ CFBundleVersion $(FLUTTER_BUILD_NUMBER) ITSAppUsesNonExemptEncryption - + LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/apps/chanora_flutter/ios/chanora_bridge.podspec b/apps/chanora_flutter/ios/chanora_bridge.podspec index fe63f46..e1df156 100644 --- a/apps/chanora_flutter/ios/chanora_bridge.podspec +++ b/apps/chanora_flutter/ios/chanora_bridge.podspec @@ -102,6 +102,9 @@ Pod::Spec.new do |s| IPHONEOS_DEPLOYMENT_TARGET=16.0 \\ CMAKE_POLICY_VERSION_MINIMUM=3.5 \\ CMAKE_OSX_DEPLOYMENT_TARGET=16.0 \\ + CARGO_PROFILE_RELEASE_DEBUG=true \\ + CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \\ + CARGO_PROFILE_RELEASE_STRIP=false \\ "$CARGO_BIN" build --release --target aarch64-apple-ios -p chanora_bridge if [ ! -f "$BRIDGE" ]; then @@ -231,6 +234,9 @@ PLIST IPHONEOS_DEPLOYMENT_TARGET=16.0 \\ CMAKE_POLICY_VERSION_MINIMUM=3.5 \\ CMAKE_OSX_DEPLOYMENT_TARGET=16.0 \\ + CARGO_PROFILE_RELEASE_DEBUG=true \\ + CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \\ + CARGO_PROFILE_RELEASE_STRIP=false \\ "$CARGO_BIN" build --release --target "$RUST_TARGET" -p chanora_bridge cd "$REPO_ROOT/apps/chanora_flutter/ios" diff --git a/apps/chanora_flutter/macos/Runner/Info.plist b/apps/chanora_flutter/macos/Runner/Info.plist index 00d425c..3a96692 100644 --- a/apps/chanora_flutter/macos/Runner/Info.plist +++ b/apps/chanora_flutter/macos/Runner/Info.plist @@ -23,7 +23,7 @@ CFBundleVersion $(FLUTTER_BUILD_NUMBER) ITSAppUsesNonExemptEncryption - + LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright diff --git a/apps/chanora_flutter/macos/chanora_bridge.podspec b/apps/chanora_flutter/macos/chanora_bridge.podspec index 3443252..5fc1351 100644 --- a/apps/chanora_flutter/macos/chanora_bridge.podspec +++ b/apps/chanora_flutter/macos/chanora_bridge.podspec @@ -57,6 +57,9 @@ Pod::Spec.new do |s| CMAKE_POLICY_VERSION_MINIMUM=3.5 \\ LIBOPUS_STATIC=1 \\ LIBOPUS_NO_PKG=1 \\ + CARGO_PROFILE_RELEASE_DEBUG=true \\ + CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \\ + CARGO_PROFILE_RELEASE_STRIP=false \\ cargo build --release --target aarch64-apple-darwin -p chanora_bridge echo "[chanora_bridge.podspec] cargo build x86_64-apple-darwin" @@ -65,6 +68,9 @@ Pod::Spec.new do |s| CMAKE_POLICY_VERSION_MINIMUM=3.5 \\ LIBOPUS_STATIC=1 \\ LIBOPUS_NO_PKG=1 \\ + CARGO_PROFILE_RELEASE_DEBUG=true \\ + CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \\ + CARGO_PROFILE_RELEASE_STRIP=false \\ cargo build --release --target x86_64-apple-darwin -p chanora_bridge if [ ! -f "$BRIDGE_ARM64" ]; then @@ -158,6 +164,9 @@ PLIST CMAKE_POLICY_VERSION_MINIMUM=3.5 \ LIBOPUS_STATIC=1 \ LIBOPUS_NO_PKG=1 \ + CARGO_PROFILE_RELEASE_DEBUG=true \ + CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \ + CARGO_PROFILE_RELEASE_STRIP=false \ cargo build --release --target aarch64-apple-darwin -p chanora_bridge echo "[chanora_bridge script_phase] cargo build x86_64-apple-darwin" @@ -166,6 +175,9 @@ PLIST CMAKE_POLICY_VERSION_MINIMUM=3.5 \ LIBOPUS_STATIC=1 \ LIBOPUS_NO_PKG=1 \ + CARGO_PROFILE_RELEASE_DEBUG=true \ + CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \ + CARGO_PROFILE_RELEASE_STRIP=false \ cargo build --release --target x86_64-apple-darwin -p chanora_bridge cd "$REPO_ROOT/apps/chanora_flutter/macos" diff --git a/apps/chanora_flutter/scripts/verify_silero_exports.sh b/apps/chanora_flutter/scripts/verify_silero_exports.sh index f205536..a1ce2d5 100755 --- a/apps/chanora_flutter/scripts/verify_silero_exports.sh +++ b/apps/chanora_flutter/scripts/verify_silero_exports.sh @@ -2,20 +2,23 @@ # verify_silero_exports.sh # # Asserts that all six chanora_silero_vad_* C symbols that the Rust -# chanora_bridge framework resolves via dlsym(RTLD_DEFAULT) are -# present in the linked app binary's dynamic export table. +# chanora_bridge framework resolves via dlsym(RTLD_DEFAULT) are present +# in the linked app binary's dynamic export table — verified per +# architecture slice for macOS universal builds, because `nm` on a +# universal Mach-O without -arch will succeed if a symbol exists in ANY +# slice, not every slice. A missing symbol in just the x86_64 slice +# would silently break Intel Macs. # -# Without this check, Xcode Archive's -dead_strip can silently -# remove these Swift @_cdecl symbols because no Swift caller exists, -# and CoreML VAD falls back to WebRTC on TestFlight/App Store with -# no compile-time, link-time, or runtime warning. We hit that bug -# once; this script ensures we never ship it again. +# Without this check, Xcode Archive's -dead_strip can remove these +# Swift @_cdecl symbols (no Swift caller exists) and CoreML VAD falls +# back to WebRTC on TestFlight/App Store with no compile-time, +# link-time, or runtime warning. We hit that bug once; this script +# ensures we never ship it again. # # Runs as an Xcode build phase after Link Binary, on iOS and macOS. set -e -# Xcode passes these. Outside Xcode, the caller must export them. if [ -z "${TARGET_BUILD_DIR}" ] || [ -z "${EXECUTABLE_PATH}" ]; then echo "error: verify_silero_exports.sh requires TARGET_BUILD_DIR and EXECUTABLE_PATH (run from Xcode build phase)" >&2 exit 1 @@ -37,20 +40,40 @@ _chanora_silero_vad_last_error _chanora_silero_vad_free_string " -EXPORTED=$(xcrun nm -gU "${BINARY}" 2>/dev/null | awk '{print $NF}') +# Enumerate slices. `lipo -archs` prints arches space-separated for +# universal Mach-O; for thin binaries it prints the single arch. +ARCHS=$(xcrun lipo -archs "${BINARY}" 2>/dev/null || echo "") +if [ -z "${ARCHS}" ]; then + echo "error: xcrun lipo -archs failed for ${BINARY}; cannot enumerate slices" >&2 + exit 1 +fi -MISSING="" -for sym in ${REQUIRED_SYMBOLS}; do - if ! echo "${EXPORTED}" | grep -qx "${sym}"; then - MISSING="${MISSING} ${sym}" +FAILED=0 +for arch in ${ARCHS}; do + EXPORTED=$(xcrun nm -arch "${arch}" -gU "${BINARY}" 2>/dev/null | awk '{print $NF}') + if [ -z "${EXPORTED}" ]; then + echo "error: xcrun nm produced no output for arch=${arch} on ${BINARY}" >&2 + FAILED=1 + continue + fi + + MISSING="" + for sym in ${REQUIRED_SYMBOLS}; do + if ! echo "${EXPORTED}" | grep -qx "${sym}"; then + MISSING="${MISSING} ${sym}" + fi + done + + if [ -n "${MISSING}" ]; then + echo "error: chanora_silero_vad exports missing from $(basename "${BINARY}") [arch=${arch}]:${MISSING}" >&2 + FAILED=1 fi done -if [ -n "${MISSING}" ]; then - echo "error: chanora_silero_vad exports missing from $(basename "${BINARY}"):${MISSING}" >&2 +if [ "${FAILED}" -ne 0 ]; then echo "error: Rust dlsym(RTLD_DEFAULT) resolution will fail and CoreML VAD will fall back to WebRTC." >&2 echo "error: Check OTHER_LDFLAGS -exported_symbol entries in Flutter/*.xcconfig and the @_cdecl exports in Runner/SileroCoreMLBridge.swift." >&2 exit 1 fi -echo "verify_silero_exports: all 6 chanora_silero_vad_* symbols present in $(basename "${BINARY}")" +echo "verify_silero_exports: all 6 chanora_silero_vad_* symbols present in $(basename "${BINARY}") [arches: ${ARCHS}]"