Files
voice-cat/docs/building.md
Talon b2af1a3001 feat(macos): validate dev + apple-dev presets on macOS, fix 3 cross-platform bugs
macOS port groundwork — core, server, tools, and tests now build and run on
macOS 26.5 / Apple Silicon. ctest --preset dev green 21/21 (2 consecutive runs).
apple-dev produces valid arm64 libvoicecat.a + XCFramework for the Swift Package.

Three real cross-platform bugs found and fixed (all latent on Windows/Linux):

1. test_m2_voice.cpp POSIX branch missing <netdb.h> — Linux glibc transitively
   includes it, macOS doesn't. Would fail on any strict POSIX system.

2. SIGPIPE killing processes on macOS — writing to a closed TCP socket raises
   SIGPIPE by default (doesn't exist on Windows, benign on Linux). Fixed by
   ignoring SIGPIPE in both core client init and server startup (POSIX-only,
   #ifndef _WIN32). Production fix, not just tests.

3. Use-after-free of Asio's kqueue reactor on server shutdown — the
   deterministic test_tofu_flow segfault. TcpServerConn's tls_read_loop runs on
   a blocking-I/O thread; when Server::run() returned, io_context was destroyed
   while those threads were still running. On macOS kqueue the reactor pointer
   is null'd immediately -> segfault in socket.close(). Latent on Windows IOCP
   and Linux epoll. Fix: TcpAcceptor now tracks connections; new shutdown()
   closes all + joins threads before io is destroyed; Server::stop() now closes
   acceptor + media_relay too (was just io.stop()).

Verified: dev + apple-dev presets build green, 21/21 tests pass, server starts
+ two vccli text chat over TLS (M1 on Mac), vccli --voice starts MIC stream via
CoreAudio (M2 protocol-level), vccli --list-devices enumerates CoreAudio
devices, xcodebuild -create-xcframework produces valid VoiceCatCore.xcframework.

No ABI or proto changes. Docs updated: building.md, clients/apple/README.md,
PROGRESS.md, CLAUDE.md status line.
2026-06-18 13:24:42 +02:00

11 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.

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.
skeleton build/skeleton none Debug ON ON ON no all The M0 skeleton. Compiles with just a C++20 compiler — no VCPKG_ROOT needed. Subsystems are stubs (VC_ERR_NOT_IMPLEMENTED). Good for "does the repo even build" sanity checks. Runs 2 tests (smoke + frame_codec).
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 (21 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 three 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.
  • skeleton — fast no-deps build to confirm the stub path still compiles (CI smoke check).

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
dev skeleton Renamed to reflect its actual purpose (no-deps stub smoke check).
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.
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

dev, release, server-release, windows-client, and apple-* all 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 (scaffolding)

The apple-dev, apple-ios, and apple-ios-sim presets produce static libvoicecat.a slices for the Swift Package / XCFramework. The apple-dev preset (macOS slice) is validated — it builds green on macOS 26.5 / Apple Silicon and produces a valid arm64 .a + XCFramework. The iOS cross-compile presets (apple-ios, apple-ios-sim) are still scaffolding — not yet CI-validated. 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
cmake --preset apple-ios
cmake --build --preset apple-ios
# → build/apple-ios/lib/libvoicecat.a

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

The three .a files are then stitched into an XCFramework via xcodebuild -create-xcframework (see clients/apple/README.md for the planned workflow). Actual XCFramework stitching, AVAudioSession integration, and iOS UI work are tracked as follow-up tasks — the presets exist so the build entry point is ready when that work starts.