Files
vdm/docs/adr/0020-cross-platform-strategy.md
T
samiandClaude Opus 5 9624e685af
CI / clang-format (push) Canceled after 0s
CI / testserver (push) Canceled after 0s
CI / bootstrap-script (push) Canceled after 0s
CI / bootstrap-script-2604 (push) Canceled after 0s
CI / extension-lint (push) Canceled after 0s
CI / build (clang) (push) Canceled after 0s
CI / build (gcc) (push) Canceled after 0s
CI / sanitizers (dev) (push) Canceled after 0s
CI / sanitizers (tsan) (push) Canceled after 0s
CI / clang-tidy (push) Canceled after 0s
CI / conformance (push) Canceled after 0s
CI / nightly-integration (push) Canceled after 0s
CI / gui-dod (push) Canceled after 0s
CI / gui-dod-nightly (push) Canceled after 0s
docs: cmake/platform.cmake belongs to PKG/QA, not PORT (ADR 0020 amendment)
As written, ADR 0020 gave PORT ownership of cmake/platform*.cmake while also
making PORT wait for Phase 0 — but Phase 0's seams are exactly what needs
velox_platform_sources() to select platform/linux/*.cpp. PORT cannot start until
Phase 0 lands, and Phase 0 cannot land without the file PORT owns. PKG/QA caught
the circularity before writing anything, which was the right call.

Central OS detection is root build infrastructure, so it stays with PKG/QA and
lands during Phase 0. PORT keeps the per-OS backend directories and the macOS
and Windows packaging, and owns no part of cmake/.

Also drops a hardcoded test count from the PORT definition of done; the suite
number moves every round and the gate is that it stays green.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-15 17:46:48 +04:00

4.2 KiB

ADR 0020 — Porting to macOS (and later Windows) without forking the tree

Status: accepted · Supersedes nothing · Applies to every lane

Context

The project was built Ubuntu-first and says so in its own project description. A survey of main (2026-09-15, 57/57 green) found the Linux-specific surface is far smaller than the Ubuntu-first framing suggests — roughly ten files, all of them already isolated at the bottom of the stack:

Area Linux-only thing Files
core/io posix_fallocate, posix_fadvise, fdatasync sparse_file.cpp
core/meta fdatasync veloxpart.cpp
daemon/rpc eventfd event_loop.{hpp,cpp}, main.cpp
daemon/rpc timerfd main.cpp, sched/scheduler.hpp
daemon/rpc SO_PEERCRED / struct ucred uds_server.{hpp,cpp}
daemon/rpc abstract-namespace socket lock single_instance.{hpp,cpp}
daemon/rpc $XDG_RUNTIME_DIR layout runtime_dir.{hpp,cpp}
build libsecret-1 is REQUIRED at the root CMakeLists.txt

Everything else is already portable: the event loop is poll(2) (POSIX, not epoll), the transfer engine is libcurl, the GUI is Qt 6, tools/mockd and tests/conformance are Node, tools/testserver is stdlib Python, and the extension is a WebExtension.

Decision

One tree, one logic, per-OS backends behind seams.

  1. No #ifdef in logic. Platform differences live behind a narrow interface, with one implementation file per OS. A reader of sparse_file.cpp must not need to know which OS they are on. #ifdef is allowed only inside a platform/<os>/ file.
  2. Linux is the reference implementation. A port may never change Linux behaviour. The gate is mechanical: the full suite stays green on Ubuntu, and the Linux backend keeps the same syscalls it uses today. If a port needs a semantic change, that is an ADR of its own, not a port commit.
  3. Seams are introduced by the owning lane, on Linux, before any port work. CORE introduces core's seams; DAEMON introduces daemon's. This is a pure refactor: move the existing Linux code behind the interface unchanged, prove the suite is still green.
  4. A new lane, PORT, owns only the per-OS backends**/platform/<os>/**. It never writes logic, and it does not own cmake/: the central OS detection module is root build infrastructure and stays with PKG/QA, who must land it during Phase 0 because Phase 0's own seams consume it. PORT owning it would deadlock — PORT cannot start until Phase 0 lands, and Phase 0 cannot select platform sources without it. That keeps CLAUDE.md §1 intact: CORE still owns core/'s logic, DAEMON still owns daemon/'s, and macOS work can proceed in parallel without cross-lane writes.
  5. VELOX_OS_* is set once, centrally, in cmake/platform.cmake (owned by PKG/QA), and platform sources are selected there — not by globbing, not per-target ad hoc.
  6. Optional dependencies are gated, never removed. libsecret stays REQUIRED on Linux and is replaced by Keychain on macOS behind the same credential-store seam.

Phases

  • Phase 0 — on Ubuntu, by CORE + DAEMON. Introduce the seams and move today's Linux code behind them. Zero behaviour change; 57/57 stays green. No macOS code exists yet.
  • Phase 1 — on macOS, by PORT. Implement the macOS backends until veloxd, velox and libveloxcore build and their unit tests pass.
  • Phase 2 — parity. tools/testserver, conformance and the engine's hostile-mode matrix pass on macOS. The GUI builds against Qt 6 for macOS.
  • Phase 3 — packaging. .dmg/Homebrew, launchd instead of systemd, and the macOS native-messaging manifest locations. Deliberately last.

Consequences

  • Windows later is the same shape: a third backend directory, no new strategy. The seams chosen here are POSIX-flavoured but interface-level, so a Win32 backend is additive.
  • CI must build both, or macOS rots silently. Until a macOS runner exists, Phase 1 is verified by hand on the porting machine and the Linux gate stays authoritative.
  • The cost is one indirection at the bottom of the I/O and RPC stacks. Measured against the alternative — #ifdef drift, or a forked repo that diverges in a month — it is cheap.