Skip to content

2026.8.26.1 — naming the host's own target changed the link line - #511

Open
Sunrisepeak wants to merge 29 commits into
mainfrom
docs/declared-but-not-made-to-exist
Open

2026.8.26.1 — naming the host's own target changed the link line#511
Sunrisepeak wants to merge 29 commits into
mainfrom
docs/declared-but-not-made-to-exist

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Aug 25, 2026

Copy link
Copy Markdown
Member

写出 --target 这个动作,曾被当成「这个构建的系统来自依赖图」。

$ mcpp build                            → ELF 64-bit LSB pie executable
$ mcpp build --target x86_64-linux-gnu  → hermetic link check failed

同一台机器、同一个编译器、同一个目标 —— 不带 --target 时宿主目标就是
x86_64-linux-gnu

分析:.agents/docs/2026-08-26-cross-target-implies-graph.md
(§6 是实测回填),六张矩阵表见
2026-08-26-target-matrix-six-tables.md

修复

① 同一个错问题被问了三遍。 crossTarget 只是 --target=<三元组> 这个字符串,
任何命名目标都非空 —— 包括命名宿主自己、且完全不依赖任何包的工程。三处
(link_toolchain_flags / payload_ld / atomic_ld)全部改问
targetSide.cAbi.prebuilt(),与 2026.8.25.1 迁走另外三处决定的同一个谓词

⚠️payload_ld 那处的注释只预告了条通道:

the C-runtime group reaches the link line through TWO channels, and a reader
who fixed one saw the identical error and could reasonably conclude the fix
had not worked.

② 第四条通道:mingw 分支发出的链接线里没有 --target= 于是一个完全合法的
Intel amd64 COFF 被交给 ELF 版的 lld:ld.lld: error: obj/main.o: unknown file type。它的注释写明了前提(x86_64-w64-mingw32-g++ 不需要 --target 因为它没有
别的),而条件没有检查这个前提 —— clang 有别的。

