149 lines
9.3 KiB
Markdown
149 lines
9.3 KiB
Markdown
# Porting calog
|
|
|
|
calog targets **Linux, macOS, and Windows**. Its core -- values, broker, the actor/threading
|
|
model, and every engine adapter -- is portable C11. The only operating-system-specific surface is
|
|
small and centralized:
|
|
|
|
- **Threads**: POSIX `pthreads`. Native on Linux and macOS; on Windows via mingw-w64's `winpthreads`.
|
|
- **Sockets + DNS**: BSD sockets + `getaddrinfo`, used only by the net/ssh/http libraries. BSD
|
|
sockets on Linux/macOS, Winsock on Windows. All of this is behind `src/calogPlatform.h`.
|
|
|
|
There is no `epoll`/`eventfd`/`kqueue`, no `fork`/`exec`, and no `dlopen` in calog's own code (the
|
|
actor model uses blocking sockets on per-context threads), so a port is a thin shim, not a rewrite.
|
|
|
|
## The platform abstraction: `src/calogPlatform.h`
|
|
|
|
The net/ssh/http libraries use these instead of raw socket calls, so the same source compiles on all
|
|
three platforms:
|
|
|
|
| Abstraction | POSIX (Linux/macOS) | Windows |
|
|
|---|---|---|
|
|
| `CalogSocketT` | `int` | `SOCKET` |
|
|
| `CALOG_INVALID_SOCKET` | `-1` | `INVALID_SOCKET` |
|
|
| `calogSockClose(fd)` | `close` | `closesocket` |
|
|
| `calogSockErrno()` / `calogSockErrStr()` | `errno` / `strerror` | `WSAGetLastError` / `FormatMessage` |
|
|
| `calogSockSetNonblock(fd, on)` | `fcntl(O_NONBLOCK)` | `ioctlsocket(FIONBIO)` |
|
|
| `calogSockInProgress(err)` | `EINPROGRESS` | `WSAEWOULDBLOCK`/`WSAEINPROGRESS` |
|
|
| `calogPoll(...)` | `poll` | `WSAPoll` |
|
|
| `calogPlatformNetInit()` / `Shutdown()` | no-op | `WSAStartup` / `WSACleanup` |
|
|
| `CALOG_MSG_NOSIGNAL` | `MSG_NOSIGNAL` | `0` (Windows has no `SIGPIPE`) |
|
|
|
|
A socket `fd` is unsigned on Windows, so the code compares against `CALOG_INVALID_SOCKET` (never
|
|
`fd < 0`). The abstraction is behavior-identical on POSIX (it maps to the same names), so the Linux
|
|
and macOS builds are unchanged by it.
|
|
|
|
## Build matrix
|
|
|
|
| Target | Command | Runtime dependencies | DNS |
|
|
|---|---|---|---|
|
|
| Linux dev | `make` | glibc, libstdc++, libgcc, ASan/UBSan (dev only) | yes |
|
|
| Linux release | `make release` | **glibc only** (libstdc++/libgcc linked static) | yes |
|
|
| Linux static | `make static` on Alpine/musl | **none** (fully static) | yes (musl resolver) |
|
|
| Linux static | `make static` on glibc | none, but see caveat | **no** (glibc NSS needs dlopen) |
|
|
| macOS | `make` (Darwin auto-detected) | libSystem (always present) | yes |
|
|
| Windows | mingw-w64 build | core OS DLLs (`kernel32`, `ws2_32`) | yes |
|
|
|
|
The Makefile auto-detects the platform (`PLATFORM` = `linux`/`darwin`/`windows`/`posix`) and adjusts
|
|
the link line: `DLLIB` is `-ldl` on Linux (empty on macOS, where `dlopen` is in libSystem) and
|
|
`SOCKETLIBS` is `-lws2_32` on Windows.
|
|
|
|
## Linux
|
|
|
|
- **`make`** -- the sanitized development build (ASan + UBSan + strict warnings).
|
|
- **`make release`** -- the distribution build: sanitizers off, `-static-libstdc++ -static-libgcc`,
|
|
glibc dynamic. `ldd` collapses to just `libc`/`libm` + the loader. Because glibc stays dynamic,
|
|
`getaddrinfo` can still `dlopen` its NSS modules, so **DNS works**. Runs on any mainstream
|
|
glibc distro with nothing to install.
|
|
- **`make static` on Alpine (musl)** -- a fully static binary with **zero** runtime dependencies and
|
|
**working DNS** (musl has a built-in resolver, no NSS/dlopen). This is the minimal-dependency
|
|
artifact. Because calog builds every dependency from source, and Alpine's default toolchain is
|
|
musl, no per-dependency changes are needed -- build it in an Alpine container.
|
|
- **`make static` on glibc** -- fully static, but name resolution and `dlopen`-based Lua C modules
|
|
do not work (glibc NSS requires runtime `dlopen`). Connect by IP, or use the musl route above.
|
|
|
|
## macOS (Darwin)
|
|
|
|
The C is already POSIX-compatible (pthreads, BSD sockets, `getaddrinfo` are all native), so the port
|
|
is build configuration, not code -- confirmed by `tools/crossBuild.sh`, which links valid Mach-O
|
|
executables for **both x86_64 (Intel) and arm64 (Apple Silicon)** using zig's bundled `libSystem`
|
|
stub (calog only needs `libSystem` -- libc/pthreads/sockets -- so no Apple SDK/frameworks are
|
|
required for the core). Apple does not support statically linking libc, but `libSystem` is part of
|
|
the OS and always present, so "static everything except libSystem" is the natural minimal-deps build.
|
|
Each vendored dependency builds its own way and needs a macOS configuration:
|
|
|
|
- OpenSSL: `./Configure darwin64-<arch>-cc`
|
|
- Tcl: its `configure` detects Darwin
|
|
- mruby / Lua / SQLite / ENet / QuickJS / Squirrel / Berry / s7 / Wren: compile as-is with clang
|
|
- libssh2 / MariaDB: CMake with the macOS toolchain
|
|
|
|
Link against dynamic `libSystem` (the default); `-ldl` is dropped automatically (`DLLIB` is empty on
|
|
Darwin) and the C++ runtime is `libc++` (`CXXLIB = -lc++`). SIGPIPE is ignored at CLI startup, since
|
|
macOS `send()` has no `MSG_NOSIGNAL`. ENet's `unix.c` backend is correct on macOS (BSD sockets).
|
|
|
|
## Windows (mingw-w64)
|
|
|
|
calog's pthread code compiles unchanged on Windows against **winpthreads** (POSIX threads over the
|
|
Win32 API), which is **vendored** at `vendor/winpthreads/` and built from source like every other
|
|
dependency (see its `NOTICE.calog`). `src/calogPlatform.h` handles the Winsock differences. Building
|
|
uses a **mingw-w64** toolchain (not MSVC) for its POSIX-ish environment; `tools/crossBuild.sh` cross-
|
|
compiles from Linux with a single zig toolchain and is the tested path. Remaining work is build
|
|
configuration:
|
|
|
|
- Build each vendored dependency for mingw (OpenSSL `mingw64`, Tcl's `win/` makefile or configure,
|
|
libssh2/MariaDB via CMake with the mingw toolchain, mruby with a mingw build_config).
|
|
- ENet selects its `win32.c` backend automatically (`ENETBACKEND`); the link adds `-lws2_32 -lwinmm`
|
|
(handled by `SOCKETLIBS`).
|
|
- Static-link the runtime (`-static -static-libgcc -static-libstdc++`) so the binary depends only on
|
|
core Windows DLLs, which are always present. DNS works through Winsock's `getaddrinfo`.
|
|
|
|
Remaining code review for Windows: `calogFs.c` and `calogHttp.c` use POSIX headers (`dirent.h`,
|
|
`sys/stat.h`, `sys/time.h`) that mingw-w64 mostly provides, but path handling (`/` vs `\`) and a few
|
|
calls may need adjustment; `calogSsh.c` already guards `sys/select.h` (Winsock supplies
|
|
`fd_set`/`select`), and `httpSetTimeouts` already branches on the Winsock `SO_RCVTIMEO` DWORD form.
|
|
|
|
## Cross-compiling from Linux with zig (`tools/crossBuild.sh`)
|
|
|
|
A single [zig](https://ziglang.org/download/) toolchain cross-compiles C and C++ to musl-static and
|
|
Windows without installing per-target toolchains, so the ports can be exercised on a plain Linux box:
|
|
|
|
```
|
|
ZIG=/path/to/zig ./tools/crossBuild.sh
|
|
```
|
|
|
|
It builds a representative set -- core + one engine of each kind (Lua, QuickJS, Squirrel/C++,
|
|
my-basic) + the socket library (`calogNet`) -- because the crypto/db/http/ssh libraries pull in
|
|
OpenSSL/MariaDB/PostgreSQL/libssh2, each needing its own per-target build. What it does:
|
|
|
|
- **musl**: fully static Linux binaries. These **run on the build host**, so the script builds AND
|
|
runs them -- verifying the core, all four engine kinds, C++, and the socket abstraction on musl.
|
|
- **Windows**: builds `vendor/winpthreads` into `libwinpthreads.a`, then links real `.exe`s
|
|
(including the Winsock + ENet-win32 socket path). Verified as valid PE executables; running them
|
|
needs wine.
|
|
|
|
Verified results (this repo): musl -- Lua, QuickJS, my-basic, Squirrel, and `testNet` all build and
|
|
**pass** (fully static); Windows -- `testEngineLua.exe` and `testNet.exe` build against vendored
|
|
winpthreads. Since the Windows binaries are the *same source* that passes on musl, the build is strong
|
|
evidence; full run-verification needs a Windows host or wine.
|
|
|
|
## Status
|
|
|
|
- **Platform abstraction** (`calogPlatform.h`) and the net/ssh/http conversion: **done**, verified
|
|
behavior-identical on Linux (`make test` 30/30, socket + HTTP loopback smoke, ASan- and TSan-clean).
|
|
- **Linux release** target: **done and verified** -- `ldd bin/calog` shows only `libc`/`libm` + loader.
|
|
- **Linux musl-static**: **run-verified** via `tools/crossBuild.sh` (zig) -- core + Lua + QuickJS +
|
|
my-basic + Squirrel + `testNet` all build fully static and pass on the host. Also works via
|
|
`make static` in an Alpine container (musl default toolchain).
|
|
- **Windows**: **build-verified** via `tools/crossBuild.sh` -- `testEngineLua.exe` and `testNet.exe`
|
|
(core + engine + Winsock socket abstraction) link against vendored `winpthreads` into real PE
|
|
executables. Run-verification needs a Windows host or wine; the binaries are the same source that
|
|
passes on musl.
|
|
- **Build system**: OS-aware (`PLATFORM`, `DLLIB`, `CXXLIB`, `SOCKETLIBS`, `ENETBACKEND`, vendored
|
|
`winpthreads`); the Linux values are identical to before, so the dev build is unchanged.
|
|
- **macOS**: **build-verified** via `tools/crossBuild.sh` -- core + Lua + `testNet` link into valid
|
|
Mach-O executables for both **x86_64 and arm64** (zig's libSystem stub; no Apple SDK needed for
|
|
calog's libc surface). Run-verification needs a Mac. The C code and Makefile hooks are all in place
|
|
(`libc++`, no `-ldl`, SIGPIPE, native pthreads + BSD sockets).
|
|
- The heavy vendored libraries (OpenSSL, Tcl, mruby, libssh2, MariaDB, PostgreSQL) still need a
|
|
per-target build to reach a *full-featured* calog on macOS/Windows; the cross-build proves the core
|
|
broker + engines + socket layer, which is where all the portability risk lived. No code rewrite is
|
|
expected -- the OS surface is only threads + sockets + DNS, all abstracted.
|