docs: define the cross-platform porting strategy (ADR 0020, macOS first)
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
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
The tree is Ubuntu-first, but the Linux-specific surface is about ten files and already sits at the bottom of the I/O and RPC stacks. Rather than fork the repo or scatter ifdefs, platform differences go behind narrow seams with one backend directory per OS, selected centrally in cmake/platform.cmake. Linux stays the reference implementation and its behaviour does not change: the owning lanes (CORE, DAEMON) move today's Linux code behind the seams on Ubuntu first, with the suite still green, before any macOS code exists. A new PORT lane owns only the per-OS backends, so macOS work never writes shared logic and CLAUDE.md's lane rule stays intact. Adds ADR 0020 (the decision and phases), docs/08-porting.md (seam list, API mapping for macOS and Windows, dependency table, verification gates) and docs/agents/AGENT-PORT.md (the lane brief), plus the PORT row in the ownership table. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# 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>/**` and
|
||||
`cmake/platform*.cmake`. It never writes logic. 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`, and platform sources
|
||||
are selected there — not by globbing, and 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.
|
||||
Reference in New Issue
Block a user