③ 目标行声明的 sysroot 从不被安装(#510)。 pinautoInstall,sysroot
是纯查询,查不到就静默跳过整块。mcpp 自己的裸机 CI 手工装它,所以每条裸机 e2e
都跑在缺陷已被抹平的机器上。

④ 能力 pin 不可被推翻,而它不止裸机一行。 x86_64-windows-musl 同理:没有
任何 gcc 载荷发得出 PE + musl。声明 gcc 会解析到宿主的 Linux musl 载荷 ——
真因是 t.is_musl() 被当成「这是 linux-musl」用,而那段注释自己写的就是 linux-musl。
收敛到 triple::pin_is_capability()

⑤ 推翻约定而不接替它。 一行有宿主的目标,其 pin 说的是「哪个载荷供给这个目标
的 C 库」;作者自带就该让位 —— 但自带的必须存在。此前这会跑完整个构建然后死在
链接上,报 crtbeginT.o (bare name),或者(在恰好装了系统 mingw 的机器上)够到
/usr/lib/gcc/x86_64-w64-mingw32/…,那比失败更糟。

机器接口

⭐⭐ 两条命令进入 --format json,矩阵与四条 e2e 不再匹配任何一句话。

mcpp toolchain list --format json                         → mcpp.toolchain.list
mcpp why toolchain --target T --toolchain C --format json → mcpp.why.toolchain

后者只解析不构建,给出五层、驱动器、三元组、C 库模型,以及 status
reasonreason 是一个记号,由新模块 mcpp.build.refusal 在每一处拒绝的
return 之前记下。

⚠️ 代价是当场量到的:本次工作里我把一句拒绝从 cannot emit it 改成
cannot be emitted by,e2e 297 的断言随即变成空转 —— 它仍然「通过」,只是不再
匹配任何东西。消息仍然是承诺(点名目标、规则与出路,e2e 照旧断言);换掉的是
分类

⚠️查询不能取代构建:llvm × x86_64-windows-gnu 解析得完全正常,失败在
链接期的封闭性检查上。矩阵两样都做。

测试与 CI

  • e2e 295 是一条恒等式:mcpp buildmcpp build --target <宿主自己的目标>
    描述同一次构建,链接线必须逐 flag 相同。它不需要期望表,任何宿主都成立 ——
    ⭐ 差异 7 项 → 5 项 → 3 项 → 0,每修一处它就指向下一处

  • e2e 296 / 297 / 298,各自两向。

  • 新增 ci-target-matrix.yml,三个宿主 × 两层:恒等式 + 全表扫描与
    tests/matrix/expected.tsv 比对。

    ⚠️ 期望表里没有 mismatch,这是刻意的。 写下它就是把缺陷声明成期望。
    一格测出 mismatch,要么修 mcpp,要么让 mcpp 在决定处给出带 reason 的拒绝。

    ⚠️ 比对脚本先断言扫描真的跑了 —— 一格没跑与全部通过,在退出码上没有区别。

本机实测

  • 单测 93 passed / 0 failed
  • 矩阵 40 格(payload 24 + graph 16),0 个 mismatch
status reason 格数
ok none 14
unsupported tier-planned 12
unsupported capability-pin 6
unsupported layer-requirement 4
unsupported convention-unreplaced 3
unsupported host-cannot-serve 1

⚠️ windows / macos 的期望行留待各自宿主的第一次 CI 运行回填 —— 从别的宿主推断出来
的一行,断言的是推断而不是那台机器。所以这两个 scan job 的第一次运行会红

Closes #510

…nd a measured docs audit

The other end of the same family: the first seven were a predicate asked
the wrong question; this one is an answer never delivered. A target row
declares its C library exactly as it declares its compiler, and only the
compiler is installed — the absence is skipped without a word and the build
fails a hundred lines later naming a header.

Includes a measured audit of every `--target` spelling in the repository's
docs. The three commands in examples/06-openkal-cross/README.md all still
work: short names and GNU spellings are both accepted and normalised.
Every cell was built, not reasoned about. The matrix is not two-dimensional:
target × compiler × who-supplies-the-system, and the third axis changes what
the first two mean — `x86_64-windows-gnu` has the MinGW CRT under a payload
and musl under a graph, one name and two C libraries.

Four cells need work and one is an ecosystem gap. The macOS and Windows
host columns are empty and are marked as such; the closing recommendation
is to make this sweep a script that runs on all three, so the matrix stops
being a document that can quietly go stale.
The second finding was measured by running `clang++ -print-search-dirs`
directly. That is not mcpp's build line — going around the thing under test
to ask one of its components returns the component's default behaviour, not
what mcpp made it do.

The criterion is what mcpp emits: the build report, and the flags in the
generated build.ninja. Under that criterion the finding is sharper — gcc
needs mcpp to say nothing because its driver carries the target sysroot,
while clang switches targets with `--target=` and needs one supplied, and
mcpp supplies an empty ldflags with no -B, -L or --sysroot.

Also states the axes in full: the build machine is one of them, and this
sweep had exactly one.
The English section landed without its Chinese counterpart and the style
check caught the heading parity.
Ten columns, three build machines, both systems, kept apart. Rows that are
not supported say so and say why; every cell is either measured on this
host or marked as derived from source, so the derived ones can be argued
with.

Finding A is sharper than the previous draft said. It is not a missing
sysroot: xim:glibc is installed and carries Scrt1.o, crti.o and crtn.o,
and gcc's link line reaches for it — `-L`, `-rpath`,
`--dynamic-linker` all pointing into that payload. llvm's link line has
none of them. The same payload, connected on one branch and not the other.
Three cells were marked as failures that are simply not built yet. The
distinction is whether the payload exists:

  llvm × x86_64-linux-gnu   xim:glibc is installed, carries Scrt1.o/crti.o/
                            crtn.o, and gcc's link line reaches for it while
                            llvm's does not — same payload, one branch wired
                            and the other not. A defect.
  llvm × x86_64-linux-musl  xim:musl is in the index; mcpp never installs it
                            for llvm. Not built yet.
  llvm × aarch64-linux-musl llvm's payload has no such sysroot directory.
  llvm × x86_64-windows-gnu no MinGW sysroot package exists at all.

What remains a defect in the last one is the diagnostic: it says
`unknown file type` where the cause is a missing sysroot, while the same
class of problem on x86_64-linux-gnu produces an accurate hermetic report.
Spelling `--target` was read as "this build's system comes from the graph".
`crossTarget` is the string `--target=<triple>`; it is non-empty for ANY named
target, including the host's own on a project that depends on nothing.

    $ mcpp build                            → ELF 64-bit LSB pie executable
    $ mcpp build --target x86_64-linux-gnu  → hermetic link check failed

The same wrong question was asked in three places, and the comment beside one
of them predicted TWO channels. All three now ask `targetSide.cAbi.prebuilt()`
— the predicate 2026.8.25.1 moved three other decisions onto.

A fourth channel: the mingw branch returned a link line with no `--target=` at
all, so a correct COFF object was handed to the ELF flavour of lld. Its own
comment stated the precondition the condition did not check.

Also fixed: a target row's declared sysroot was never installed (#510); a
bare-metal row's toolchain pin was overridable (it is a capability, not a
convention), and so is `x86_64-windows-musl`'s — no gcc payload emits PE+musl,
and declaring one resolved the host's Linux musl payload; overriding a hosted
row's convention with nothing in its place ran the whole build and failed at
the link.

