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.
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-serverbuild/dev/bin/vcclibuild/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.