Skip to content

Commit 20f4de3

Browse files
committed
fix(wire): a target row's toolchain meant two things, so the convention 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.
1 parent a8bb3a3 commit 20f4de3

4 files changed

Lines changed: 101 additions & 4 deletions

File tree

docs/03-toolchains.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,41 @@ toolchain = "gcc@16.1.0"
417417
linkage = "static"
418418
```
419419

420+
### A convention may be overridden. A capability may not.
421+
422+
The pin on a **hosted** row answers *which payload supplies this target's C
423+
library*, so a project that supplies one itself may name any compiler — that is
424+
the escape hatch the whole openkal ecosystem is built on. What it may not do is
425+
name a different compiler and supply nothing:
426+
427+
```
428+
$ mcpp build --target x86_64-linux-musl # [toolchain] default = "llvm@…"
429+
error: target 'x86_64-linux-musl' takes its C library from the 'gcc@16.1.0'
430+
payload, and 'llvm@22.1.8' has none here.
431+
```
432+
433+
Two rows answer a different question, and their pin cannot be overridden at all:
434+
435+
| row | why |
436+
|---|---|
437+
| every `*-none-elf` | no per-host cross payload exists; clang and lld are cross-compilers by construction and gcc is not |
438+
| `x86_64-windows-musl` | no gcc payload emits a PE with a musl C library — the mingw payload emits PE with the MinGW CRT, which is the separate `-gnu` row |
439+
440+
```
441+
$ mcpp build --target riscv64-none-elf # [toolchain] default = "gcc@…"
442+
error: target 'riscv64-none-elf' cannot be emitted by 'gcc@16.1.0'.
443+
```
444+
445+
**Both refusals are decided where the decision is made**, not left to the
446+
compiler. Before 2026.8.26.1 the first ran the whole build and died at the link
447+
on `crtbeginT.o (bare name)`, and the second produced
448+
`g++: error: unrecognized argument in option '-mabi=lp64d'` — a message about an
449+
option, for a decision made a hundred lines earlier.
450+
451+
A program classifying these reads `data.reason` from
452+
`mcpp why toolchain --format json` (`convention-unreplaced` / `capability-pin`)
453+
rather than the sentence — see [chapter 11](11-machine-output.md).
454+
420455
A project can set its *default* build target — this is where "this project
421456
ships fully-static" belongs (static output is a product property, not a
422457
compiler-family property):

docs/zh/03-toolchains.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,39 @@ toolchain = "gcc@16.1.0"
386386
linkage = "static"
387387
```
388388

389+
### 约定可以被推翻,能力不行
390+
391+
**有宿主**那一行的 pin 回答的是*哪个载荷供给这个目标的 C 库*,所以一个自己供给
392+
C 库的工程可以写任何编译器 —— 整个 openkal 生态就建立在这道口子上。不能做的是
393+
写另一个编译器而什么都不供给:
394+
395+
```
396+
$ mcpp build --target x86_64-linux-musl # [toolchain] default = "llvm@…"
397+
error: target 'x86_64-linux-musl' takes its C library from the 'gcc@16.1.0'
398+
payload, and 'llvm@22.1.8' has none here.
399+
```
400+
401+
有两类行回答的是另一个问题,它们的 pin 根本不可被推翻:
402+
403+
|| 为什么 |
404+
|---|---|
405+
| 所有 `*-none-elf` | 不存在按宿主分的交叉载荷;clang 与 lld 按构造就是交叉编译器,gcc 不是 |
406+
| `x86_64-windows-musl` | 没有任何 gcc 载荷发得出 PE + musl —— mingw 载荷发的是 PE + MinGW CRT,那是隔壁 `-gnu` 那一行 |
407+
408+
```
409+
$ mcpp build --target riscv64-none-elf # [toolchain] default = "gcc@…"
410+
error: target 'riscv64-none-elf' cannot be emitted by 'gcc@16.1.0'.
411+
```
412+
413+
**两处拒绝都发生在做出决定的地方**,而不是留给编译器。2026.8.26.1 之前,前者
414+
会跑完整个构建然后死在链接上,报 `crtbeginT.o (bare name)`;后者给出
415+
`g++: error: unrecognized argument in option '-mabi=lp64d'` —— 一条关于选项的
416+
消息,而决定在一百行之前。
417+
418+
要给这些结果分类的程序读 `mcpp why toolchain --format json``data.reason`
419+
(`convention-unreplaced` / `capability-pin`),而不是那句话 ——
420+
[第 11 章](11-machine-output.md)
421+
389422
项目还可以声明自己的*默认*构建 target——"本项目发布全静态"这类语义
390423
就该放在这里(全静态是产物属性,不是编译器家族属性):
391424

src/toolchain/lifecycle.cppm

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,20 @@ export int toolchain_list(const mcpp::config::GlobalConfig& cfg,
573573
std::string target; // canonical triple
574574
std::string note; // "host" / "static" / "PE" / "cross" tags
575575
std::string toolchain; // "gcc 16.1.0" or "—"
576+
// ⚠️⚠️ `toolchain` MEANS TWO DIFFERENT THINGS DEPENDING ON THE ROW, and
577+
// that is fine for a column a person reads and wrong for a field a
578+
// program reads. On an INSTALLED row it is the payload that is here; on
579+
// a vocabulary row it is the target table's convention pin. A row can
580+
// have the first and no second — `x86_64-linux-gnu` has an installed
581+
// gcc and no convention at all.
582+
//
583+
// Measured 2026-08-26: e2e 298 selected "a row whose pin is a gcc" from
584+
// this field and got `x86_64-linux-gnu` on the CI runner, where the pin
585+
// is empty. It then asserted a refusal that correctly did not happen.
586+
//
587+
// ⭐ So the convention travels in its own field, empty when there is
588+
// none. Same defect family as the release this was written for.
589+
std::string pin; // the target table's convention pin, or empty
576590
std::string status; // installed | available | planned
577591
bool isDefault = false;
578592
int rank = 0; // display order: installed < available < planned
@@ -604,6 +618,8 @@ export int toolchain_list(const mcpp::config::GlobalConfig& cfg,
604618
r.target = t.str();
605619
r.note = note_for(t);
606620
r.toolchain = tcLabel;
621+
for (auto const& info : mcpp::toolchain::triple::known_targets())
622+
if (info.canonical == r.target) { r.pin = std::string(info.pin); break; }
607623
r.status = "installed";
608624
r.rank = 0;
609625
r.isDefault = defSpec
@@ -666,6 +682,7 @@ export int toolchain_list(const mcpp::config::GlobalConfig& cfg,
666682
std::string pin(info.pin);
667683
if (auto at = pin.find('@'); at != std::string::npos) pin[at] = ' ';
668684
r.toolchain = pin.empty() ? "" : pin;
685+
r.pin = std::string(info.pin);
669686
// Three answers, not two. "available" means a payload here produces it;
670687
// "via dependency graph" means the compiler is here and the system has
671688
// to come from packages — a different thing to do next, so a different
@@ -688,6 +705,7 @@ export int toolchain_list(const mcpp::config::GlobalConfig& cfg,
688705
{"target", r.target},
689706
{"note", r.note},
690707
{"toolchain", r.toolchain},
708+
{"pin", r.pin},
691709
{"status", r.status},
692710
{"default", r.isDefault},
693711
});

tests/e2e/298_overriding_a_convention_requires_replacing_it.sh

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,23 @@ fail=0
5555
# The target is read from the machine rather than hardcoded: any row whose pin
5656
# names a family other than llvm will do, and which rows exist is the target
5757
# table's business, not this test's.
58-
# ⭐ A ROW WHOSE PIN IS A gcc AND WHOSE STATUS SAYS THIS HOST SERVES IT.
59-
# Both halves matter: a `planned` row would refuse for a different rule and
60-
# half one would then pass while testing nothing.
58+
# ⭐⭐ `pin`, NOT `toolchain` — AND THE DIFFERENCE COST A RED CI RUN.
59+
#
60+
# `toolchain` is what the row is associated with: the installed payload on an
61+
# installed row, the convention on a vocabulary row. A row can have the first
62+
# and no second. This test needs a row that HAS a convention, so it reads the
63+
# field that carries only that.
64+
#
65+
# Measured 2026-08-26 on ubuntu-24.04: selecting on `toolchain` picked
66+
# `x86_64-linux-gnu`, whose convention pin is empty, and the test then demanded
67+
# a refusal that correctly did not happen.
68+
#
69+
# The status filter matters for the same reason in the other direction: a
70+
# `planned` row refuses under a different rule, and half one would pass while
71+
# testing nothing.
6172
pinned="$("$MCPP" toolchain list --format json 2>/dev/null \
6273
| jq -r '[.data.targets[]
63-
| select(.toolchain | startswith("gcc"))
74+
| select(.pin | startswith("gcc"))
6475
| select(.status != "planned")
6576
| .target][0] // empty')"
6677
if [ -z "$pinned" ]; then

0 commit comments

Comments
 (0)