Machine interface: `toolchain list` and `why toolchain` now speak
`--format json`. `why toolchain` resolves without building and reports the five
layers, the driver, the triple, the C-library model, and `status`/`reason` —
`reason` being a token from the new `mcpp.build.refusal`. The target matrix and
four e2e tests classify from it, so no criterion in this repository is a
substring search any more. Measured cost of the old way, in this same session:
rewording one refusal turned an assertion into a no-op.

Tests: e2e 295 is an identity (naming the host's own target must not change one
flag) and it counted the channels down 7 → 5 → 3 → 0; 296, 297, 298; and
ci-target-matrix.yml scans the full table on three hosts against
tests/matrix/expected.tsv, which carries no `mismatch` rows by construction.
@Sunrisepeak Sunrisepeak changed the title docs(analysis): declared but not made to exist 2026.8.26.1 — naming the host's own target changed the link line Aug 25, 2026
…ry host's

macos-14, measured:

    c-abi     libSystem   (payload)
    ldflags = -isysroot /Applications/Xcode_15.4.app/…/MacOSX.sdk

— and that is correct. Darwin's C library is the SDK's libSystem and the SDK
belongs to the machine, not to a payload; 296 hardcoded `registry/data/xpkgs`
and called its absence a defect.

It now asks the query for `cLibrary.path` — the value `resolve_link_model`
produced, which is the same function the flag emitter calls — and requires it on
the link line. What mcpp says it will pass is what mcpp passes, on any host.

297 declares a non-llvm compiler, and every toolchain mcpp installs on macOS is
llvm, so there the distinction has nothing to distinguish. The exemption is per
host and written down, rather than accepting "a skip is a conclusion"
everywhere — which is what this step exists to prevent.
…tion got its own field

On an installed row it is the payload that is here; on a vocabulary row it is
the target table's convention pin. A row can have the first and no second —
`x86_64-linux-gnu` has an installed gcc and no convention at all.

That is acceptable for a column a person reads and wrong for a field a program
reads. Measured on ubuntu-24.04: e2e 298 selected "a row whose convention is a
gcc" from `toolchain`, got `x86_64-linux-gnu`, and demanded a refusal that
correctly did not happen. Locally the first matching row was
`aarch64-linux-musl`, which does have one, so it passed.

Same defect family as the release this interface was added for, introduced in
the interface itself.

Also: docs/03 now states the convention/capability distinction, in both
languages — including which two kinds of row cannot be overridden and why.
… read a sink it had not cleared

