From cd9c08a47afcfa4d434943f8a0fc6754dd7a2fde Mon Sep 17 00:00:00 2001 From: Talon Date: Tue, 23 Jun 2026 14:25:30 +0200 Subject: [PATCH] fix(apple): merge vendored librnnoise.a into xcframework fat static lib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both VoiceCatMac and VoiceCatiOS failed to link with 'Undefined symbols for architecture arm64: _rnnoise_create/_rnnoise_destroy/_rnnoise_process_frame'. Root cause: build-xcframework.sh merged vcpkg deps into the fat static lib but NOT the locally-built vendored librnnoise.a (a CMake target from third_party/rnnoise/ linked privately into voicecat via VOICECAT_HAS_NS — not a vcpkg dep). The xcframework had been rebuilt after the RNNoise commit but still omitted the symbols, so every slice's libvoicecat-fat.a referenced _rnnoise_* with no defining object. The iOS slices were also stale (pre-rnnoise) and absent from the xcframework entirely. Fix: build-xcframework.sh now also collects .a files from build//lib/ (excluding libvoicecat*) so vendored CMake-target static libs like librnnoise.a get merged in. Future-proof: any new vendored static-lib target landing in build//lib/ is picked up automatically. README 'Fat static library' section updated. Verify: rebuilt VoiceCatCore.xcframework --all → all 3 slices (macos-arm64, ios-arm64, ios-arm64-simulator) now carry the 10 _rnnoise_* symbols; fat lib ~30 MB → ~33 MB. xcodebuild Debug BUILD SUCCEEDED for VoiceCatMac, VoiceCatiOS (iphonesimulator arm64), and VoiceCatiOS (iphoneos arm64). No core/ABI/proto changes — xcframework artifact + build script only. --- PROGRESS.md | 21 +++++++++++++++++++++ clients/apple/README.md | 14 +++++++++----- clients/apple/scripts/build-xcframework.sh | 20 +++++++++++++++----- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index 7d15438..dc91dd9 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -10,6 +10,27 @@ up instantly. Newest status at the top. ## ▶ Where we left off / next action +- **Done (2026-06-23):** **Fixed Apple client link failure (stale xcframework missing + RNNoise).** Both `VoiceCatMac` and `VoiceCatiOS` failed to link with `Undefined symbols for + architecture arm64: _rnnoise_create / _rnnoise_destroy / _rnnoise_process_frame`. Root + cause: `clients/apple/scripts/build-xcframework.sh` merged vcpkg deps into the fat static + lib but NOT the locally-built vendored `librnnoise.a` (a CMake target from + `third_party/rnnoise/`, linked privately into `voicecat` via `VOICECAT_HAS_NS` — not a + vcpkg dep). The xcframework had been rebuilt at 14:17 after the RNNoise commit but still + omitted the symbols, so every slice's `libvoicecat-fat.a` referenced `_rnnoise_*` with no + defining object. The iOS slices were also stale (pre-rnnoise) and absent from the + xcframework entirely. + - **Fix:** `build-xcframework.sh` now collects `.a` files from `build//lib/` + (excluding `libvoicecat*`) in addition to `vcpkg_installed//lib/`, so vendored + CMake-target static libs like `librnnoise.a` are merged into the fat lib. Future-proof: + any new vendored static-lib target landing in `build//lib/` is picked up + automatically. README "Fat static library" section updated. + - **Verify:** rebuilt `VoiceCatCore.xcframework --all` → all 3 slices (macos-arm64, + ios-arm64, ios-arm64-simulator) now carry 10 `_rnnoise_*` symbols each; fat lib + ~30 MB → ~33 MB. `xcodebuild` Debug **BUILD SUCCEEDED** for `VoiceCatMac`, + `VoiceCatiOS` (iphonesimulator arm64), and `VoiceCatiOS` (iphoneos arm64, + `CODE_SIGNING_ALLOWED=NO`). No core/ABI/proto changes — xcframework artifact only. + - **Done (2026-06-23):** **Remote-stream noise suppression — real backend (RNNoise).** The two-sided NR plumbing (`RemoteStream::recv_ns` + `vc_set_remote_stream(... noise_reduction)`) was wired but **inert** — `ApmProcessor::create()` returned a no-op passthrough, because the diff --git a/clients/apple/README.md b/clients/apple/README.md index ba446ae..c019651 100644 --- a/clients/apple/README.md +++ b/clients/apple/README.md @@ -106,12 +106,16 @@ scripts/build-xcframework.sh --all The `apple-dev` CMake preset produces a 1.9 MB `libvoicecat.a` containing only voicecat's own object files — vcpkg's static dependencies (protobuf, mbedtls, libsodium, opus, sqlite3, -spdlog, asio, abseil, …) are 107 separate `.a` files under `vcpkg_installed/arm64-osx/lib/`. -A Swift Package binary target can only link ONE `.a` per XCFramework slice, so -`build-xcframework.sh` merges them all into a single self-contained `libvoicecat-fat.a` -(~30 MB) using `libtool -static`. This is the Apple equivalent of how the Windows client +spdlog, asio, abseil, …) are 107 separate `.a` files under `vcpkg_installed/arm64-osx/lib/`, +and the vendored RNNoise noise-suppression lib (`third_party/rnnoise/`, built as a CMake +target → `build//lib/librnnoise.a`) is another. A Swift Package binary target can +only link ONE `.a` per XCFramework slice, so `build-xcframework.sh` merges them all — vcpkg +deps plus the locally-built vendored libs — into a single self-contained `libvoicecat-fat.a` +(~33 MB) using `libtool -static`. This is the Apple equivalent of how the Windows client ships a single `voicecat.dll` with all deps statically linked (via MinGW's `-static` flags -in [`core/CMakeLists.txt`](../../core/CMakeLists.txt)). +in [`core/CMakeLists.txt`](../../core/CMakeLists.txt)). If you add another vendored (non-vcpkg) +static-lib target to the core, it's picked up automatically as long as it lands in +`build//lib/` and isn't named `libvoicecat*`. ### Swift Package diff --git a/clients/apple/scripts/build-xcframework.sh b/clients/apple/scripts/build-xcframework.sh index 93eda46..222a16d 100755 --- a/clients/apple/scripts/build-xcframework.sh +++ b/clients/apple/scripts/build-xcframework.sh @@ -104,14 +104,24 @@ build_slice() { # to build protoc/etc at configure time. Merging those macOS .a files into the iOS # fat library triggers xcodebuild's "binaries with multiple platforms" rejection. local vcpkg_target_lib_dir="$REPO_ROOT/build/$preset/vcpkg_installed/$vcpkg_triplet/lib" + local local_lib_dir="$REPO_ROOT/build/$preset/lib" local fat_lib="$REPO_ROOT/build/$preset/lib/libvoicecat-fat.a" - echo "[build-xcframework] $slice_name: merging vcpkg deps into fat static lib (triplet: $vcpkg_triplet)" - # Collect all .a files (libvoicecat.a + every vcpkg target .a). libtool -static concatenates - # object files from all input archives; duplicate-object warnings are benign (the linker - # resolves duplicates at final link time). "no symbols" warnings are for empty AVX2/AVX512 - # objects on arm64 — also benign. + echo "[build-xcframework] $slice_name: merging vcpkg + vendored deps into fat static lib (triplet: $vcpkg_triplet)" + # Collect all .a files (libvoicecat.a + every vcpkg target .a + locally-built vendored static + # libs). libtool -static concatenates object files from all input archives; duplicate-object + # warnings are benign (the linker resolves duplicates at final link time). "no symbols" + # warnings are for empty AVX2/AVX512 objects on arm64 — also benign. + # + # Two source dirs: + # - vcpkg_target_lib_dir: vcpkg-installed deps (protobuf, mbedtls, sodium, opus, …). + # - local_lib_dir: CMake static-lib targets built in this tree that are NOT vcpkg deps — + # e.g. the vendored RNNoise lib (third_party/rnnoise, VOICECAT_HAS_NS). Without it the + # fat lib references _rnnoise_* symbols that nothing defines → undefined-symbol link + # errors in Xcode. Exclude libvoicecat* (the main lib is $lib; libvoicecat-fat.a is the + # output we're building here). local all_libs=( "$lib" ) while IFS= read -r f; do all_libs+=( "$f" ); done < <(find "$vcpkg_target_lib_dir" -name '*.a' -not -name 'libvoicecat*' | sort) + while IFS= read -r f; do all_libs+=( "$f" ); done < <(find "$local_lib_dir" -maxdepth 1 -name '*.a' -not -name 'libvoicecat*' | sort) libtool -static -o "$fat_lib" "${all_libs[@]}" 2>&1 | grep -v 'has no symbols' || true echo "[build-xcframework] $slice_name -> $fat_lib ($(stat -f%z "$fat_lib") bytes, fat)" }