Skip to content

recipes ask which C environment this is, not which operating system - #458

Merged
Sunrisepeak merged 10 commits into
mainfrom
recipes-ask-for-the-header
Sep 21, 2026
Merged

Sunrisepeak merged 10 commits into
mainfrom
recipes-ask-for-the-header

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Sep 20, 2026

Copy link
Copy Markdown
Member

A C library that presents POSIX on Windows is a target these recipes did not
have when they were written, and each of them answers "which C environment is
this" by asking a different question about the operating system. This branch
replaces those questions one at a time, and finishes with the measurement that
reads them.

The measurement

Thirty members, two targets, sixty cells, against mcpp 2026.9.21.2 and
openkal-llvm-runtime 0.15.0:

before after
runs 50 57
fails 10 3, all declared
builds (built, tests failed) 0 0

compat.py check against the published baseline: no published label
regressed, and no declaration was contradicted.

What each commit measured

A configure snapshot describes a C environment, not an operating system
(compat.c-ares). The three frozen ares_config.h files from gRPC were
selected by OS. Over openkal the target is still PE and still Win64, and
config_windows then describes nothing that is there: of the sixteen headers
it declares absent, fifteen are present in that graph. The diagnostics arrive
one macro at a time —

ares_setup.h:81           fatal error: 'windows.h' file not found
ares_timeval.c:70         call to undeclared function 'gettimeofday'
ares_parse_ns_reply.c:71  incomplete type 'struct hostent'

— with thirteen more behind them. Giving that target config_linux makes the
member run, so the three snapshots move into one top-level generated_files
entry with three arms and _WIN32 / __APPLE__ select between them. Each arm
is its snapshot verbatim, checked byte for byte; linux and macOS select what
they selected before.

A library built outside the graph says so (compat.openssl). Its
install() hook runs OpenSSL's own Configure with gcc --sysroot=<glibc payload>, so the archive is a glibc artefact and the hook cannot be otherwise
— it runs once at install time and knows nothing about the graph a consumer
will resolve. provides = ["platform-sdk"] puts that where a reader and a
machine can both see it, instead of undefined symbol: setcontext three
layers down.

curl's two cells have one cause, and it is not curl. No TLS provider
exists in this graph, and the member's own test refuses a curl without one
(transfer.cpp:45). Declared per target with the evidence, and the
declaration is falsifiable: compat.py check fails if a declared cell builds.

Two of the three guards that would have excluded this target never arrive
(compat.mimalloc). prim-tls.h:176 enables __builtin_thread_pointer()
unless __APPLE__, __CYGWIN__ or MI_LIBC_MUSL says otherwise; mcpp
suppresses __CYGWIN__ deliberately and MI_LIBC_MUSL is a build option
upstream expects the packager to set. The guard passes and LLVM has no
lowering, which reaches the results as the bare line error: build failed.
The header documents a user override; this uses it, in the Windows branch,
where a Win32 target never reads it.

A static image cannot answer dladdr, and that is not a failure (expat's
member). musl's dladdr is a weak stub returning 0 and a static link has no
loader to override it. Skipping would make "no loader" and "the loader
answered wrongly" the same reading, so the fallback asserts what distinguishes
them.

builds was a statement about the dependencies (the harness). Every
member keeps its sources under tests/, and a target with no runner was
measured with mcpp build, which builds the package — for these members,
nothing. Measured on archive for aarch64-macos: 1990 objects, none from the
member. The reading was never wrong in CI, because both pinned targets are
runnable there; it was wrong the moment a third target was considered.
mcpp test --no-run compiles and links the member's own tests instead, and
the command choice moves into command_for with three selftest legs.

Dependency

The harness change needs mcpp test --no-run, which is
mcpp-community/mcpp#682. pins.toml and the workflow's MCPP_VERSION move
together to the release that carries it; until then both pinned targets are
runnable and the new path is not reached.

Process

24f77d9 is a reapply: the original was pushed directly to main, which this
repository does not allow for anything but documentation. It was reverted in
75f5313 and resubmitted here.

Every member of this suite keeps its sources under `tests/`, and for a target
with no runner the harness measured the cell with `mcpp build`, which builds
the PACKAGE. For these members that compiles nothing of the member at all: it
compiled their dependencies, exited 0, and this file recorded `builds`.
Measured on `archive` for `aarch64-macos`, 1990 objects, none of them from
`tests/compression.cpp` or `tests/versions.cpp`.

The reading was never wrong in CI, because both pinned targets are runnable
there — linux natively and windows through Wine, which the workflow installs
and checks. It was wrong the moment a third target was considered, which is
what this change was written for.