Two review findings on the machinery added in this branch.

`MCPP_TOOLCHAIN` was set and never restored. Harmless as the last thing a
process does and wrong for a library function — the next thing this process did
would inherit a compiler nobody asked it for. `ScopedEnv` already exists for
exactly this.

The refusal sink is per-thread and `prepare_build` recurses for tool
provisioning, so reading it after the call without clearing first could report a
code recorded by an earlier query as this one's reason. A stale reason is worse
than none: it is a specific, plausible, wrong answer.
…ws-2022 said so

295 named `data.host` and called it "the target this build would use anyway".
On Linux those coincide for every family and the assumption stayed invisible; on
Windows the host target is `x86_64-windows-msvc` and a mingw gcc targets
`x86_64-windows-gnu`, so the identity asked gcc to emit MSVC and reported the
refusal as a defect. It now asks the query which triple THIS toolchain resolves
to with no target named, which is exact on every host.

296 skipped whenever the query named no C-library path, and on windows-2022 both
toolchains took that branch — relation one had no coverage there at all. No path
is also a claim: the self-contained arrangement must bring in nothing external
either, which is checkable and is what the branch now asserts.

⚠️ Both spellings of the flag exist. `-B/usr/lib` is one word, `-B /usr/lib`
is two; matching `^-B` flags the bare `-B` of the second form, and requiring
a path inside the token makes the second form invisible. Measured both while
writing the line. A false negative is the worse failure for a leak check, so the
two forms are joined before anything is decided.

297's second half named `x86_64-linux-gnu` — a hosted row on Linux and a cross
row on Windows. It now uses the row that toolchain would use anyway.

298 selects the host's own architecture when a matching row exists. ⚠️ The first
version of that comment said the cross row was expensive to build; measured, the
whole invariants step is 85 seconds either way. The reason is
representativeness, and a reason written from a guess is the kind that later
talks someone out of a correct change.
…e one host where it mattered

windows-2022: relation one printed both its `ok` lines and the script stopped.
No relation two, no conclusion, no error.

`build_and_read` ended on `[ -n "$f" ] && ldflags=…`. With no build.ninja the
whole chain returns 1, that becomes the function's exit status, and under
`set -e` the caller dies — for exactly the case the function exists to handle.
The graph build legitimately produces no link line on Windows and should have
reached the SKIP branch twenty lines below.

⭐ 295's `ldflags_of` had the identical line and was fixed with the identical
comment earlier in the same session. Fixing one instance of a shape is not
fixing the shape.

⚠️ And the shape is narrower than it looks. Measured: a FUNCTION ending in
`[ … ] && …` kills its caller; the same list ending a `{ …; } > file` group
does not, because errexit exempts a `&&` list whose failure came from a
non-final component. 290, 292 and 293 all end such a group that way and are all
called with an empty second argument — they are correct as written, and
"fixing" them from a grep would have changed three working tests.
The first version let macOS skip 297 because every toolchain mcpp installs there
is llvm. The very next windows-2022 run skipped for the same reason and went red:
one run's restored cache held `gcc@16.1.0`, the next held only `llvm@20.1.7`.

Naming the host encodes where the fact happened to hold. The skip is now accepted
for a named REASON — `gcc is not installed here` — on any host, and a skip for
any other reason is still a failure.

⭐ And the denominator is asserted separately: linux always has a gcc payload
because it backs the host row, so that job keeps the strict check and the test is
guaranteed to run somewhere on every push. A reason accepted everywhere, with
nothing requiring it to be exercised anywhere, is a test that runs nowhere.
…xist at all

The two axes the chapter already had — which compiler, and where the C library
comes from — are choices a project makes. The machine the build runs on is not,
and `mcpp toolchain list` reports only the rows it can reach: 12 of the target
table's 14 on Linux, with `x86_64-windows-msvc` and `aarch64-macos` correctly
absent.

