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).
This commit is contained in:
Edison Jwa
2026-06-07 23:12:07 +09:00
parent a589ac953f
commit 9d8a1f8fd1
8 changed files with 73 additions and 45 deletions
-3
View File
@@ -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/
+11 -21
View File
@@ -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/<triple>/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.
@@ -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;
};
+1 -1
View File
@@ -25,7 +25,7 @@
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<true/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
@@ -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"
+1 -1
View File
@@ -23,7 +23,7 @@
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<true/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
@@ -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"
@@ -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}]"