Reproducible builds,
continuous delivery.
Until this week, Wok had no CI. Every check ran on one machine with one compiler: Apple Clang on arm64. For a compiler project, that is an embarrassing gap. It now builds on every push — five legs across linux-x64, linux-arm64 and macos-arm64, under both GCC and Clang, plus the Haskell library and its 2,428-test suite.
The toolchain comes from a Nix flake pinned to a nixpkgs commit, not a branch. A fixed source
revision then gets you a fixed compiler. Reproducible here means pinned inputs, not
bit-identical outputs. GHC output is not byte-for-byte reproducible, and we do not claim it is. Nix pins
the compiler. cabal.project.freeze with an index-state pin fixes the Haskell
packages.
The useful part is what the second compiler found the first time it read our C.
The C pitfalls we walked into
Attribute placement. C23 puts [[unsequenced]] and
[[reproducible]] on the function declarator, after the parameter list. We wrote them as
prefixes, before the return type. Clang 19 does not implement the attribute, so our capability check was
false and the GNU fallback was used. Nothing was reported. GCC 15 does implement it, and rejected the
code.
The fix was not to move the attributes. We put the GNU spelling first. It is valid in prefix position on both compilers and gives the same optimisation hint. Moving every site to suffix position, so we can use the standard spelling, is a separate mechanical change.
#if defined(__GNUC__) # define WOK_PURE __attribute__((const)) #elif defined(__has_c_attribute) && __has_c_attribute(unsequenced) # define WOK_PURE [[unsequenced]] #else # define WOK_PURE #endif
The guard is a capability check, not a version check. C23 is already required elsewhere. A
compiler can be fully conforming and still not implement [[unsequenced]].
Pragmas only one compiler reads. Our diagnostic suppressions around some vendored code
used #pragma clang diagnostic. GCC ignores it. On GCC none of them applied, and nobody had
read the warnings underneath. Each suppression now has to work on both compilers, or be removed. A
suppression that works on one compiler is not a decision. It is a blind spot.
Feature-test macros set too late. Under strict -std=c23, glibc hides POSIX
unless you ask for it, and the request only works if it comes before the first system header. Ours were
missing or too late. Apple’s libc hid the problem, because it exposes POSIX anyway. The ordering
rule used to be a comment. It is now a build error: our base header fails to compile if it detects that a
system header was included first.
Smaller findings. A comparison between two anonymous enums. Truncation at both ends of the shrinker’s report chain. A file-scope static that nothing used. None of these are exotic. They are what one compiler misses.
All of them have the same cause. What the standard defines, what your compiler implements, and what your libc exposes are three different things. One compiler answers only one of them.
Where the floor actually is
We now require GCC 15 or Clang 19. Both numbers come from CI rejecting the version
below, not from a guess. GCC 14 compiles every C23 feature we use, but reports
__STDC_VERSION__ as 202000L. Our own header rejects that, so GCC 14 fails on
our guard, not on a missing feature. Clang 18 has no constexpr in C, and the tokeniser needs
it.
What ships out the other end
Coverage is published as data, not as a badge, at coverage.wokml.org:
coverage.json, build provenance and the raw tracefile. Each is stamped
with the commit it measured, and dated by that commit instead of the wall clock, so regenerating the
report produces the same bytes.
That is what ships today. Binaries are next. When releases start, they will be attested: signed, and carrying the provenance of the commit and the CI run that built them. You can then trace an artifact back to its source instead of trusting us. The pinned toolchain is what makes attestation useful, because it records the inputs.
The change is public: github.com/WokML/wokml.
Everything else we’ve written is over here.