States the rule in six lines, gives the full 14-row table with a column per
host, and names which CI runner is which — including that `macos-14` resolves
`arm64-apple-darwin23.6.0`, ARM rather than x86_64, which is the fact three of
this branch's criteria assumed away.

⚠️ And says why the cell count is not a constant: it follows from what that
machine has installed, and the same runner has been measured with different
toolchains on consecutive runs. The comparison therefore asserts that the scan
produced rows and that every row the expected table names was reached — a cell
that vanishes because a payload was not restored is otherwise indistinguishable
from a cell that passed.
… it is (os, arch)

The axis was three OS names, chosen because three runners were at hand.
`release.yml` publishes four host binaries — linux-x86_64, linux-aarch64,
macosx-arm64, windows-x86_64 — and a host mcpp is distributed for but never
scanned is a host whose target table is a claim nobody checked. aarch64 Linux has
been supported since before this branch and had no target-matrix coverage.

⚠️ And the key is (os, arch), not os. The two Linux hosts do not serve the same
rows: `x86_64-linux-gnu` needs the host-native `xim:glibc` and
`xim:linux-headers` payloads, which exist for the host's own architecture only,
so it is reachable from one and not the other. Under a single `linux` key the
second scan to finish would explain away the first one's rows, and which one wins
depends on scheduling rather than on correctness.

`ubuntu-24.04-arm` is the runner; `ci-aarch64-fresh-install.yml` has used it
since it shipped.

docs/16 now carries the full 14-row table with a column per build host, the rule
behind the columns, and which runner resolves which host target — including that
`macos-14` is ARM and `windows-2022` resolves `-msvc` while a mingw gcc
there targets `-gnu`. Three of this branch's criteria assumed the Linux
coincidence and had to be corrected on the other hosts.
… scanned

Every check in this workflow is per host, and no per-host check can notice a host
that never ran. Deleting a host from the matrix silently retires every
expectation the table holds for it: nothing measures those rows, nothing compares
them, and the workflow is green.

The new `coverage` job reads the expected table as a whole and requires the run
to have covered it — both directions. A host with rows and no scan is named; a
host that scanned and has no rows is named as a new build host nobody declared
expectations for, which is a cause rather than 40 unexplained cells.

⭐ And it ties the axis to the release. The set of build hosts is DERIVED from
`release.yml`'s asset names rather than restated, so adding a fifth published
host fails this step until the matrix covers it. A host that gets a binary and no
scan is a host whose target table is a claim nobody checked; a host scanned and
never shipped is coverage spent on a machine no user has.

⚠️ It reds right now, by design: the release publishes four and expected.tsv
holds one. The other three backfill from their first scan artifact.
`bootstrap-mcpp` branched on `uname -s` alone and fetched
`xlings-<v>-linux-x86_64.tar.gz` for every non-Darwin host. Invisible until a
Linux runner is not x86_64 — measured the first time this repo scheduled
`ubuntu-24.04-arm`:

    .../xlings-2026.8.17.2-linux-x86_64/subos/default/bin/xlings:
      cannot execute binary file: Exec format error
    Process completed with exit code 126

xlings has published `linux-aarch64` all along; nothing here asked for it.
`ci-aarch64-fresh-install.yml` never hit this because it bootstraps through
`quick_install.sh`, which does read the architecture.

⚠️ The other `linux-x86_64` spellings in .github are on jobs pinned to
`ubuntu-24.04`, including release.yml's `build-linux-aarch64`, which
cross-builds under qemu on an x86_64 host. Naming the architecture there is a
statement about the runner, not an assumption about it — checked before
touching them.

⭐ Found by adding the aarch64 build host to the target matrix. Four hosts get a
published binary and only three were ever exercised by this action.
…for another

Reading `uname -m` to pick the right xlings tarball was correct and was not
enough. The download never happened:

    Cache hit for: xlings-Linux-v2-xl2026.8.17.2-…
    …/subos/default/bin/xlings: cannot execute binary file: Exec format error

