Files
voice-cat/docs/building.md
Talon 2c8178fa02
Some checks failed
Build Linux Binaries / linux/amd64 (push) Has been cancelled
Build Linux Binaries / linux/arm64 (push) Has been cancelled
chore: remove skeleton build mode and stub #ifdef scaffolding
Drop the M0 no-deps skeleton preset and all VOICECAT_HAS_NET/AUDIO/OPUS/NS
guards that it required. Every subsystem is fully implemented; the stub
#else paths were dead code that added noise to every header and source file.

- CMakePresets.json: remove skeleton configure/build/test entries
- CMakeLists.txt (root/core/tests): remove VOICECAT_USE_VCPKG_DEPS option
  and guards; all targets now build unconditionally
- 17 C++ source files: unwrap HAS_* guards, delete stub #else blocks
- apm_processor.cpp: delete ApmPassthrough no-op class; create() always
  returns RnnoiseProcessor
- 18 test files: remove HAS_* guards and stub int main() skip bodies
- docs/building.md: remove skeleton from preset table and prose

VOICECAT_HAS_LOOPBACK (Windows WASAPI loopback platform gate) unchanged.
29/29 ctest green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 11:32:22 +01:00

19 KiB

Building & Manual Testing

This doc explains what each CMake preset in CMakePresets.json is for, which one to actually use day-to-day, and the commands to stand up a real server + vccli clients against each other for manual testing. For the one-paragraph quick-start see CLAUDE.md; for ctest targets see AGENTS.md. This doc is the missing middle: how the presets relate to each other, which platform each targets, and how to drive the binaries by hand.

Quick navigation:

What you want to build Section Key command
Server + vccli + tests (all platforms) §3 cmake --preset dev && cmake --build --preset dev && ctest --preset dev
Production server (stripped, no tests) §5 cmake --preset server-release && cmake --build --preset server-release
Windows client (C# / WinForms) §7 dotnet build clients/windows/VoiceCat.slnx
macOS client (AppKit) §8 scripts/build-macos-client.sh
iOS client (SwiftUI / simulator) §9 scripts/build-ios-client.sh
Launch iOS app on simulator §9 scripts/run-ios-simulator.sh
Swift core + tests §8 cd clients/apple && swift test

1. What each preset is for

Preset Binary dir Deps Build type Server Tools Tests Strip Platform What it's for
vcpkg-common vcpkg all Hidden base. Sets the vcpkg toolchain wrapper (cmake/voicecat-toolchain.cmake) which auto-resolves the triplet from the host platform. Not used directly.
dev build/dev vcpkg Debug ON ON ON no all The one you actually want. Day-to-day development: real protocol, crypto, voice, server — everything. Builds server + tools + tests (29 tests). Works on Windows, Linux, and macOS (triplet auto-resolved).
release build/release vcpkg Release ON ON ON no all Optimized build with the full test suite. Use to run tests against optimized code, profile, or catch optimizer-sensitive bugs. Symbols kept (not stripped) so stack traces and profiling remain useful.
server-release build/server-release vcpkg Release ON ON OFF yes all Production-shaped build for deployment. Optimized + stripped binaries (-s), no tests. This is what you'd ship/run — see docs/deployment.md.
windows-client build/windows-client vcpkg Release OFF OFF OFF no Windows Produces a redistributable voicecat.dll for the C# WinForms client (M4). Static MinGW runtime — no libgcc_s_seh-1.dll etc. See clients/windows/README.md.
apple-dev build/apple-dev vcpkg Release OFF OFF OFF no macOS Static libvoicecat.a for the Swift Package / XCFramework (macOS slice). Validated on macOS 26.5 / Apple Silicon — builds green, produces valid arm64 .a + XCFramework. See clients/apple/README.md.
apple-ios build/apple-ios vcpkg Release OFF OFF OFF no macOS→iOS Scaffolding — cross-compiled static libvoicecat.a for iOS device (arm64-ios). One XCFramework slice. Not yet CI-validated.
apple-ios-sim build/apple-ios-sim vcpkg Release OFF OFF OFF no macOS→iOS sim Scaffolding — cross-compiled static libvoicecat.a for iOS simulator (arm64-ios-sim). One XCFramework slice. Not yet CI-validated.

So in practice there are two presets that matter for day-to-day work:

  • dev — everything: real protocol, real voice, real manual testing. This is the loop you run constantly.
  • release — same suite, optimized. Run it when you want to check optimized behavior or profile.

The rest are purpose-specific: server-release for deployment, windows-client for the DLL, apple-* for Apple platform slices.

Platform matrix

The vcpkg presets (dev, release, server-release, windows-client, apple-*) auto-resolve the vcpkg triplet via cmake/voicecat-toolchain.cmake:

Host platform Auto-resolved triplet Notes
Windows (MinGW/MSYS2) x64-mingw-static The project's Windows toolchain. MSVC users must set VCPKG_TARGET_TRIPLET=x64-windows explicitly.
Linux x64 x64-linux Server's primary deployment target (Docker, systemd).
Linux arm64 arm64-linux Raspberry Pi / ARM VPS.
macOS (Apple Silicon) arm64-osx apple-dev uses this automatically.
macOS (Intel) x64-osx apple-dev uses this automatically.

Cross-compile presets (apple-ios, apple-ios-sim) override VCPKG_TARGET_TRIPLET explicitly; VCPKG_HOST_TRIPLET stays the host's (e.g. arm64-osx when building iOS on Apple Silicon).

Preset history

The preset set was cleaned up on 2026-06-18 (see PROGRESS.md). The old names map as follows:

Old name New name Notes
m1-dev dev Renamed — the project is past M5, so milestone-named presets were misleading. This is now the default development preset.
m2-dev (dropped) Was cache-identical to m1-dev (same flags, same triplet, only the binary dir differed). Removed.
skeleton (dropped) Removed — was a no-deps stub build mode used during M0. All subsystems are now fully implemented; the stub #ifdef scaffolding has been deleted.
server-release server-release Unchanged name; now stripped (-s) and auto-triplet.
(new) release New: optimized build with tests on, symbols kept.
windows-client windows-client Unchanged name; triplet now auto-resolved.
(new) apple-dev, apple-ios, apple-ios-sim New: Apple platform scaffolding.

If you see m1-dev or m2-dev in old scripts, commits, or PROGRESS.md history entries, use dev instead. Historical PROGRESS.md entries are left intact as a true record of what was run.

2. One-time setup for the real-deps presets

All presets except vcpkg-common need VCPKG_ROOT pointing at a bootstrapped vcpkg checkout:

# once:
git clone https://github.com/microsoft/vcpkg
./vcpkg/bootstrap-vcpkg.sh        # .bat on Windows

# every shell session (PowerShell):
$env:VCPKG_ROOT = "D:\path\to\vcpkg"

# or on Linux/macOS:
export VCPKG_ROOT=/path/to/vcpkg

vcpkg.json (manifest mode) pins every dependency (protobuf, mbedTLS, libsodium, asio, sqlite3, spdlog, opus, miniaudio) — cmake --preset dev resolves and builds them automatically on first configure. That first configure is slow (vcpkg building from source); subsequent ones are cached.

3. Build + test (the loop you'll run constantly)

cmake --preset dev
cmake --build --preset dev
ctest --preset dev

Binaries land in build/dev/bin/ (.exe suffix on Windows):

  • build/dev/bin/voicecat-server
  • build/dev/bin/vccli
  • build/dev/bin/voicecat-admin

To run the same suite against optimized code:

cmake --preset release
cmake --build --preset release
ctest --preset release

4. Manual testing: server + two clients

Start the server

./build/dev/bin/voicecat-server --name "Test Server" --data-dir ./voicecat-data

First run generates an Ed25519 identity + self-signed cert under --data-dir, creates the SQLite store, and creates a default "Lobby" channel. Other server flags:

--port <n>          control+media port (default 8384)
--data-dir <path>   data directory (default ./voicecat-data)
--name <name>       server name
--no-guests         disable guest access (then provision accounts via voicecat-admin)
--print-config      print effective config and exit
--version           print version and exit

Drive it with vccli

vccli is the headless client used to exercise the protocol by hand. Full flag list:

vccli [--host H] [--port P] [--nick NAME] [--channel ID]
      [--voice] [--mute] [--text MSG] [--list-devices]
      [--input-device ID] [--input-mode vad|ptt] [--share-screen-audio]

  --host H              server host (default 127.0.0.1)
  --port P              server TCP port (default 8384)
  --nick NAME           guest nickname (default vccli-test)
  --channel ID          channel to join after auth (default 1, Lobby)
  --voice               start a MIC stream and stay connected until Ctrl+C
  --mute                start with the mic muted (only meaningful with --voice)
  --text MSG            send MSG to the channel, then exit
  --list-devices        print input/output devices (vc_list_devices) and exit
  --input-device ID     use device ID (from --list-devices) for the MIC stream
  --input-mode vad|ptt  send-side input gate mode (default vad)
  --share-screen-audio  also start a SCREEN_AUDIO stream (WASAPI loopback on Windows)

While --voice is running, stdin accepts ptt on, ptt off, mode vad, mode ptt to toggle the input gate live.

Smoke test — two clients talking:

# terminal A
./build/dev/bin/vccli --nick Alice --text "hello from Alice"

# terminal B (separate window, after A confirms it sent)
./build/dev/bin/vccli --nick Bob --text "hello from Bob"

Real voice between two clients (needs working mic/speakers, two terminals):

# terminal A
./build/dev/bin/vccli --nick Alice --voice

# terminal B
./build/dev/bin/vccli --nick Bob --voice

Speak into the mic on one side; you should hear it on the other. Ctrl+C to disconnect.

Enumerate audio devices before picking one:

./build/dev/bin/vccli --list-devices
./build/dev/bin/vccli --nick Alice --voice --input-device <ID> --input-mode ptt

Provisioning a non-guest account (if the server was started with --no-guests):

./build/dev/bin/voicecat-admin --data-dir ./voicecat-data account add alice --password secret
./build/dev/bin/voicecat-admin --data-dir ./voicecat-data account list

5. server-release (production-shaped build)

Same dependency story, but Release build type, stripped binaries, and no tests — this is the closest local analogue to what docs/deployment.md's "from source" path produces:

cmake --preset server-release
cmake --build --preset server-release
./build/server-release/bin/voicecat-server

Use this to sanity-check release-mode behavior (e.g. perf, optimized codepaths) — not for day-to-day development, since it has no test target wired up. The -s linker flag strips symbol tables from the binaries, producing smaller executables suitable for distribution.

6. Apple platform builds

The apple-dev, apple-ios, and apple-ios-sim presets produce static libvoicecat.a slices for the Swift Package / XCFramework. All three are validated on macOS 26.5 / Apple Silicon. Build on macOS:

# macOS slice (arm64-osx on Apple Silicon, x64-osx on Intel)
cmake --preset apple-dev
cmake --build --preset apple-dev
# → build/apple-dev/lib/libvoicecat.a

# iOS device slice (arm64-ios)
cmake --preset apple-ios
cmake --build --preset apple-ios
# → build/apple-ios/lib/libvoicecat.a

# iOS simulator slice (arm64-ios-simulator)
cmake --preset apple-ios-sim
cmake --build --preset apple-ios-sim
# → build/apple-ios-sim/lib/libvoicecat.a

You rarely need to run these CMake commands directly. The clients/apple/scripts/build-xcframework.sh script drives them internally and stitches the result into a self-contained VoiceCatCore.xcframework:

# macOS slice only (default — used by macOS AppKit client)
clients/apple/scripts/build-xcframework.sh

# All 3 slices (macOS + iOS device + iOS sim — required for iOS client)
clients/apple/scripts/build-xcframework.sh --all

See clients/apple/README.md for the XCFramework internals (fat static lib merge, module map staging).

7. Windows client (C# / WinForms)

The Windows client is a .NET 10 WinForms app that loads voicecat.dll (the MinGW-built shared library from the windows-client preset) via P/Invoke. Full details in clients/windows/README.md.

Prerequisites: .NET SDK 10, MinGW-w64 / MSYS2 UCRT64 (GCC 13+), vcpkg.

Build the DLL

cmake --preset windows-client
cmake --build --preset windows-client
# → build/windows-client/bin/voicecat.dll

Build the C# solution

cd clients/windows
dotnet build VoiceCat.slnx

Directory.Build.props copies voicecat.dll into the output directory automatically.

Run the app

# Terminal 1 — start the server (built with the dev preset)
./build/dev/bin/voicecat-server.exe --name "My Server"

# Terminal 2 — launch the client
dotnet run --project clients/windows/VoiceCat.App/VoiceCat.App.csproj

Run the C# interop tests

dotnet test clients/windows/VoiceCat.slnx

8. macOS client (AppKit)

The macOS client is an Xcode project (AppKit / Swift) that links libvoicecat via the VoiceCatCore Swift Package, which consumes a binary XCFramework target. Full details in clients/apple/README.md.

Prerequisites: Xcode, vcpkg (VCPKG_ROOT set), macOS 14+ (deployment target).

Build the XCFramework

The XCFramework is a local build artifact (gitignored, like the Windows DLL). It bundles libvoicecat.a + all vcpkg static deps into a single fat .a per slice, plus staged headers with a module map so Swift gets import VoiceCatC.

# macOS slice only (default, validated)
clients/apple/scripts/build-xcframework.sh
# → clients/apple/VoiceCatCore.xcframework/

# All 3 slices (macOS + iOS device + iOS sim — iOS still scaffolding)
clients/apple/scripts/build-xcframework.sh --all

The script runs cmake --preset apple-dev + cmake --build --preset apple-dev internally, then merges vcpkg's static deps with libtool -static and stitches the XCFramework with xcodebuild -create-xcframework.

Build the Swift core (SPM)

cd clients/apple
swift build        # builds VoiceCatCore library
swift test         # 6 smoke tests against a real voicecat-server

swift test requires the dev CMake preset to be built (build/dev/bin/voicecat-server + voicecat-admin).

Build the macOS app (Xcode)

xcodebuild -project clients/apple/macOS/VoiceCatMac.xcodeproj \
    -scheme VoiceCatMac -configuration Debug build
# → ~/Library/Developer/Xcode/DerivedData/VoiceCatMac-*/Build/Products/Debug/VoiceCatMac.app

Or open the project in Xcode and build from the UI:

open clients/apple/macOS/VoiceCatMac.xcodeproj

Run the app

# Terminal 1 — start the server (built with the dev preset)
./build/dev/bin/voicecat-server --name "My Server"

# Terminal 2 — launch the client
open ~/Library/Developer/Xcode/DerivedData/VoiceCatMac-*/Build/Products/Debug/VoiceCatMac.app

Or use the per-artifact script which builds and stages to dist/macos-client/:

scripts/build-macos-client.sh

9. iOS client (SwiftUI)

The iOS client is an Xcode project (clients/apple/iOS/VoiceCatiOS.xcodeproj) that links libvoicecat via the same VoiceCatCore Swift Package as the macOS client. Full details in clients/apple/README.md.

Prerequisites: Xcode, vcpkg (VCPKG_ROOT set), iOS Simulator runtime installed (Xcode > Settings > Platforms > iOS). iOS deployment target: 18.0.

Build the XCFramework (all slices)

The iOS build requires the ios-arm64-simulator slice in the XCFramework — not just the macOS slice. Use the --all flag to produce all three slices:

clients/apple/scripts/build-xcframework.sh --all
# → clients/apple/VoiceCatCore.xcframework/
#   ├── macos-arm64/
#   ├── ios-arm64/
#   └── ios-arm64-simulator/

Build the iOS simulator app

# Via the convenience script (recommended):
scripts/build-ios-client.sh
# → clients/apple/iOS/build/Debug-iphonesimulator/VoiceCatiOS.app
# → dist/ios-client/VoiceCatiOS.app

# Or as a single command (what the script does under the hood):
SIM_SDK="iphonesimulator$(xcrun --sdk iphonesimulator --show-sdk-version)"
BUILD_DIR="$(pwd)/clients/apple/iOS/build"
xcodebuild \
    -project clients/apple/iOS/VoiceCatiOS.xcodeproj \
    -target VoiceCatiOS \
    -sdk "$SIM_SDK" \
    -configuration Debug \
    CODE_SIGNING_ALLOWED=NO \
    ARCHS=arm64 \
    ONLY_ACTIVE_ARCH=YES \
    SYMROOT="$BUILD_DIR" \
    OBJROOT="$BUILD_DIR" \
    build

Why -target instead of -scheme -destination? Using -scheme VoiceCatiOS -destination 'platform=iOS Simulator,OS=latest' requires a simulator runtime whose iOS version exactly matches the SDK version (iphonesimulatorX.Y). If you have an older runtime installed (common when the SDK ships ahead of runtime availability in Xcode), the build fails with "Unable to find a destination matching the provided destination specifier." Using -target bypasses destination matching and builds against the SDK directly.

Why SYMROOT=OBJROOT=clients/apple/iOS/build? When building with -target (not -scheme), the local Swift Package (VoiceCatCore) resolves its build products relative to OBJROOT. By default, SPM resolves into clients/apple/build/, while the app target looks in clients/apple/iOS/build/. Pointing both to the same directory fixes the "unable to resolve module dependency: 'VoiceCatCore'" error.

Run on the iOS Simulator

# Via the script (finds or boots an iPhone simulator, installs, launches):
scripts/run-ios-simulator.sh

# Build and run in one step:
scripts/run-ios-simulator.sh --build

# Stream app logs after launch:
scripts/run-ios-simulator.sh --log

# Target a specific device by name or UDID:
scripts/run-ios-simulator.sh --device "iPhone 16 Pro"
scripts/run-ios-simulator.sh --udid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

Under the hood the script uses xcrun simctl commands:

# Boot a simulator (if not already running):
xcrun simctl boot <UDID>
open -a Simulator
xcrun simctl bootstatus <UDID> -b   # wait until boot is complete before installing

# Install the built .app:
xcrun simctl install <UDID> clients/apple/iOS/build/Debug-iphonesimulator/VoiceCatiOS.app

# Launch the app:
xcrun simctl launch <UDID> cat.voice.VoiceCatiOS

# Stream logs (Ctrl+C to stop — does not kill the app):
xcrun simctl spawn <UDID> log stream --predicate 'subsystem contains "VoiceCat"'

Run the full stack (server + iOS simulator)

# Terminal 1 — start the server
./build/dev/bin/voicecat-server --name "My Server"

# Terminal 2 — build + launch iOS client on simulator
scripts/run-ios-simulator.sh --build

On first connect the app will show a TOFU identity sheet — accept it, then join a channel.

Open in Xcode

open clients/apple/iOS/VoiceCatiOS.xcodeproj

Xcode can build and run on the simulator directly. The XCFramework must already exist (clients/apple/VoiceCatCore.xcframework/) — run build-xcframework.sh --all once before opening Xcode.