`mcpp test --no-run` compiles and links the member's own tests for the target
and does not execute them. The command choice moves into `command_for` so it
has a criterion that needs no toolchain, network or member: three selftest
legs, one per branch, and the one that matters names `--no-run` rather than
`build`.

Also drops a tracked `__pycache__/*.pyc` — a compiled artefact of one
interpreter version, which produced a diff for anyone running the harness on
3.13 and an untracked second copy for anyone on 3.12 — and widens .gitignore
so the next one is not committed either.
c-ares cannot run its own configure here, so this package ships gRPC 1.83.0's
three frozen `ares_config.h` snapshots and selected between them by the
operating system. That was the same question as "which C environment is this"
until a C library began presenting POSIX on Windows. Over openkal the target
is still PE and still Win64, and `config_windows` then describes nothing that
is there: of the sixteen headers it declares absent, fifteen are present in
that graph.

The diagnostics arrive one macro at a time, which is what made this look like
a list of small fixes rather than one wrong answer:

    ares_setup.h:81      fatal error: 'windows.h' file not found
    ares_timeval.c:70    call to undeclared function 'gettimeofday'
    ares_parse_ns_reply.c:71  invalid application of 'sizeof' to an
                              incomplete type 'struct hostent'

with thirteen more behind them. Measured directly: give that target
`config_linux` and the member goes from `fails` to `runs`.

`_WIN32` and `__APPLE__` are the engine's answer to exactly this question —
mcpp defines `_WIN32` for a Win32 environment and suppresses it for a
POSIX-presenting one — so the three snapshots move into one top-level
`generated_files` entry with three arms and the preprocessor selects. This
needs no new key in the descriptor and no new axis in the engine.

Each arm is its snapshot verbatim, checked byte for byte against the file it
came from; none is edited to suit another. Linux and macOS select what they
selected before, so neither reading moves. `cflags`/`ldflags` stay per-OS: a
real Windows target still needs ws2_32, and a POSIX-presenting one asks for
nothing in it.

Measured after the change: `runs` on x86_64-windows-gnu and on
x86_64-linux-gnu.
`compat.openssl` runs OpenSSL's own Configure and make from `install()`,
outside mcpp's compile rules. `cc_override()` hands that build
`gcc --sysroot=<xim:glibc payload>` on linux and `/usr/bin/cc` on macOS, so
the archive is a glibc or libSystem artefact — and the hook cannot be
otherwise, because it runs once at install time and knows nothing about the
graph a later consumer will resolve.

Linked into a graph whose C library is musl, it fails at symbols that library
does not have. Measured on x86_64-linux-gnu over openkal:

    ld.lld: error: undefined symbol: setcontext
    >>> referenced by async.c
    >>>    libcrypto-lib-async.o in archive .../libcrypto.a

musl omits the `*context` family deliberately, and `__isoc23_strtol` is
glibc's header redirection for `strtol`. Neither names the cause, and both
arrive after a successful configure, compile and archive step.

`provides = ["platform-sdk"]` is the engine's existing word for "this package
brings the platform's own side into the graph" (docs/06). It does not make the
link succeed and is not meant to. Measured, it puts the fact where a reader
and a machine can both see it:

    Target              platform-deps     openssl@3.5.1

and with `[build] platform-dependencies = "refuse"`:

    error: [build] platform-dependencies = "refuse", and the dependency graph
           brings a platform dependency: openssl@3.5.1.

Building OpenSSL inside the graph is what would make the link succeed. That is
a different change: its source list comes from Configure, so it needs the
snapshot treatment compat.c-ares and compat.ffmpeg have.
Both targets fail because this graph supplies no TLS provider, and the member's
own test refuses a curl without one — transfer.cpp:45 fails when
CURL_VERSION_SSL is clear, on the stated reasoning that "a curl built with no
SSL still links and still runs, it just silently cannot do https". Configuring
the member without TLS would turn both cells green and measure a different
library.

On linux the provider is compat.openssl, which is built by an install() hook
through OpenSSL's own Configure and is therefore a glibc artefact; it now
declares `provides = ["platform-sdk"]` and fails this link at `setcontext` and
`__isoc23_strtol`.

On windows the provider is Schannel, selected together with the checked-in
lib/config-win32.h by `_WIN32` — the macro a C library presenting POSIX on
Windows does not define. With neither HAVE_CONFIG_H nor _WIN32 curl reads no
configuration at all and stops at `curl_setup.h:591: "too small curl_off_t"`,
which names a consequence 591 lines away from its cause. compat.openssl has no
Windows build, so there is no second provider to fall back to.

Declared rather than left unexplained, and the declaration is falsifiable:
`compat.py check` fails if a declared cell builds, so an in-graph OpenSSL would
force these lines to be removed rather than quietly keeping a stale excuse.
…rive