These caches hold installed BINARIES — `~/.xlings`, `~/.mcpp`, `target/` —
and `runner.os` alone names two machines. `ubuntu-24.04-arm` restored what
`ubuntu-24.04` had stored, and the fix one layer down never ran.

`runner.arch` now appears in all three keys and their restore-keys.

⚠️ The remaining OS-only keys are in cross-build-test.yml and release.yml, where
each OS appears on exactly one runner architecture — checked job by job before
leaving them. `ci-target-matrix.yml` is the first workflow in this repository to
put two architectures of one OS in the same matrix, and it is why this surfaced
now rather than as a wrong artifact later.
Fetching the right architecture got as far as `No such file or directory`,
exit 127. Measured on the 2026.8.17.2 assets:

    linux-x86_64   subos/default/bin/xlings   513 entries
    linux-aarch64  bin/xlings                 494 entries

The path is now found rather than named, bounded to depth 5, and an empty result
is a hard error — a bootstrap that continues without xlings fails forty lines
later on something that reads like a different problem.

⚠️ Checked before changing the three hosts that already worked: on x86_64 the
find resolves `bin/xlings` where the line before named
`subos/default/bin/xlings`, and those are the same file — the second is a
symlink to the first, md5 94b8e2f2… for both. A find that quietly moved macOS
and Windows onto a different binary would have been worse than the defect it
fixed.
… which repository

mcpp publishes a linux-aarch64 binary, and once it is there the only things that
work are `musl-gcc` and `ninja`. Every other host-code package — llvm, glibc,
linux-headers, zlib, libxml2, gcc-runtime, mingw-cross-gcc — ships x86_64 only.

Surveyed and tabulated across four repositories, with the distinction the whole
plan turns on: host code needs a per-host-arch build, target code does not.
`picolibc-riscv` declares both arches and needs neither, because one archive of
target code serves every host; `glibc` and `linux-headers` look like host
packages and are the target's C library, which is a different piece of work.

⚠️ Upstream LLVM stopped publishing linux-aarch64 after 19.x, and mcpp pins
20.1.7 / 22.1.8 with `import std` — so this has to be built rather than
repackaged. Two shapes are set out; static-musl is recommended because it takes
the dependency chain from five packages to none, and because `musl-gcc` — the
one toolchain that works on that machine today — is already that shape. Its
first criterion is whether libc++'s std module survives it.

⚠️ And a warning against the obvious mcpp-side shortcut: do not copy the index's
architecture coverage into mcpp. That table becomes wrong on the day the llvm
build lands, and nothing would say so.
…nd the step said no

`ubuntu-24.04-arm`, with the bootstrap now working end to end:

    295  ok   gcc@16.1.0: naming aarch64-linux-musl changes nothing
    296  ok   the C library the query names is on the link line
    297  ok   ok   ok
    298  SKIP: llvm is not installed here

That skip is correct and is the ecosystem gap itself: upstream LLVM stopped
publishing linux-aarch64 after 19.x, mcpp pins 20.1.7 / 22.1.8, and the index has
no aarch64 llvm. 298 declares llvm, so on that host it has nothing to declare.

The exemption is granted by REASON, mirroring 297's, with linux-x86_64 keeping
the strict check as the denominator. ⭐ Granting it that way makes it retire
itself: the day an aarch64 llvm lands, this stops being a skip and becomes the
assertion, with nothing here to change.

⚠️ Note what 295 measured on that host — the target it resolves is
`aarch64-linux-musl`, not `aarch64-linux-gnu`. mcpp already routes gcc to the
musl payload on non-x86_64 Linux, and the identity holds there.
…erred in the open

`available_toolchain_indexes()` branched per OS and never per architecture, so
an aarch64 Linux host was told llvm could be installed. Measured against the
index and upstream on 2026-08-26:

    xlings-res/llvm 20.1.7 / 22.1.8   no linux-aarch64 asset
    llvm/llvm-project 20.1.7, 21.1.0  no linux-aarch64 asset
    llvm/llvm-project 19.1.7          has one, too old for `import std`

