fix(apple): merge vendored librnnoise.a into xcframework fat static lib

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/<preset>/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/<preset>/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.
This commit is contained in:
2026-06-23 14:25:30 +02:00
parent 2e0e0caccb
commit cd9c08a47a
3 changed files with 45 additions and 10 deletions

View File

@@ -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/<preset>/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/<preset>/lib/` and isn't named `libvoicecat*`.
### Swift Package

View File

@@ -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)"
}