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
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]>
73 lines
4.2 KiB
Markdown
73 lines
4.2 KiB
Markdown
# 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.
|