so `mcpp toolchain install llvm 22.1.8` there is a 404 the list promised would
work. `mingw-cross-gcc` is x86_64-only for the same reason and gets the same
gate. What remains on that host is `musl-gcc`, which mcpp already routes gcc to
on non-x86_64 Linux, and `ninja`.

⭐ This is a POLICY statement — which families mcpp supports on this host, the
same kind of statement `tier` makes for a target row — not a copy of the index's
coverage. The plan that retires it is
.agents/docs/2026-08-26-aarch64-linux-ecosystem-closure.md §P1.

⚠️ AND ITS PREMISE IS RECHECKED EVERY RUN. `check_aarch64_llvm_deferral.sh`
fails when its reason stops holding — the day an aarch64 llvm is published — and
names the gate to remove. A deferral nobody rechecks is indistinguishable from a
defect. Network trouble is not read as "it appeared": an unreadable asset list
leaves the premise alone and says so.
…r gtimeout

macos-14, the first run that reached this script: all 20 cells came back
`mismatch / query-failed`. `mcpp toolchain list`, which the script does not
wrap, worked — which is how the wrapper was identified as the difference.

⭐ `tests/e2e/run_all.sh` has had `timeout` → `gtimeout` → none detection
since it shipped. The defect was not that the problem is hard; it is that a
second copy of a decision was written without looking at the first. No timeout
command is a legitimate state here: the workflow's `timeout-minutes` is the
backstop and running unwrapped beats not running.

⚠️ AND THE EVIDENCE WAS BEING THROWN AWAY. The query ran under `2>/dev/null`, so
`query-failed` was the right classification with nothing to say why — twenty
identical cells and no cause anywhere in the log. stderr now goes to a file and
the first two lines are printed beside the failing cell.

That redirect is the same shape as the classification it defeats: a cell that
cannot be explained and a cell that is unsupported were being written the same
way.
… about which hosts it ships for

Two host_can_serve branches assumed x86_64, and two new build hosts each found
one of them.

ubuntu-24.04-arm, `--target x86_64-linux-musl`: admitted because
`target.is_musl()` was read as "any Linux arch can reach it". A musl payload
really does carry its own sysroot — and it still has to EXIST for the host
running it. The cross packages are published per host arch:

    x86_64-linux-musl-gcc    archs = { "x86_64" }
    aarch64-linux-musl-gcc   archs = { "x86_64", "aarch64" }

so the row resolved a package with no aarch64 asset and failed at install —
`mismatch / build-failed`, twice. ⭐ The native row stays reachable on every
arch, which is why `aarch64-linux-musl` is `ok` there and is the whole of
"aarch64 supports musl-gcc".

Same host, `--target x86_64-windows-gnu`: `mingw-cross-gcc` is
`archs = { "x86_64" }` too. Its refusal carried no reason at all —
`unsupported / other` — which is exactly what that token was added to make
visible, and it named an uncoded branch on its first run.

⚠️ And `build-failed` was throwing its evidence away, the same shape as the
`query-failed` redirect fixed one commit ago. The failing cell now prints two
error lines beside itself; the full log is not dumped, because 40 cells of
complete build output is not something anyone reads.

Local: 4 e2e green, 40 cells match — both gates are the identity on x86_64.
The scan reached all four build hosts and produced six `mismatch` cells. The
evidence capture added last commit paid for itself immediately — all six causes
came out of one query.

⭐ THE MACHINE INTERFACE DID NOT ROUND-TRIP. `toolchain list --format json`
reported `msvc@19.44.35228`, which is cl.exe's version, and
`--toolchain msvc@19.44.35228` is refused by design — "names a COMPILER
version, not a toolset". A consumer reads `family` and `version`, joins them,
and hands the result back; scan.sh did exactly that and every msvc cell came
back with mcpp rejecting its own output. A system toolset is `msvc@system`; the
display number keeps a field of its own. e2e 296 now asserts the round-trip for
every family on every host.

