feat(windows): zig cross-build knob, AE_TEST_RUNNER hook, fast CI lane - #1596
Conversation
Closes #1592, #1593, #1594. The MSYS2 Windows legs take ~20 minutes, so Windows-only compile breakage surfaces long after the author moved on. This adds a fast lane that compiles the same code paths on a Linux runner in minutes and runs the base of the test pyramid under Wine. WINDOWS=1 (#1592) — cross-build the ae/aetherc toolchain FOR Windows via `zig cc -target x86_64-windows-gnu`. Companion to FREEBSD=1 and simpler: zig bundles the mingw-w64 headers and CRT, so there is no sysroot to fetch. Capability-lean like the FreeBSD leg (OpenSSL/zlib/nghttp2/YAML forced off — host pkg-config would resolve the HOST's libs and poison the target); vendored PCRE2 keeps std.regex. Three host-keyed spots needed opting out or the cross link picked up host POSIX libs: AUDIO_LDFLAGS, the -pthread block, and __USE_MINGW_ANSI_STDIO (which turned out to be correct as-is under zig's libmingwex). AE_TEST_RUNNER (#1592) — `ae run` / `ae test` prefix the binary they just built with this runner instead of exec'ing it directly, modelled on cargo's CARGO_TARGET_<triple>_RUNNER. Empty by default, so every existing caller is unaffected. Keeps target-awareness at the EDGE: nothing in std.spec or the test sources knows it is under an emulator, and the AE_SPEC_* structured-report contract is inherited through the wrapper. CI fast lane (#1593) — `windows-cross` job in windows.yml (not a separate file: `needs:` only works within one workflow). Cross-builds, asserts the artifacts really are PE, warms a Wine prefix once, smoke- tests hello-world, then sweeps with AE_TEST_RUNNER=wine. Both MSYS2 legs and headers-msvc now `needs:` it, so a fast-lane failure skips ~20 minutes of doomed compute. Exclusions are named in tests/ae_sweep_prune_wine.txt — fs/path semantics, sockets/h2/sendfile, actor timing and the LD_PRELOAD sandbox, all areas where a green Wine run would be misleading. `make test-ae` gained AE_SWEEP_EXTRA_PRUNE to layer that list, applied to BOTH the .ae and the .sh sweeps (the latter had no prune filter at all). Build-target stamp — one build/ tree holds one target's objects; mixing ELF and PE died deep in the link with "unknown file type". build/ now carries .build-target and a mismatching build stops immediately with an actionable message. `make clean`/`help` are exempt, or the guard would block its own remedy. Docs (#1594) — README's "enforced down to libc" now names the platform boundary, and docs/containment-sandbox.md states that Windows is out BY CONSTRUCTION and that a Wine run does not exercise containment at all (preloading into the wine process intercepts wine's libc, not the guest's). docs/build-system.md documents both cross knobs and the runner. Verified locally: clean WINDOWS=1 build produces PE32+ ae.exe/aetherc.exe; AE_TEST_RUNNER proven to intercept via a logging wrapper on both `ae run` and `ae test`, and inert when unset; native build + 390 unit tests unaffected; every prune pattern matches real paths. The Wine half is unproven locally (no Wine on this box) — CI exercises it first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI proved the Wine half of #1593 doesn't work as designed, so ship the half that does rather than a lane that fails on every PR. What the run showed: provisioning, cross-build, PE verification and the Wine prefix all succeeded; the hello-world smoke step failed because `ae.exe` under Wine tried to download 250 MB of MinGW-w64. That is structural, not a config slip — `ae` is a compile-and-run driver, so running it inside a Wine prefix needs a WINDOWS C toolchain there to compile the C the compiler emits. The obvious alternative (drive the NATIVE `ae` with AE_CC="zig cc -target x86_64-windows-gnu", so only the resulting binary goes through Wine) needs a Windows libaether.a living beside the native one — verified locally: it fails to link against the Linux archive — and one build/ tree holds exactly one target's archives. The sweep step would have failed too, independently: test-ae shells out to build/ae, which is build/ae.exe in a WINDOWS=1 tree. So windows-cross now cross-builds and asserts the artifacts are PE, and stops there. The gating is unchanged and is most of the value: the MSYS2 legs still `needs:` it, so a compile break skips ~20 minutes of doomed compute and surfaces at minute ~4. AE_TEST_RUNNER stays — it works, it is tested, and it is exactly what a future execution lane needs. tests/ae_sweep_prune_wine.txt stays too, reframed as the design record: the durable part was always the ANALYSIS of which areas a Wine lane must never claim to cover, not the wiring. CHANGELOG and both docs now describe what ships instead of what was planned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Scope correction after the first CI run: the Wine execution half is deferred; this PR is now compile-only. The run is worth reading as a result rather than a failure — it invalidated a premise in #1593's plan that neither of us had tested:
I checked the obvious alternative before giving up on it: drive the native That is a real piece of design work, not a tweak, so it belongs in its own issue rather than blocking this one. What still ships, and why it is still worth it:
CHANGELOG, Happy to close #1593 as partially-delivered and open a follow-up for the execution lane, or leave #1593 open scoped down to just that — your call on which reads better in the tracker. |
Closes #1592, closes #1593, closes #1594 — the three-part plan for a fast Linux-hosted Windows signal.
Why: the MSYS2 legs take ~20 minutes, so Windows-only compile breakage is discovered long after the author context-switched away. This lane compiles the same code paths on a Linux runner in minutes. Minutes are free on a public repo — the prize is PR turnaround, and now also not burning 20 minutes of doomed compute when the fast lane fails.
WINDOWS=1cross knob (#1592)Mirrors
FREEBSD=1, viazig cc -target x86_64-windows-gnu. Simpler than the FreeBSD leg: zig bundles the mingw-w64 headers and CRT, so there is no sysroot to fetch.make compiler ae stdlib WINDOWS=1 ZIG=/path/to/zig file build/ae.exe # PE32+ executable (console) x86-64, for MS WindowsCapability-lean by design (out of scope: cross-compiling OpenSSL/nghttp2, the genuinely expensive part) — those std features ship as their "unavailable" stubs, exactly as on any box without the libraries. Vendored PCRE2 needs no host library, so
std.regexsurvives.Three host-
uname-keyed spots had to opt out or the cross link picked up the host's POSIX libs (zig-lld: unable to find dynamic system library 'pthread'):AUDIO_LDFLAGS, the-pthreadblock, and__USE_MINGW_ANSI_STDIO— the last of which turned out to be correct as-is, since zig ships mingw-w64's libmingwex.AE_TEST_RUNNERhook (#1592)ae run/ae testprefix the binary they just built with the runner instead of exec'ing it — cargo'sCARGO_TARGET_<triple>_RUNNERpattern. Empty by default, so every existing caller is byte-for-byte unaffected. 45 lines, two call sites.The point is that it keeps target-awareness at the edge: no forked
_windowsspec files, nothing in std.spec or the test sources knows it is under an emulator, and theAE_SPEC_FORMAT/AE_SPEC_REPORTcontract is inherited straight through the wrapper.Fast CI lane (#1593)
windows-crosslives inside windows.yml, not a separate file —needs:only works within one workflow, and gating was the point. Both MSYS2 legs andheaders-msvcnowneeds: windows-cross, so a fast-lane failure skips the slow tier. (needs:-skipped jobs report as skipped, not failed; the PR still reads red from the fast lane's own failure, so branch protection behaves.)Steps: provision zig (same cache discipline as ci.yml's FreeBSD leg) → cross-build → assert the artifacts really are PE → warm the Wine prefix once → smoke-test hello-world → sweep with
AE_TEST_RUNNER=wine.Exclusions are named in
tests/ae_sweep_prune_wine.txt, not skipped silently — fs/path semantics (Wine's case-mapping is not NTFS), sockets/h2/sendfile (IOCP and TransmitFile, not epoll/sendfile), actor-scheduler timing (flake under emulation), and the LD_PRELOAD sandbox.make test-aegainedAE_SWEEP_EXTRA_PRUNE=<file>, applied to both the.aeand.shsweeps — the.shsweep had no prune filter at all, so the excluded suites would otherwise have run anyway.Build-target stamp (fell out of the work)
One
build/tree holds one target's objects and archives; mixing ELF and PE dies deep in the link withunknown file type, and a cross build silently clobberslibaether.afor the next native build.build/.build-targetnow makes the next mismatching build stop immediately with an actionable message.make clean/helpare exempt — the guard must not block its own remedy.Docs (#1594)
README's "enforced down to libc" names the platform boundary;
docs/containment-sandbox.mdstates Windows is out by construction (there is noLD_PRELOAD, so this is not a port waiting to be written) and that a Wine run does not exercise containment at all — preloading into the wine process intercepts wine's libc calls, not the guest's, so a green run there would be actively misleading.docs/build-system.mddocuments both cross knobs and the runner hook.Verification
WINDOWS=1build from scratch → PE32+ae.exe+aetherc.exe, correct stamp.AE_TEST_RUNNERproven to intercept with a logging wrapper on bothae runandae test; inert when unset or empty.AE_SWEEP_EXTRA_PRUNEverified end-to-end (687 → 203 tests with a test list, message reports the count)..aesweep: 850 passing. The only failures arehttp_server_h2— documented as failing on clean main inasks/h2-50-stream-stress-framing-layer-error.md— plus a load-relatedhttp_request_remote_addrflake that passes in isolation, andcontrib_vulkan_portability(no GPU on this box).Honest gap: the Wine execution half is unproven locally (no Wine installed here). The cross-build and the runner hook are both proven; CI is the first real exercise of
AE_TEST_RUNNER=wine. If it needs tuning, expect it in the timeout or the prune list rather than the mechanism.Incidentally the lane already earned its keep: the cross-build surfaced two Windows-only
-Wsign-comparediagnostics inside Winsock's ownFD_SETmacro (SOCKETis unsigned). They originate in zig's bundled headers rather than our code, so the fast lane deliberately does not promote warnings to errors — that enforcement stays with the MSYS2 legs, against the headers Windows users actually compile with.🤖 Generated with Claude Code