You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: cxx_stdlib, the curriculum in CI, and two criteria that machine state decided (#579)
`mcpp::cxx_stdlib()` / `MCPP_CXX_STDLIB` (2026.9.6.3). The engine has resolved
this value for a long time -- it is in the cache key, the ABI tag, the toolchain
fingerprint and `resolution.json` -- and never handed it to the layer that has
to decide on it. `compiler()` cannot answer it: clang links libc++ on one
machine and libstdc++ on another and reports "clang" either way, while the two
differ in what they accept. llama.cpp-m has the case today, and its refusal
lands in the same round. e2e 617 compares the answer against `resolution.json`
and against the compiler family; removing the wiring turns it red.
The examples say which device ran them. All four islands in 09-heterogeneous
printed the same four numbers as their CPU fallback, so a silent fallback was
indistinguishable from a device run -- in the one set of examples whose subject
is which device ran the computation. The seam carries `saxpy_device_name()`,
each backend fills in its own device, and it is set only after a successful
call: a device run that did not happen has no device to report. Measured:
`device: llvmpipe (LLVM 22.1.8, 256 bits)` against
`device: cpu (this build names no accelerator)`.
CI builds the curriculum. No job did. `.github/tools/build_examples.sh`
enumerates the example ROOTS from the tree and compares them against a build
list and a skip table; a root in neither fails the job, and every skip names its
reason and where the coverage is. Six of fifteen build, including
05-lib-distribution through its own README's two-step order -- which also checks
that the ABI tag its consumer hardcodes is still the one `mcpp pack` produces.
The Vulkan example is built and RUN on the lavapipe payload.
Two e2e criteria were decided by machine state, and both were green on every
runner because a runner installs exactly one of anything.
`206_runtime_binding_physics` asserted `inconclusive` on the assumption that the
private loader cannot reach a host `libtinfo`; `xim:ncurses` is an ordinary
ecosystem package, and a sub-OS that has it puts `libtinfo.so.6` on the
artifact's RPATH, where the closure genuinely closes. It now reads the
artifact's own search path and asserts in both directions.
`168_build_mcpp_musl_host_static` selected its payload with `ls | head -1` --
lexicographic order, hence the oldest -- and on a machine with three musl
toolchains chose one that predates the `std` module. It takes the newest, which
is what resolution picks when nothing pins a version.
The four device examples move to `mcpp:plugins` 0.2.1; the SYCL one also
declares `xim:glibc` and `xim:linux-headers`, unpinned, because the device
compiler is a second compiler and does not inherit the toolchain the engine
configured.
Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
| R6 | written | a `vulkan` job on a GPU-less runner, gated on the lavapipe payload and `GGML_VK_VISIBLE_DEVICES=0`|
237
237
| R7 | done | docs/20 and docs/zh/20, "What a framework looks like on top of this" |
238
-
| R5 |blocked on release order |the index entry can only name an artefact that exists, so it follows llama.cpp-m's tag|
238
+
| R5 |done, with a second lever |mcpp-index#359 carries `b10069.1`. It also had to raise the index's CI pin: `validate.yml` was on 2026.8.27.2, ten releases behind, and this package's build program calls an accessor from 2026.9.5.2. `min_mcpp` deliberately did NOT move -- see 7e|
239
239
240
240
**The release order is the reverse of the dependency order**, as it always is
241
241
here: mcpp 2026.9.6.2 first (llama.cpp-m's CI pins it), then llama.cpp-m
242
242
`b10069.1`, then the index entry that names that tag. The index PR for
243
243
`compat:spirv-headers` went first and separately because llama.cpp-m cannot
244
244
build without it -- splitting it out was forced by the cycle, not chosen.
245
245
246
+
## 7e. The index's CI pin is not its floor
247
+
248
+
`ggml-org:llamacpp@b10069.1` failed three of mcpp-index's workspace jobs with
249
+
250
+
error: 'toolchain_sysroot' is not a member of 'mcpp'
251
+
252
+
because `validate.yml` pinned mcpp 2026.8.27.2 while that accessor is
253
+
2026.9.5.2+. Two levers exist and they govern different things:
254
+
255
+
|| governs | moves when |
256
+
|---|---|---|
257
+
|`index.toml [index] min_mcpp`| descriptor GRAMMAR -- the oldest mcpp able to resolve every descriptor | a descriptor uses a new key, in lock-step with the CI pin |
258
+
|`validate.yml MCPP_VERSION`| which mcpp the index builds its members with | the engine moves; a pin that lags validates the index against an engine no user runs |
259
+
260
+
A build program's API belongs to the second. Raising `min_mcpp` would refuse
261
+
the WHOLE index (E0006) to a client on the floor over one package's
262
+
build-program call it may never reach, which is the failure mode
263
+
`index-floor-must-degrade` names: the index is data, mcpp is the program, and
264
+
publishing data must not invalidate the program. Measured: all 218 descriptors
265
+
parse under both versions, so the grammar did not move; `b10069` stays
266
+
published for a client that cannot use `b10069.1`.
267
+
268
+
The cost is paid once per raise: the members' caches key on `MCPP_VERSION`, so
269
+
the first run after the pin moves rebuilds everything.
270
+
271
+
This is not a one-off. `b10069.2` calls `mcpp::cxx_stdlib()` (2026.9.6.3), so
272
+
the same five-step chain runs again: mcpp release, xim-pkgindex bump, package
273
+
change, package release, index bump carrying the new pin. mcpp has no
274
+
per-package engine floor, so a client on an older engine gets
275
+
`'X' is not a member of 'mcpp'` rather than a refusal that names a version.
276
+
246
277
### What the angles decide
247
278
248
279
**Architecture.** The shader pipeline is 136 edges in the build graph, not one
@@ -319,27 +350,52 @@ libraries, is closed: F1 measured 134 device objects inside `libllama.a` with
319
350
a consumer's link line, since `cudart_static` plus 186 objects is the largest
320
351
link this ecosystem has attempted.
321
352
322
-
## 7d. Open, with the reason each is open
323
-
324
-
**`mcpp::toolchain_stdlib()`.** mcpp resolves `stdlibId` and writes it into
325
-
`resolution.json`; the build-program environment does not carry it. A build
326
-
program driving a SECOND compiler may need it, and llama.cpp-m has the case
327
-
today -- `backend-vulkan` cannot compile under libc++ (upstream's own source),
328
-
and the refusal it would like to write is impossible because the only available
329
-
signal is the compiler's NAME, which would also refuse clang with libstdc++.
330
-
Same family as the two defects this round fixed. Deliberately not shipped
331
-
alone: without a consumer in the same release it is a recorded field with no
332
-
reader, which is the shape this ecosystem keeps finding.
333
-
334
-
**No CI job builds an example.** e2e 616 checks that the curriculum and its
335
-
index agree structurally, and says so: it builds nothing. The Vulkan example
336
-
needs no GPU, so it is the one that could join CI first.
337
-
338
-
**`206_runtime_binding_physics` is decided by machine state.** It asserts a
339
-
status that depends on the private loader's default prefix NOT existing, and a
340
-
leftover `fromsource-x-glibc@2.39/lib` makes it exist. Green in CI, red on a
341
-
developer machine that has one. The criterion should name the directory it
342
-
depends on rather than assume its absence.
353
+
## 7d. Closed in round 5b, and what remains open
354
+
355
+
Round 5 left four items recorded rather than done, on the stated ground that
356
+
each needed a consumer or a measurement it did not yet have. Three are now
357
+
closed; the reasons they were open turned out to be partly wrong, and saying so
358
+
is the point of writing them down.
359
+
360
+
**`mcpp::cxx_stdlib()` -- done, 2026.9.6.3.** Shipped as `MCPP_CXX_STDLIB` and
361
+
`mcpp::cxx_stdlib()`, with its consumer in the same round: llama.cpp-m's
362
+
`backend-vulkan` refuses a libc++ toolchain by name instead of handing the user
363
+
a page of errors from an upstream header. The name changed from the one
364
+
recorded here -- `cxx` is in it because `MCPP_TARGET_LIBC` is the C library,
365
+
and in an ecosystem that names glibc and musl constantly the two must not share
366
+
a word. Its criterion (e2e 617) compares the answer against `resolution.json`
367
+
and against the compiler family, and was checked by removing the wiring and
368
+
watching it go red.
369
+
370
+
**No CI job builds an example -- done.**`.github/tools/build_examples.sh`
371
+
enumerates the example ROOTS from the tree and compares them against a build
372
+
list and a skip table; a root in neither fails the job, and every skip carries
373
+
its reason and where the coverage actually is. Six of fifteen build, including
374
+
`05-lib-distribution` through its own README's two-step order, which also
375
+
checks that the ABI tag the consumer hardcodes is still the tag `mcpp pack`
376
+
produces. The Vulkan example is built AND RUN on the lavapipe payload.
377
+
378
+
That run needed one more change. All four device examples printed the same four
379
+
numbers as their CPU fallback, so a run that silently fell back was
380
+
indistinguishable from a device run -- in a curriculum whose subject is
381
+
heterogeneous compute. The seam now carries `saxpy_device_name()`, each backend
382
+
fills in its own device, and `main` prints it after the call, never before.
383
+
384
+
**`206_runtime_binding_physics` -- done, and the recorded reason was wrong.**
385
+
It was not a stale `fromsource-x-glibc` prefix. `xim:ncurses` is an ordinary
386
+
ecosystem package, and a sub-OS that has it links `libtinfo.so.6` into the
387
+
library view that IS on the artifact's RPATH; there the closure genuinely
388
+
closes and `pass` is the correct verdict. The test now reads the artifact's own
389
+
runtime search path and decides which verdict the model owes, so it fails in
390
+
both directions instead of assuming a directory is absent.
391
+
392
+
A second test had the same shape and was found while fixing the first:
393
+
`168_build_mcpp_musl_host_static` selected its musl payload with `ls | head -1`
394
+
-- lexicographic order, hence the OLDEST installed version. On a machine with
395
+
13.3.0, 15.1.0 and 16.1.0 it chose 13.3.0, which predates the `std` module, and
396
+
the error it produced described the test's own choice. It now takes the newest,
397
+
which is what resolution picks when nothing pins a version. Both were green on
398
+
every CI runner, because a runner installs exactly one of anything.
343
399
344
400
**The four examples' CPU fallback does not generalise.** Each writes
345
401
`cfg(not(accelerator = "<its own>"))`, which is correct for one backend and
# Build every example in `examples/` that this runner can build.
3
+
#
4
+
# WHY THIS EXISTS. Until it did, no CI job built any example. e2e 616 checks
5
+
# that the curriculum and the table documenting it agree structurally, and says
6
+
# so explicitly: it builds nothing. Every example here could have stopped
7
+
# compiling and the first report would have come from a reader.
8
+
#
9
+
# THE LIST IS DERIVED FROM THE TREE. `BUILD` and `SKIP` are compared against
10
+
# the roots actually on disk, and a root in neither list fails this script.
11
+
# That is the denominator: adding an example forces a decision about whether CI
12
+
# can build it, instead of leaving it silently uncovered.
13
+
#
14
+
# A SKIP CARRIES ITS REASON AND ITS ELSEWHERE. "CI cannot build this" is only
15
+
# acceptable when something else does, and each entry says what.
16
+
set -uo pipefail
17
+
18
+
MCPP="${MCPP:?set MCPP}"
19
+
cd"$(dirname "$0")/../.."
20
+
21
+
BUILD=(
22
+
examples/01-hello
23
+
examples/02-with-deps
24
+
examples/03-pack-static
25
+
examples/04-workspace
26
+
examples/08-build-rules/app
27
+
)
28
+
29
+
# `key|reason`.
30
+
SKIP=(
31
+
"examples/05-lib-distribution/producer|packed and then consumed by the dedicated step below, which is the order the example's own README gives"
32
+
"examples/05-lib-distribution/consumer|same"
33
+
"examples/06-openkal-cross|cross-builds to a second target; the payload matrix is what the target-matrix workflow already covers, and repeating it here would double a long job for no new signal"
34
+
"examples/07-project-subos|provisions a project-local sub-OS, which e2e 27_self_contained_home covers directly and far more cheaply"
35
+
"examples/08-build-rules/rules-embed|a rule package is not a standalone build: its interface imports the bundled mcpp module, which exists only inside a consumer's build. Verified by building 08-build-rules/app, the same way mcpp-plugins verifies its own members"
36
+
"examples/08-build-rules/rules-tidy|same"
37
+
"examples/09-heterogeneous/cuda/app|needs the CUDA payload set, and a device to run; the rule package is covered by mcpp-plugins' own CI"
38
+
"examples/09-heterogeneous/hip/app|same, for the HIP payloads"
39
+
"examples/09-heterogeneous/sycl/app|needs the dpcpp payload (over a gigabyte) and a device its runtime accepts"
40
+
"examples/09-heterogeneous/vulkan/app|built AND RUN by the next step of this job, on the lavapipe payload, which needs no GPU"
41
+
)
42
+
43
+
# Every ROOT manifest in the tree: a directory with an `mcpp.toml` that has no
44
+
# ancestor manifest below `examples/`. A workspace member is not a root.
0 commit comments