⚠️ A TARGET NO PAYLOAD HERE SERVES WAS STILL HAVING ITS PAYLOAD INSTALLED.
`unservedTargetDiagnosis` is decided early and released late, deliberately —
whether the graph supplies the system is not knowable until it is resolved — and
the install sits between the two. On ubuntu-24.04-arm it failed first and hard:
`xlings install of 'xim:x86_64-linux-musl-gcc@16.1.0' failed`, the cross-musl
packages being published per host arch. Skipping the install leaves both later
paths intact; attempting it cannot help either.

⚠️ AND I TRUNCATED MY OWN EVIDENCE. The `build-failed` line was cut at 160
characters, so the macOS cell read `precompiled file '/private/var/…/target/`
and stopped exactly before the part worth reading. Three lines, whole lines.

⚠️ THE CELL COUNT WAS A FUNCTION OF CACHE STATE. Same ubuntu-24.04 runner: one
run had gcc+llvm and scanned 40 cells, the next had gcc alone and scanned 20.
The matrix now installs the toolchains its expected table names, and fails there
if one cannot be installed, rather than reporting a screen of "never reached".
…e ran the scan

⚠️ `--rtlib=compiler-rt` IS A CODEGEN FACT ON aarch64, AND IT WAS DECLARED ON A
KEY THAT REACHES ONE SIDE. macos-14 building `aarch64-linux-musl` over the graph:

    error: precompiled file 'std.pcm' was compiled with the target feature
           '+outline-atomics' but the current translation unit is not

`openkal-llvm-runtime` put it in `std-module-flags`, which reaches the std
module's command and no consumer's TU. ⭐ Same defect as `-fdwarf-exceptions`,
one flag later — hostflags.cppm describes that one as "its objects agreed with
each other and nothing else did", and the answer then was the same as now: a
property of the graph cannot be declared by one package for one command.
x86_64 lists no such feature on either side, so it was invisible until a second
architecture was built.

⚠️ PE + musl HAS NO PAYLOAD ON ANY HOST, AND `host_can_serve` SAID OTHERWISE ON
EXACTLY ONE. windows-2022, payload system:

    c-abi    musl(payload)
    c++-abi  msvc-stl(payload)
    lld-link: error: undefined symbol: __mingw_vfprintf

musl's C library, MSVC's STL and MinGW's CRT symbols in one link.
`pin_is_capability()` and chapter 16 both already say the row is graph-only;
Linux had been answering `host-cannot-serve` all along.

Local: 4 e2e green (296 now 8 relations, including the round-trip), 40 cells
match, and aarch64/x86_64/windows-gnu all build over the graph.
…refusal

    payload  windows-x86_64  aarch64-linux-gnu^M  llvm@22.1.8  …  unsupported  other

git-bash runs jq with stdout in text mode, so `\n` becomes `\r\n`, and `\r`
is not in IFS — every word keeps one. `--target "aarch64-linux-gnu\r"` does not
parse, and the whole host was scanning a corrupted target list.

⭐ `other` earned its place again. Thirty-eight identical unnamed refusals are
impossible to read as anything but a systemic fault; had the uncoded branch been
folded into a neighbouring reason, that host would have looked plausibly
"mostly unsupported".

⚠️ AND THE POISONED ROWS NEARLY BECAME THE EXPECTED TABLE. Backfilling all four
hosts, 124 measured rows produced 80 — the 44 Windows rows whose target name
carried a CR were dropped by the field-count filter, and the 4 that survived
would have been declared as fact and gone green for ever. Row counts were
checked; Windows is now marked as awaiting a clean measurement.

⭐ The strip is at the point of reading — one `jq_r` helper — not at each use.
The latter is the same decision written N times, which this branch has already
paid for four times today.

The other three hosts are backfilled from their own machines: linux-x86_64 40,
linux-aarch64 16 (musl-gcc only, llvm deferred), macos-arm64 20.
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.

a target row's sysroot is declared but never installed

2 participants