mimalloc's prim-tls.h:176 enables `__builtin_thread_pointer()` for x86_64 with
clang >= 14 unless `__APPLE__`, `__CYGWIN__` or `MI_LIBC_MUSL` says otherwise.
Two of those three describe this target and neither reaches it: mcpp suppresses
`__CYGWIN__` deliberately, because source must not read the realisation triple
as a statement about Cygwin, and `MI_LIBC_MUSL` is a build option upstream
expects the packager to set ("Enable this when linking with musl libc",
CMakeLists.txt:39) rather than something it detects.

So the guard passes and LLVM has no lowering for the builtin on this OS:

    fatal error: error in backend: Target OS doesn't support
                 __builtin_thread_pointer() yet.

which arrives as a backend crash with no source location, and reaches the
compatibility results as the bare line "error: build failed".

prim-tls.h:176 opens with `#if !defined(MI_USE_BUILTIN_THREAD_POINTER) /* allow
user override */`, so the fix is to give it one. It goes in the Windows branch,
where a Win32 target never reads it — `_WIN32` selects `NtCurrentTeb()` three
arms earlier — so it changes only the target that has no `_WIN32`.

Measured: `runs` on x86_64-windows-gnu and unchanged `runs` on
x86_64-linux-gnu.
expat's member asserts that the code which parsed is not the ecosystem
payload's libexpat, and asks `dladdr` to attribute it. musl's `dladdr` is
`weak_alias(stub_dladdr, dladdr)` returning 0 (src/ldso/dladdr.c); the dynamic
loader overrides it, and a static link has no loader to do so. An openkal
program is statically linked, so the call returns 0 for every address and the
member failed one check on both targets.

The risk the check exists for cannot arise there either: nothing was loaded at
runtime, so the only expat in the process is the one that was linked in.

Skipping it would make "no loader" and "the loader answered wrongly" the same
reading, so the fallback asserts what distinguishes them. `main` is in this
executable by construction; if `dladdr` cannot locate that either, there is no
dynamic symbol information at all. If it locates `main` and not
`XML_ParserCreate`, the check fails as it did before.

Measured both ways: `runs` on both openkal targets, and on a dynamic host
build the positive branch still runs and still prints what it resolved.
@Sunrisepeak Sunrisepeak changed the title recipes ask whether a header is there instead of inferring it from the platform recipes ask which C environment this is, not which operating system Sep 20, 2026
macOS is measurable now that a target with no runner compiles the member's own
tests, so it was measured: 20 of 30 members build for aarch64-macos over
openkal and 10 do not.

Nine of the ten are reached under `#ifdef __APPLE__` — TargetConditionals.h
five times, then sys/cdefs.h, sys/event.h, xlocale.h and pthread_threadid_np —
and on this target `__APPLE__` is correct. It is an Apple platform. What it
does not say is which C library is underneath, and upstream uses it to mean
both because on a real macOS the two coincide.

This is the Windows problem again with one difference: there the engine has a
lever, because presenting POSIX is realised as a cygwin triple and `_WIN32`
goes away. On macOS the realisation adds `-D__unix__` and leaves `__APPLE__`
standing, because it is true. The whole identity a source file sees there is
`__APPLE__ __MACH__ __MCPP_TARGET_MACOS__ __OPENKAL__ __unix__`, measured,
and none of it answers the question.

So the target is not pinned: it would add ten red cells whose repair is one
design question. The number is recorded so the question is asked with one.
`mcpp test --no-run` is what measures a target with no runner, and 2026.9.21.3
is the first release to carry it. `pins.toml` and the workflow's
`MCPP_VERSION` move together because the workflow checks that they agree.

`runtime` does not move with it. 0.15.0 is the graph the engine change was
measured against, and moving both at once would make a red cell ambiguous
between the two.

aarch64-macos is still not pinned, and that is a measurement rather than an
omission: 20 of 30 members build there and 10 do not, nine of the ten reaching
for the Apple C environment under `#ifdef __APPLE__` on a target where
`__APPLE__` is true and libSystem is not present. The number is in
docs/openkal-compat.md so the design question behind it is asked with one
attached.
CI measured mimalloc's Windows cell as `builds`: the backend crash is gone and
the member compiles and links, and its `alloc` test then exits 1 under the Wine
that CI installs while passing under the one on this machine. The recipe
comment claimed `runs` on the strength of the local run.

The build repair stands — the cell was `fails: error: build failed` — but a
repaired build is not a passing test, and both the recipe and docs now say
which one this is. The sixty-cell table is CI's, not mine: 56 runs, 1 builds,
3 fails, all three declared.
@Sunrisepeak
Sunrisepeak merged commit 8ae25b6 into main Sep 21, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant