Skip to content

Commit 7097acd

Browse files
committed
"Already satisfied" and "cannot be done" were the same refusal; three
repositories hit the same shape within a day of 2026.9.18.1 Three regressions from 2026.9.18.1, all one shape: the engine treated a target already satisfying a [c-abi] declaration and a target being unable to realise it as the same case, so it either refused a compiler it never needed to, or refused a target it could have realised. 1. GCC on Linux was refused for a package merely DECLARING [c-abi], before realisation ran at all. openkal-musl 0.15.0's own declaration on Linux (presents = "posix", data-model = "arch-default", wchar = 32) realises to nothing -- Linux's default triple already is that -- so nothing about it needed Clang. Every GCC user of openkal-musl on Linux lost the package for a substitution nothing asked GCC to perform. Fixed by reordering: cenv::realise (a pure function of the declaration and the target, not the compiler) runs first, and the Clang-only gate fires only when the result is non-empty. Windows is unchanged: the Cygwin-flavoured substitution there is non-empty, so a non-Clang compiler is still refused, naming what it cannot do. 2. macOS does not satisfy presents = "posix" by default -- caught by the verification probe on a real build (declared __unix__ defined, measured undefined), which is exactly the class of error that probe exists to catch. Apple's clang predefines __APPLE__/__MACH__ on its default triple, never __unix__; the original design text assumed macOS already presented it the way Linux does. 3. A freestanding target had no realisation for presents = "posix" at all, refused outright, which blocked openkal-llvm-runtime on riscv64-none-elf before it reached any other target. 2 and 3 dissolve into the same one-line rule, simpler than what it replaces: realising presents = "posix" means the same observable fact everywhere -- __unix__ defined, _WIN32 not -- and targets differ only in what it costs. Linux: nothing. macOS and freestanding: one token, -D__unix__ -- neither already defines it, for the identical reason. Windows: the Cygwin-flavoured substitution. Known cost, left to the mcpp-index 30-member measurement to weigh: #ifdef __unix__ ... #elif defined(__APPLE__) now takes the Unix branch on macOS too. Tests: test_cenv.cpp pins the GCC/Linux-accepted and GCC/Windows-still- refused halves via cenv::realise directly (MacosPosixArchDefaultDefinesUnix, FreestandingPosixDefinesUnix, FreestandingWindowsIsStillRefused); test_cenv_probe.cpp adds the token cenv::realise now produces for macOS/ freestanding handed to a real compiler, proving the probe agrees with it; e2e 742 reproduces the GCC/Linux regression against a real build (fails without this fix) and pins GCC/Windows still refused. Version bump: 2026.9.18.1 shipped these regressions; downstream needs a release with the fix, so this is 2026.9.18.2.
1 parent 12d8a47 commit 7097acd

10 files changed

Lines changed: 379 additions & 40 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@
55

66
## [Unreleased]
77

8+
## [2026.9.18.2] - 2026-09-18
9+
10+
### 2026.9.18.1 发布几小时内,三个下游仓库的 CI 揭出的三处同形缺陷:声明满足与做不到被当成了一回事
11+
12+
三处缺陷,同一个形状:引擎把「目标默认已经满足这条声明」和「这条声明做不到」当成了同一种
13+
情况来处理,结果要么拒绝了一个根本不需要拒绝的编译器,要么拒绝了一个其实能实现的目标。
14+
15+
- **GCC 在 Linux 上被拒绝,仅仅因为声明了 `[c-abi]`,即便实现出来的东西是空的。**
16+
openkal-musl 0.15.0 在 Linux 上的声明——`presents = "posix", data-model =
17+
"arch-default", wchar = 32`——正是 x86_64 Linux 的默认三元组本来就是的样子,实现出来
18+
不需要任何令牌。旧的检查顺序是先问「这是不是 Clang」,再算实现——所以只要图里出现
19+
`[c-abi]` 就先把非 Clang 编译器拒了,不管实现到底需不需要它做什么。每一个用 GCC 在
20+
Linux 上构建 openkal-musl 的用户,因此白白丢了这个包。现在 `cenv::realise` 先跑(它是
21+
声明与目标的纯函数,与编译器无关),只有在结果令牌非空时才检查编译器族——Windows 上的
22+
Cygwin 式替换仍然是非空的,所以那条路径的拒绝没有变。
23+
- **macOS 默认并不满足 `presents = "posix"`,校验探针当场抓到了这一点——这正是它存在的
24+
意义。** 探针报告:声明 `__unix__` 已定义,实测未定义。Apple 的 clang 默认预定义的是
25+
`__APPLE__`/`__MACH__`,从来不是 `__unix__`;最初设计文本假设 macOS 的默认三元组像
26+
Linux 一样已经满足,这个假设是错的。
27+
- **裸机(freestanding)目标对 `presents = "posix"` 一律拒绝,openkal-llvm-runtime 因此
28+
在 `riscv64-none-elf` 上还没碰到别的目标就先被挡下来了。**
29+
30+
三处修成一条规则,也是比它取代的那条更简单的表述:实现 `presents = "posix"` 在每个目标上
31+
陈述的是同一件可观察的事实——`__unix__` 已定义,`_WIN32` 未定义——目标之间的差别只在于
32+
实现它要花多大代价。Linux:不花代价。macOS 与裸机:定义 `__unix__`,一个令牌。Windows:
33+
Cygwin 式替换。已知的权衡,留给 mcpp-index 30 个成员的实测去称量:写成
34+
`#ifdef __unix__ ... #elif defined(__APPLE__)` 的可移植代码,现在在 macOS 上也会走 Unix
35+
分支。
36+
(`src/toolchain/cenv.cppm`、`src/build/prepare.cppm`,单测 `test_cenv.cpp`(
37+
`MacosPosixArchDefaultDefinesUnix`、`FreestandingPosixDefinesUnix`、
38+
`FreestandingWindowsIsStillRefused`)与 `test_cenv_probe.cpp`(令牌与探针互相印证的一例),
39+
e2e `tests/e2e/742_gcc_accepted_when_the_realisation_is_empty.sh`,docs/22 及其 zh 镜像)
40+
841
## [2026.9.18.1] - 2026-09-18
942

1043
### C 库层可以声明它呈现的 C 环境,引擎实现并校验:设计 2026-09-18(2026.9.18.1)

‎docs/22-target-side.md‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,42 @@ that names no C library:
361361
| Target | Request | Realisation |
362362
|---|---|---|
363363
| Linux | `posix` / `arch-default` | the default triple already satisfies it |
364-
| macOS | `posix` / `arch-default` | the default triple already satisfies it |
364+
| macOS | `posix` / `arch-default` | one token, `-D__unix__` — Apple's clang predefines `__APPLE__`/`__MACH__` on its default triple, never `__unix__` |
365+
| freestanding | `posix` / `arch-default` | the same one token, `-D__unix__`, for the same reason: nothing here defines it either |
365366
| Windows | `posix` / `arch-default` | Cygwin-flavoured: `--target=x86_64-pc-cygwin` on the compile line only; `__CYGWIN__`/`__CYGWIN32__` are left defined (see the note below); `data-model` becomes LP64 as a consequence of the triple, not a separate flag |
366367
| any | `builtins = "iso"` | turns off code-generation idioms that assume a platform C library — `-fno-builtin-memset_pattern16` on Apple targets is the one this survey measured; see `src/toolchain/cenv.cppm` for what else was checked and found not to apply |
367368
| anything else | | refused, naming the target, the request and what is missing — never a silent downgrade |
368369

370+
**The macOS and freestanding rows are a correction, not the design's original
371+
text (coordinator revision, caught by real builds within a day of
372+
2026.9.18.1 shipping).** The rule realising `presents = "posix"` follows is:
373+
the SAME observable fact on every target — `__unix__` defined, `_WIN32`
374+
not — and targets differ only in what realising it COSTS. The original text
375+
assumed macOS's default triple already presented it, the way Linux's does;
376+
the verification probe (below) caught that assumption as false on a real
377+
build — declared defined, measured undefined — which is precisely the class
378+
of error that probe exists to catch. The original text also refused a
379+
freestanding target outright for asking `presents = "posix"` at all, which
380+
blocked a whole target (`riscv64-none-elf`, openkal-llvm-runtime) rather
381+
than realising a fact this engine can in fact deliver: a freestanding target
382+
starts with neither macro defined, exactly like macOS's default triple, so
383+
the fix costs it the identical token. Known cost, left to the mcpp-index
384+
30-member measurement to weigh: portable code written
385+
`#ifdef __unix__ ... #elif defined(__APPLE__)` now takes the Unix branch on
386+
macOS too, correct only if that branch is written to also be correct there.
387+
388+
**A compiler is refused only when the realisation actually needs a token it
389+
cannot take — not merely because `[c-abi]` is declared.** `cenv::realise`
390+
(above) runs first, as a pure function of the declaration and the target;
391+
the Clang-only requirement is checked second, and only fires when the
392+
resulting tokens are non-empty. openkal-musl's own declaration on Linux —
393+
`presents = "posix", data-model = "arch-default", wchar = 32` — realises to
394+
nothing at all (the first row of the table above), so GCC is accepted there
395+
and the verification probe confirms it, exactly as it would for any other
396+
empty realisation. The Windows case is unchanged: the Cygwin-flavoured
397+
substitution is non-empty, so a non-Clang compiler is still refused there,
398+
naming what it cannot do.
399+
369400
The Windows row is the flagship case: `x86_64-w64-windows-gnu` and
370401
`x86_64-pc-cygwin` produce IDENTICAL machine code — same PE format, same
371402
Win64 calling convention, same SEH — and differ only in what the

‎docs/zh/22-target-side.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,32 @@ libunwind 的 `assembly.h`,正是按这个宏来选寄存器保存集的)。mcpp
298298
| 目标 | 请求 | 实现 |
299299
|---|---|---|
300300
| Linux | `posix` / `arch-default` | 默认三元组已经满足 |
301-
| macOS | `posix` / `arch-default` | 默认三元组已经满足 |
301+
| macOS | `posix` / `arch-default` | 一个令牌,`-D__unix__`——Apple 的 clang 默认三元组预定义的是 `__APPLE__`/`__MACH__`,从来不是 `__unix__` |
302+
| 裸机(freestanding) | `posix` / `arch-default` | 同样一个令牌,`-D__unix__`,原因相同:这里同样没有任何东西定义它 |
302303
| Windows | `posix` / `arch-default` | 采用 Cygwin 式语义:仅在编译行加 `--target=x86_64-pc-cygwin`;`__CYGWIN__`/`__CYGWIN32__` 保持定义(见下方说明);`data-model` 变为 LP64 是三元组切换的结果,不是另一个开关 |
303304
| 任意目标 | `builtins = "iso"` | 关闭代码生成阶段假定平台 C 库在场的惯用法识别——本轮实测到的唯一一例是 Apple 目标上的 `-fno-builtin-memset_pattern16`;`src/toolchain/cenv.cppm` 记录了还核实过哪些、结论是不适用 |
304305
| 其余情况 | | 明确拒绝,点名目标、请求与缺什么——不静默降级 |
305306

307+
**macOS 与裸机这两行是一次修正,不是设计原文(协调者修订,2026.9.18.1 发布不到一天就被真实
308+
构建抓到)。** 实现 `presents = "posix"` 遵循的规则是:在每一个目标上陈述的是同一件可观察的
309+
事实——`__unix__` 已定义,`_WIN32` 未定义——目标之间的差别只在于实现它要花多大代价。原文
310+
假设 macOS 的默认三元组已经像 Linux 一样呈现了它;真实构建上的校验探针把这个假设当场
311+
抓包为假——声明已定义,实测未定义——这正是那个探针存在的意义所在,不是它的漏洞。原文还
312+
把裸机目标对 `presents = "posix"` 的任何请求一律拒绝,这挡住了整整一个目标
313+
(`riscv64-none-elf`,openkal-llvm-runtime),而不是去实现这个引擎其实能给出的事实:裸机
314+
目标一开始两个宏都没有定义,和 macOS 默认三元组一模一样,所以这处修复付出的是同一个令牌
315+
的代价。已知的成本,留给 mcpp-index 30 个成员的实测去权衡:写成
316+
`#ifdef __unix__ ... #elif defined(__APPLE__)` 的可移植代码,现在在 macOS 上也会走 Unix
317+
分支,只有那条分支本身在 macOS 上也写对了才是对的。
318+
319+
**编译器只在实现真的需要一个它拿不出来的令牌时才被拒绝——不是仅仅因为声明了
320+
`[c-abi]`。** `cenv::realise`(上文)先跑,是声明与目标的纯函数;只有 Clang 才行的要求
321+
第二个才检查,且只在结果令牌非空时才触发。openkal-musl 自己在 Linux 上的声明——
322+
`presents = "posix", data-model = "arch-default", wchar = 32`——实现出来什么都没有(上表
323+
第一行),所以 GCC 在那里被接受,校验探针照常核实,和任何其他空实现一样。Windows 那一情形
324+
没有变:Cygwin 式替换是非空的,所以一个非 Clang 编译器在那里仍然被拒绝,并点名它做不到
325+
什么。
326+
306327
Windows 一行是旗舰情形:`x86_64-w64-windows-gnu` 与 `x86_64-pc-cygwin` 生成的机器码完全一致——
307328
同样的 PE 格式、同样的 Win64 调用约定、同样的 SEH——差别只在预处理器看到什么、`long` 有多宽。
308329
因此实现只触及**编译**行;**链接**行保持图解析出的三元组,因为目标文件格式没有变化。

‎mcpp.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "2026.9.18.1"
3+
version = "2026.9.18.2"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

‎modules/versioning/src/version.cppm‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ import std;
3131

3232
export namespace mcpp {
3333

34-
inline constexpr std::string_view MCPP_VERSION = "2026.9.18.1";
34+
inline constexpr std::string_view MCPP_VERSION = "2026.9.18.2";
3535

3636
} // namespace mcpp

‎src/build/prepare.cppm‎

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10788,21 +10788,29 @@ prepare_build(bool print_fingerprint,
1078810788
// below is therefore skipped, and every command line unchanged, for
1078910789
// the graph this engine has always built.
1079010790
if (tc && resolvedTargetSide.cAbiDecl) {
10791-
if (!mcpp::toolchain::is_clang(*tc)) {
10792-
refusal::record(refusal::Code::CEnvUnrealisable);
10793-
return std::unexpected(std::format(
10794-
"the C library ('{}', {}) declares [c-abi], and this "
10795-
"build's compiler ('{}') is not one mcpp can realise it "
10796-
"through.\n"
10797-
" [c-abi] is realised with Clang-specific "
10798-
"mechanisms — a `--target=` substitution and "
10799-
"`-f[no-]short-wchar` — so a Clang toolchain is required "
10800-
"for this target while a [c-abi] block is in the graph.\n"
10801-
" Select one: [toolchain] default = \"llvm@<version>\", "
10802-
"or [target.<triple>] toolchain = \"llvm@<version>\".",
10803-
resolvedTargetSide.cAbi.interfaceName,
10804-
resolvedTargetSide.cAbi.impl, tc->compiler_family()));
10805-
}
10791+
// `cenv::realise` FIRST, THE COMPILER-FAMILY GATE SECOND — not
10792+
// the other way around (coordinator report, openkal-musl 0.15.0
10793+
// regression: GCC on Linux refused for a declaration
10794+
// `presents = "posix", data-model = "arch-default", wchar = 32,
10795+
// builtins = "iso"` that Linux/x86_64's own default ALREADY
10796+
// satisfies, needing no substitution at all — a gratuitous
10797+
// refusal that lost the package for every GCC user on Linux).
10798+
// `cenv::realise` is a pure function of the declaration and the
10799+
// TARGET, not of the compiler (`mcpp.toolchain.cenv`'s own
10800+
// module header) — computing it before asking anything about the
10801+
// compiler is what lets an EMPTY realisation answer "does this
10802+
// compiler need to be Clang" correctly: no. The Clang-specific
10803+
// mechanisms (`--target=` substitution, `-f[no-]short-wchar`,
10804+
// the `builtins = "iso"` flags) are only needed when realisation
10805+
// actually produces tokens; when it produces none, the target's
10806+
// own default already IS the declaration, and any compiler that
10807+
// can run the verification probe below (`-E -dM`, not a
10808+
// Clang-specific flag) can be trusted to have gotten there —
10809+
// which is exactly what that probe then confirms rather than
10810+
// assumes. The Windows/GCC case is UNCHANGED by this: there the
10811+
// realisation is non-empty (the Cygwin-flavoured substitution),
10812+
// and MinGW's `long` is 32-bit regardless of flags (openkal-musl
10813+
// measured), so the gate below still refuses it.
1080610814
auto tt = mcpp::toolchain::triple::parse(tc->targetTriple);
1080710815
auto realised = mcpp::toolchain::cenv::realise(
1080810816
*resolvedTargetSide.cAbiDecl, tt ? tt->os : std::string{},
@@ -10811,6 +10819,24 @@ prepare_build(bool print_fingerprint,
1081110819
refusal::record(refusal::Code::CEnvUnrealisable);
1081210820
return std::unexpected(realised.error());
1081310821
}
10822+
if ((!realised->tokens.empty() || !realised->builtinsTokens.empty())
10823+
&& !mcpp::toolchain::is_clang(*tc)) {
10824+
refusal::record(refusal::Code::CEnvUnrealisable);
10825+
return std::unexpected(std::format(
10826+
"the C library ('{}', {}) declares [c-abi] whose "
10827+
"realisation for this target requires Clang-specific "
10828+
"substitution, and this build's compiler ('{}') cannot "
10829+
"carry it out.\n"
10830+
" [c-abi] is realised, for targets that need "
10831+
"anything at all, with Clang-specific mechanisms — a "
10832+
"`--target=` substitution and `-f[no-]short-wchar` — so "
10833+
"a Clang toolchain is required for this target while "
10834+
"this declaration is in the graph.\n"
10835+
" Select one: [toolchain] default = \"llvm@<version>\", "
10836+
"or [target.<triple>] toolchain = \"llvm@<version>\".",
10837+
resolvedTargetSide.cAbi.interfaceName,
10838+
resolvedTargetSide.cAbi.impl, tc->compiler_family()));
10839+
}
1081410840
tc->cEnvTokens = realised->tokens;
1081510841
tc->cEnvBuiltinsTokens = realised->builtinsTokens;
1081610842
tc->cEnvExpectWcharBits = realised->expectWcharBits;

0 commit comments

Comments
 (0)