FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Tags · mcpp-community/mcpp · GitHub

Tags: mcpp-community/mcpp

Tags

v2026.8.25.2

Toggle v2026.8.25.2's commit message
2026.8.25.2 — one predicate family, closed

v2026.8.25.1

Toggle v2026.8.25.1's commit message
2026.8.25.1 — the C library layer decides the link line, not the system

v2026.8.24.6

Toggle v2026.8.24.6's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2026.8.24.6 — musl on Windows has a name, and the segment that disagr…

…eed now says so (#501)

* 2026.8.24.6 — musl on Windows has a name, and the segment that disagreed now says so

⭐ **同一个 `--target x86_64-windows-gnu`,两种体系下的 C 库完全不同**,
而 mcpp 用同一个名字称呼它们。实测同一份源码:

                  MinGW CRT      musl / openkal
    体积             587,894         9,815,552   (16.7×)
    依赖 DLL      + msvcrt.dll     (无)

MinGW 的 C 库不是自足的 —— `printf`/`malloc` 的实现在目标机自带的
`msvcrt.dll` 里,587KB 只是「你的代码 + 胶水」。musl 是自足的。
**两者不该共用一个名字。**

── ⚠️ 这个名字 LLVM 拼不出来,而理由不是我以为的那个 ────────

我三次说「LLVM 词表没有 windows-musl 这个拼写」。**不准确。** 实测:

    clang++ --target=x86_64-pc-windows-musl -c t.cpp
        #5  llvm::MCWinCOFFStreamer::emitCGProfileEntry(...)

三元组解析器**认识**它(`-dumpmachine` 原样返回),代码生成路径
**没有实现**它 —— 崩在 COFF 写出器,是 ICE 不是诊断。Windows 上四个
非 MSVC 环境 `gnu`/`cygnus`/`itanium`/`musl`,前三个能编,只有 musl 死;
预定义宏说明它从未被建模(无 `__MINGW32__`)。

⭐ 于是 mcpp 的名字与交给 clang 的三元组**必须**是两个字符串 ——
而它们本来就是,报告里那个箭头两侧就是:

    Target x86_64-windows-musl → x86_64-w64-windows-gnu
           ^ 回答「C 库是谁」      ^ 回答「遵循哪套对象 ABI」

**我此前用编译器的词表限制了 mcpp 自己的词表**,把两个轴混成一个。
这是同一个问题三轮没改对的根子。

⚠️ pin 为 `llvm@22.1.8` 不是偏好:全局默认为 gcc 时空 pin 会让它落到
musl-gcc 载荷并报「没有 C++ 前端」—— 一条关于缺前端的消息,而真正的
问题是只有 clang 能发这个目标。

⚠️ 档为 `preview`:端到端还差一环 —— `openkal-musl@0.3.3` 精确钉死
`openkal-windows = "0.1.3"`,索引里已有的 0.1.4 到不了消费者。

── 修复:同形的两件事,一个报一个不报 ────────────────────

    x86_64-linux-gnu     名字说 gnu,事实是 musl  →  ⚠️ 警告
    x86_64-windows-gnu   名字说 gnu,事实是 musl  →  ❌ 静默

豁免的理由「Windows 上 gnu 命名对象 ABI 而非 C 库」**只对了一半**:
那一段捆着对象 ABI(被兑现)与 MinGW 的 C 运行时(被图替换)。
判据改为「轴 ∈ {CLibrary, ObjectAbi}」,消息额外说明 ABI 那一半未受影响。

⚠️ 裸机的 `elf` 继续豁免 —— 它在任何平台上都不命名 C 库。

── 文档 ──────────────────────────────────────────────

docs/16 新增「三套词表」(中英):`x86_64-w64-mingw32` 为何把实现放在
OS 位(autoconf 世界观)、clang 如何重拼为 `windows-gnu`、以及
`gnu` 这个 env 值正是 LLVM 拆开 `mingw32` 后给剩下那一半起的名字。

.agents/docs 另收两份:三轴实测矩阵,以及一份**待 review 的规范设计** ——
提议 mcpp 拥有自己的目标词表(`<arch>-<os>-<libc>`,第三段恒定表示
C 库),与 clang 的分歧限于五处且每处有实测支撑。

单元测试 93/0(targetside 断言随语义反转更新),e2e 284 全绿。

* docs(spec): macOS 也有 musl,而 libSystem 不该有名字

两处我写错了,都是同一种错误的两个方向。

⚠️ **「苹果 SDK 不可再分发」把源码和 SDK 混成了一件事。** Apple 的 Libc
是开源的(`apple-oss-distributions/Libc`);不可再分发的是 SDK 里的头文件
与 `.tbd` 存根库 —— 那才是交叉链接 Mach-O 所需的。我把「不能交叉」的原因
说成了「libc 闭源」。另外 `libSystem` 不等于 Libc,它是一把伞,
底下含 libc、libm、libpthread、libdyld。

⚠️ **「macOS 只有一个 C 库,不需要名字」已经不成立** —— openkal 让
macOS 上也有了 musl。而我在同一份文档的 §5 里记下了这个实测,
**却没有回头改 §1.1 的表**。测出来了、没把结论传播到该改的地方,
这是本轮第二次。

── ⭐ 但结论仍是「libSystem 不该有名字」,理由换成实测的 ──────

macOS 的三元组形状与另外两个平台不同:OS 段带部署目标,且没有 env 段。
clang 接受任意 env 后缀却不赋予含义:

    arm64-apple-macos14.0-gnu   → 原样返回,未规范化
    arm64-apple-macos14.0-musl  → 原样返回,未规范化

原样保留而不像 Windows 那样重拼,说明 **Darwin 那一支根本不看 env 段**。
给 libSystem 起名会得到一个**在编译器侧没有对应物**的字符串。

于是规范定:**macOS 第三段只有一个合法值 `musl`,libSystem 由省略表达。**
三平台因此不对称,而不对称来自被映射的那一侧,不是 mcpp 的选择。

⭐ 连带:openkal 路径压根不碰 libSystem,所以这个命名问题在构建期体系里
从一开始就不存在 —— 只在预构建路径上被问起,而那条路径今天用的正是省略式。

── §6 新增:已知代价,记录在案 ──────────────────────────

「libSystem 由省略表达」的代价是**说不出它**:一个装了 openkal 依赖、
却想在 macOS 上用 libSystem 的工程无法表达这个意图,而 Linux 上 `-gnu`
恰恰能做到。

不解的理由、**重新打开它的三个触发条件**、以及届时的候选做法都写进 §6。
⭐ 并把 §5 与 §6 的性质分开:§5 是**尚未测**,§6 是**测过了并决定不做** ——
这两种「待办」看着像,后来者对它们该做的事完全不同。

* test(triple): a duplicate row in the target table is a machine-checkable fact

自查时读 diff 发现 `x86_64-windows-musl` **有两行** —— 改 pin 时插入了修正
后的行,没删掉原来那行。`find_known_target` 取首命中,所以每一处行为都
正确、什么都没失败;表里多的是一行**列值与活着那行不一致的死数据**。

⭐ 靠通读 diff 发现它,机制是错的:重复是表的性质,机器看得见。
两条测试各四行:

    TheTargetTableHasNoDuplicateNames    没有两行共用一个规范名
    EveryTableRowIsItsOwnCanonicalForm   每行都能 parse 回它自己

第二条守的是另一件事:一个 `parse`/`str` 往返不还原的规范名,
是一行**任何 `--target` 都到不了**的行。

实测:临时插一行重复的,第一条立刻转红。

* docs(spec): §7 aarch64-linux-musl 作为规范的验收目标,三处缺陷逐层剥出

⭐ **一套目标词表的价值只有在第二个架构上才被检验。** x86_64 上
「按 OS 分」与「按架构分」给出相同答案,判据用错了轴也看不出来。

来源:#492 的使用者报告 —— 他要同时出 x86_64 与
aarch64 两份静态二进制。逐层剥出三处:

**7.1 ✅ 已修** x87 例程按 OS 排,而它是架构的性质。
`*xf*.c` 的排除挂在 `os = "macos"` 与 `os = "none"` 下,aarch64-linux
落进 `os = "linux"` 那一支去编只有 x86 才有的 `xf_float`。
⚠️ 两处此前都对 —— 那两个 OS 恰好都蕴含非 x87 架构;linux 段不排除
也对 —— x86 上 `__mulxc3` 会被真实调用。**两个方向都会坏,错的只是轴。**
已提 mcpplibs/openkal-llvm-runtime#5。

**7.2 ⚠️ 未修** `--no-default-config` 本身改变目标特性:

    clang --target=aarch64-unknown-linux-musl                       →  +outline-atomics
    clang --target=aarch64-unknown-linux-musl --no-default-config   →  -fmv

⭐ 根因**不是**三元组不一致(两边逐字核对过都是
`aarch64-unknown-linux-musl`),**不是**缓存串目标(键含 target_triple,
两个 aarch64 条目按工具链正确分开)—— 这两条我都先猜错并被实测否掉。

std 模块带 `--no-default-config` 编(包要求,否则宿主 C 库的头混进来),
普通 TU 不带。**两个都成立的要求相撞**,而 x86_64 上该 flag 不改变特性,
所以从未暴露。三条候选修法按代价列出,均未实测。

**7.3 / 7.4 未查**:`aarch64-linux-gnu` 仍是 planned 而使用者真正要的是
`-musl`(诊断没说「你要的那个另有拼法」);以及使用者报的另两条 ——
`std::random_device` 被 `__config_site` 关掉、GMF 里 include 标准头时
宿主头与 openkal 头混合。⭐ 后者与 7.2 是**同一个 cfg** 的两个症状。

* docs(spec): §7.2/7.2b/7.5 实测回填,aarch64 验收达成

7.2 与 7.2b 从「未修」改为已修并附实测;新增 7.5 —— `std::random_device`
的根在 openkal 规范(SURFACE.txt 全文无 random/entropy),而非运行时包:

    std::random_device → libc++ 五条后端 → getentropy → musl getrandom()
    → syscall_cp(SYS_getrandom) → openkal-musl 无替代 → 规范无该接口

⚠️ `_LIBCPP_HAS_RANDOM_DEVICE 0` **不是保守设置,是事实的准确记录**。
改成 1 只让编译期通过,链接会缺符号。`generic` 那份也关着是对的 ——
它同样跑在 openkal 之上,`generic` 不等于「宿主 Linux」。

* docs(spec): §7.5 修正——移植机制现成,缺的是规范里可转的目标

⚠️ 我写「openkal-llvm-runtime 只能开关那个宏」,错了。
`okm_syscall.c` **已经把 69 个系统调用转给了 openkal**,移植机制现成;
真正的约束是规范里没有随机源接口可转。

并补两条:经 fs 打开 `/dev/urandom` 在设计上被挡住(`kal_fs_open` 只能
相对 preopen 目录,而 openkal-linux 只给 2 个)——⭐ 那正是能力模型要挡的
东西,不是缺口是拒绝;以及当下可做的 feature 形状,`openkal-musl` 已有
`[features]` 机制,但名字要说清它假设宿主(`host-entropy` 而非 `random`),
且 `_LIBCPP_HAS_RANDOM_DEVICE` 必须跟随它而非独立开关。

* docs(spec): §7.5.2 openkal.random 接口设计草案

按 §7.5.1 三条判定(不加实现不了 / 不能绕过接口层 / 宿主通用裸机不通用)
给出形状,照 `openkal.time` —— 现有接口里最小的一个,问题结构同型。

⚠️ **不设 AVAILABLE 能力字**:「有没有」由接口在场与否回答(6.1 条),
这正是 openkal-opensbi@0.1.3 撤回的教训。

⭐ uefi 那一行最有意思:同一后端在不同固件上可能提供或不提供,而 6.1 条
要求「全有或全无」,所以必须在**构建期**决定 —— 运行期拒绝违反该条。

三处连带改动都写了约束:AT_RANDOM 要保留 fallback(否则裸机上 stack canary
起不来)、`_LIBCPP_HAS_RANDOM_DEVICE` 跟随目标、一致性套件那条会以 2^-256
概率误报而写清楚比省掉好。

⚠️ 草案未实测,不属于目标词表这一 PR。

* test(e2e): a branch that accepts anything is not a check

The assertion that the toolchain line carries mcpp's own name for the
target had an `*) : ;;` fallback, so it printed on a match and passed on
anything else. This file's whole subject is a name that used to be
accepted by nothing, and the one line proving the mapping — mcpp's
canonical form in, the compiler's spelling out — could not fail.

The fallback is gone, and the pin is asserted beside it. The pin is what
the row is for: a global default of gcc carried onto this target
produces a true sentence about the wrong subject,

    error: toolchain payload 'xim:musl-gcc@16.1.0' has no known C++
           frontend in …/xim-x-musl-gcc/16.1.0/bin

so the report showing `llvm@22.1.8` winning is the observable form of
the row working.

Measured against a binary built from this branch:

      ok  x86_64-windows-musl is known, and the refusal names the real reason
      ok  the toolchain line carries mcpp's own name
      ok  the row's pin decided the toolchain

* docs(spec): `aarch64-macos-musl` was written as a spelling that does not exist

§1.1 listed it as "显式请求 musl" and §6.1 built an argument on it: that
macOS can name musl but cannot name libSystem. Measured against a binary
built from this branch, neither is true —

    $ mcpp build --target aarch64-macos-musl
    error: unknown target 'aarch64-macos-musl'

— the table has one macOS row and it carries no env segment, so both
directions are unsayable there. Windows can name its C library because
this PR adds `x86_64-windows-musl`; macOS is missing the same row rather
than missing a policy, which is a smaller and more accurate statement of
the gap than the one that was written.

Every target name in docs/16 (both languages) and the three analysis
documents was then checked against the table. Twenty-three appear; four
short spellings are valid by the fill rule, four more are candidate
spellings in passages that say they are not adopted, and this was the
only one presented as real that is not.

* docs(spec): §8 跨仓库自审 —— 六条被判据抓住的问题

并修正 §7.5.2 排在 §7.6 之后的顺序。

* docs(spec): §8.7/§8.8 —— 又两条被判据抓住的问题

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.8.24.4

Toggle v2026.8.24.4's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(run): `--target X` wrote X's build into the host's cache slot (#498)

* fix(run): `--target X` wrote X's build into the host's cache slot

`mcpp run --target X` 正确地为 X 交叉构建,然后把结果**记在了宿主的槽里**。
损害不在这条命令,而在下一条:

    $ mcpp run --target riscv64-none-elf        # 正确,经 qemu 跑起来
    $ mcpp run
         Running `target/riscv64-none-elf/…/bin/openkal-same-source`
    exit=1

缓存文件里逐字可见 —— 一次 riscv 交叉构建写下的是:

    [target=]

空键的含义是「为这台机器构建」,而这正是 `try_fast_run` 允许自己
**直接 exec 缓存产物**的唯一依据。于是裸的 `mcpp run` 命中它,
跳过整个 prepare_build(所以既没有构建宿主目标,也没有解析 runner),
把另一个目标的二进制在本机执行了。

真因是一个丢掉的实参。`build_run_target` 收到了 `target_triple`、
用它建好 `ov` 并正确交叉构建,唯独在这里没有传下去:

    -  run_build_plan(*ctx, false, no_cache);
    +  run_build_plan(*ctx, false, no_cache, target_triple);

而最后那个形参就是缓存条目的 `[target=]` 键。`cmd_build` 的两个调用点
都传了,只有 run 这条路径没传。

⚠️ **`try_fast_run` 的注释里记着同一个缺陷的另一条到达路径**(目标写在
清单的 `[build] target` 里),并且只守了那一扇门:

    if (!want->defaultTarget.empty()) return std::nullopt;

这是另一扇门:flag。同一个缺陷有两个入口,修好一个不会暴露另一个 ——
只有走到那条路径上才会。

── 回归测试的判据是路径,不是退出码 ────────────────────────

⭐ 异架构产物 exec 会失败,所以只看退出码的测试**在那里因错误的理由通过,
而在同架构上完全测不到** —— 后者恰恰更危险:用户不会看到崩溃,
只会拿到一个 musl 产物冒充宿主产物,静默地跑下去。

`283_run_target_flag_owns_its_cache_slot.sh` 因此用
`x86_64-linux-musl`(同架构、本机能跑),断言 `Running \`…\`` 那行里的
**路径**不含交叉目标的目录。在修复前的二进制上实测为红:

    FAIL: a bare `mcpp run` exec'd the x86_64-linux-musl artifact
           target/x86_64-linux-musl/50af2973556be3d7/bin/runslot

修复后转绿。单元测试 93 通过 / 0 失败。

发现路径:openkal-llvm-runtime 的 CI 有一步名为「on bare metal」却跑裸的
`mcpp run`(即宿主)。把它改成真的 `--target riscv64-none-elf` 之后,
**下一步**炸了 —— 缺陷一直都在,只是没有任何东西走到那条顺序上。

* 2026.8.24.4 — a cross build must not occupy the host's cache slot

版本号两处同步(mcpp.toml + src/version.cppm),CHANGELOG 记下缺陷、
两条到达路径,以及回归测试为何断言路径而非退出码。

bootstrap pin 不动:它是自举起点,指向一个已发布且能构建当前树的版本。

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.8.24.3

Toggle v2026.8.24.3's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(target): the triple is a request, and the row's convention waits …

…for the graph (#495)

* fix(target): the triple is a request, and the row's convention waits for the graph

两个症状,一个病根:**`compiler` 被声明为第五层,而它的解析留在了包含它的结构
之外。** 一个在包含它的结构之前就被决定的层,不是结构的成员,是伪装成成员的输入。

── 症状 1:`--target x86_64-linux` 报告 `-gnu` ────────────

    Target x86_64-linux-gnu → x86_64-unknown-linux-gnu
           c-abi   musl   (openkal-musl@0.3.3, graph)

名字与它下面那一行矛盾,而构建成功了。

三元组同时充当**身份**(输出目录、缓存键、`cfg()` 的主语)和**请求**。
身份必须是全的,请求必须能说「没指定」。`parse` 用自动填充让身份变全,
代价是请求消失 —— 两态在下游不可区分。

⚠️ 修法是窄的,而窄是量出来的:读 `env` 的共 **22 处,其中 10 处在
triple.cppm 自己里**。保留填充,另记 `Triple::envExplicit`。

⚠️ `operator==` 不能再 `= default`:`envExplicit` 记录的是来源不是身份,
默认比较会让用户写的 `x86_64-linux-gnu` 与 `host_triple()` 推出的不相等,
第一个坏掉的是 `mcpp toolchain list` 的 `host` 标记。

⚠️ 请求**必须在规范化之前捕获**。`str()` 渲染的是填好的身份,
`overrides.target_triple = parsed->str()` 之后再 parse 就分不出来了 ——
第一版正是在这里丢的,`--target x86_64-linux` 被当成显式 gnu 而遭拒。

── 症状 2:约定在图之前应用,而它的问题在图之后才有答案 ──

`x86_64-linux-musl → gcc@16.1.0` 说的不是「偏好 gcc」,是
「musl-gcc 载荷供给这个目标的 C 库」。工程的 C 库若来自依赖图,
该载荷根本不被使用 —— 而这只有解析完图才知道。

⚠️ 早决定被**双向实测**否掉:无条件应用会替换用户用 `mcpp toolchain default`
设下的工具链(上一版的做法);不应用会让一个零依赖的交叉构建从可用变为不可用
(更早那一版被回退的做法)。两者都错,因为都在猜一个尚不存在的事实。

判据换成它本来就该是的那个:**图供给 `kernel-abi` 或 `c-abi` 时,约定不适用。**

⚠️ 代码不搬,只搬执行时机:原地包成 `resolve_target_toolchain` lambda,
在 `packages` 填好之后调用。先前记录的「39 处读写挡着」是**没测就写下的** ——
实测依赖解析段读 `tc` 仅 **1 处**,而那一处要的是**三元组不是编译器**
(`tc->targetTriple` 在检测后几行就被改写成请求的三元组,两者同值)。
逃逸出 lambda 的局部只有 `tcSpecIsMsvc` 一个,提到外层即可。

── 实测 ────────────────────────────────────────────────

| 场景 | 结果 |
|---|---|
| openkal 工程 + 全局 llvm 默认 + `--target x86_64-linux-musl` | `Resolved llvm@22.1.8` ✅ |
| 无依赖工程 + 同一目标 | `Resolved gcc@16.1.0`(约定生效)✅ |
| 无依赖工程 + `x86_64-windows-gnu`(先前的回归点) | `Resolved gcc@16.1.0` ✅ |
| `--target x86_64-linux` + 图供 musl | 构建成功,报告写 `Target x86_64-linux` ✅ |
| `--target x86_64-linux-gnu` + 图供 musl | 拒绝,指出请求与事实 ✅ |

test: test_targetside 31 passed(新增请求语义三格);unit 93 passed;
      e2e 新增 282;全量 e2e 进行中,至此无新增失败。

* fix(target): report the name/fact mismatch instead of refusing it

⚠️ 上一版的拒绝是语义上干净的答案,而它在 mcpp 自己的 openkal 矩阵上第一个开火:

    $ mcpp build --target x86_64-linux-gnu        # openkal 工程
    error: this build requests the `gnu` C ABI, and its dependency graph
           supplies `musl`.

`x86_64-linux-gnu` 正是 `mcpp toolchain list` 打印的宿主目标拼写,
因此也是人们会写的那一个。拒绝它等于打破每一个已经这么写的工程与 CI 配置。

判据是该请求**不改变任何东西**:图两种写法下都供给同一个 C 库,
所以那一段是**被忽略而非被违反**。一个去掉它也完全相同的构建,不是该被拒绝的
构建 —— 它是一个名字与自身不符的构建,而说出来就是全部的补救。

    warning: the target name asks for the `gnu` C ABI and the dependency graph
             supplies `musl`.
           The graph decides, so the build below uses `musl` — the name is what
           is inaccurate, not the artifact. Drop the segment to say what is
           actually meant:
               --target x86_64-linux

⚠️ 补救必须可执行,而这正是 `x86_64-linux` 必须先能用的原因:
告诉一个人他的目标名字写错了,只有在存在一个对的名字可以给他时才有用。

test: unit 93 passed;e2e 268/280/281/282 全绿。
spec: SPEC-002 §3.4 改为「必须报出、禁止据此失败」,并记下被否掉的那一版。

* feat(examples): one source over four machines, and a chapter for the model

── examples/06-openkal-cross ──────────────────────────────

一个「询问它落在哪台机器上」的程序,从任何宿主构建到四个目标而不修改一个字符。
`src/main.cpp` 里**没有一条预处理指令,也没有一处按目标名分支**。

实测,同一份源码、同一台 Linux 宿主、两个目标:

| 事实 | x86_64-linux | x86_64-windows-gnu |
|---|---|---|
| 预开目录数 | 2 | 5 |
| 路径区分大小写 | yes | **no** |
| 单调时钟粒度 (ns) | 1 | **100** |

代码路径处处相同,答案不同 —— 这正是「询问而不假定」与「在若干套 `#if` 下
编译」的区别。选的几项查询各自压到目标侧的不同层,而其中展开那一项最严:
展开是 C++ 运行时里**无论是否可用都能链接成功**的那一部分,
一个没有展开器的程序照样构建,只在抛出时失败;析构函数在展开中运行,
才把两者分开。

⚠️ 而这个程序在移植过程中挖出了一条规范缺陷 —— 见 openkal-opensbi#2:
一个**一次文件系统调用都没有**的程序,仅仅因为「询问是否存在文件系统」
就在裸机后端上链接失败,因为查询是属性对象上的 inline 函数。
读规范两轮没发现,写一个程序跑一次就挖出来了。

── docs/15(中英)────────────────────────────────────────

模型、工程书写什么、生态供给什么、以及三条已实测的界限。

⚠️ 顺带修掉一处我自己刚引入的语义错误:请求检查把 env 段一律当作 C 库请求,
而**在 Windows 上那一段命名的是对象 ABI**(`gnu` = PE/GNU,`msvc` = PE/MSVC),
两者都与不止一种 C 库相容。把 Windows 构建报成「请求了 gnu C ABI」描述的是
一条该名字从未涉及的轴,而它建议的更正指向一个不存在的目标。
检查因此限定在该段确实命名 C 库的平台上 —— 这一点我自己的 §4 分析写过,
实现时没照做。

test: unit 93 passed;e2e 268/280/281/282 全绿;
      示例四个宿主目标(host / x86_64-linux / aarch64-macos / x86_64-windows-gnu)
      全部构建通过,Linux 与 wine 下均运行。

* docs: the two routes to a bare x86_64 machine are different things

UEFI 是其中一条,而不是全部。补上另一条:x86 内核开发。

| 路线 | 目标 | 平台层 | 入口 |
|---|---|---|---|
| UEFI 应用 | `x86_64-windows-gnu` | `openkal-uefi` | 固件,Boot Services 可用 |
| 内核 / 裸机 | `x86_64-none-elf` | 无,或 `openarch` | 复位向量,其下一无所有 |

区别在于**谁加载这个程序**。UEFI 应用有固件服务可调,因此它是 PE/COFF、
经微软 x64 调用约定进入,与 Windows 程序共用三元组;内核没有,
因此它是零 libc 档的 ELF,在自己的 `_start` 被进入并直接触达硬件。

⚠️ `openarch` **不应答 `mcpp:kernel-abi`**,写清楚这一点是必要的:
它不是平台接口,而是架构机制 —— 执行上下文、陷阱、每 CPU 状态、地址空间 ——
跨若干指令集的一个接口,每个指令集一个后端包。一个内核依赖它,
并供给自己的平台层,或者不供给。

── 并记下 x86_64 裸机为何需要引擎侧的工作 ────────────────

`riscv64-none-elf` 与 `aarch64-none-elf` 只是表中的行:clang 对两者都有
BareMetal 工具链。它对 x86_64 没有,于是那个三元组落到通用 GCC 工具链,
而后者的链接器是**宿主的 g++**:

    g++: error: unrecognized command-line option '-fuse-ld=…/ld.lld'

对裸 x86_64 三元组的每一种拼写都实测过且无法由任何 flag 纠正,
因此该行携带链接器 emulation,由 mcpp 自己调用 `ld.lld`。

示例 README 同步:该目录里的程序在内核路线上跑不起来,
因为它向平台接口提问而那里没有平台接口 —— 说明白比含糊带过有用。

* docs: "identical either way" was measured, and it was not quite true

我写了「两种写法下产物相同」,然后去核验这句可证伪的话:

    x86_64-linux       b9bcf550…
    x86_64-linux-musl  481b27d9…      ← 不同
    strip 之后         5a3ed2e3… 两侧一致
    .text 单独取出     ba856ed6… 两侧一致

⭐ 代码是同一份代码,差异整个落在**调试信息**里 —— 它记录了输出目录,
而目录以三元组命名。原话按字面是假的,而它想说的那件事是真的,
于是把测出来的东西写进去,而不是把话说得更满。

── 顺带记下验收结果 ──────────────────────────────

用报告里那两条命令实测(2026.8.24.3,本机):

    --target x86_64-linux        Target x86_64-linux → …-gnu
                                 c-abi musl (graph)          标题不再自相矛盾
    --target x86_64-linux-musl   exit 0                      原先是「未知目标」
    --target x86_64-linux-gnu    warning + 可粘贴的改法       原先静默

第三条的提示给出 `--target x86_64-linux` 这一行可直接粘贴,
因为「说清楚哪里不对」和「说清楚该怎么写」不是同一件事。

* docs(examples): the link error was the specification working, and I had it backwards

示例声称「一份源码,四台机器」,其中第四台是 riscv64 裸机。它跑不起来,
而我上一轮把原因定成了后端的缺陷,给 `openkal-opensbi` 加上了

    const kal_uintptr kal_fs_props   = 0;
    const kal_uintptr kal_task_props = 0;

发布为 0.1.3 并合进了索引。⚠️ 这是规范明令禁止的那一种补法。

openkal SPEC 6.1:「实现不提供的接口,作为链接期定义是缺席的,
使用它的消费者链接失败。」6.2 的表把三个时机分开:

    依赖解析 │ 包声明它提供什么 │ 这个程序可否针对这个实现构建
    链接     │ 未定义符号       │ 是否用了它不提供的接口
    运行     │ 能力字           │ 在**它提供的接口内**它如何表现

⭐ 能力字回答的问题比它初看上去窄得多。给一个没有任何操作的接口
定义能力字,是在回答第三个问题而跳过第二个 —— 程序随后越过了
链接器存在的意义,接着撞上未定义的 `kal_fs_open`,或者认为自己
有一个零能力的文件系统。opensbi 只实现 abort/stream/memory/env/time,
一个 `kal_fs_*` 操作都没有。

于是本提交把方向反过来:**程序才是那个不该这么写的东西**。

  示例 mcpp.toml   删掉 `[target.riscv64-none-elf]`,并写明为何没有
  示例 main.cpp    「可以问一个文件系统怎样比较名字,不可以问一台
                   机器有没有文件系统」;两处正文注释原先说反了
  示例 README      新增「本程序不问什么」,把那条链接错误作为机制展示
  docs/15 中英     新增「源码是同一份,程序不是」

「同一份源码」是关于工具链与标准库的断言,它成立;它从来不是
「任何程序都能为任何目标构建」的断言 —— 后者由依赖图回答。

实测(本机 Linux 宿主,2026.8.24.3):
    x86_64-linux        ELF 64-bit LSB executable
    aarch64-macos       Mach-O 64-bit arm64 executable
    x86_64-windows-gnu  PE32+ executable (console) x86-64

上游:mcpplibs/openkal-opensbi#4 撤回 0.1.3,发 0.1.4。

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.8.24.2

Toggle v2026.8.24.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(targetside): five layers, and the largest of them had no name (#494

)

* feat(targetside): five layers, and the largest of them had no name

一次真实使用暴露了十条问题:一个 hello-world 工程,`[dependencies]` 里只加了一行
`openkal-llvm-runtime`,随后在四个目标上连续失败,每一次的错误信息都来自编译器,
没有一条提到 mcpp 作出的决定。

`01d6cef` 把「目标侧从哪来」收敛到了一处解析,但它的消费者仍停在旧模型上。
本次补齐模型本身。

── 1. 第五层:compiler-runtime ──────────────────────────────

实测 `openkal-llvm-runtime` 编译的 729 个对象里,**498 个是 compiler-rt 的
builtins**、21 个是 libunwind 的。这个包最大的一块此前声明在 `mcpp:c++-abi`
名下 —— 而 `__udivti3` 是一个纯 C 程序需要的东西,与 C++ 无关。

把 builtins 算作 C++ 运行时的一部分,与本文件开头记录的那个缺陷同形:
一个交叉到 macOS 的 C 程序被问「有没有 C++ 运行时」,答「没有」,
链接行因而保留了载荷自带的 libc++。**一个只有部分程序需要的层仍然是层。**

`compiler` 同时成为一层,因为规则二需要一个被检查的对象。它是唯一一个
包不能供给的层:族与族之间的差异(flag 拼写、模块模型、BMI 格式、驱动 cfg)
是引擎必须持有的事实,不是数据能描述的。

⚠️ `compiler` 层上报的是**族名**(`llvm`)而不是驱动名(`clang`)。
使用者书写的每一处都用族名,报告用驱动名会让
`requires = ["mcpp:compiler=llvm"]` 永远不可满足。

── 2. requires:在引擎里不出现实现名的前提下执行分层规则 ────

    requires = ["mcpp:compiler=llvm"]

libc++ 的源码、尤其它的 std 模块源,由 clang 编译。把它递给 gcc 会在 libc++
自己的头文件深处失败。该事实属于包 —— 在引擎里写
`if (stdlib == "libc++" && compiler == gcc)` 会把两个实现名放进引擎。

实测,修复前后同一条命令:

    error: std module precompile failed (rc=1):
      …/std.cppm:16: fatal error: __config: No such file or directory

    error: `openkal-llvm-runtime@0.1.1` requires the compiler to be `llvm`.
             compiler          gcc            (16.1.0, payload)
             required          llvm           (required by openkal-llvm-runtime@0.1.1)
           Select that compiler — yours outranks mcpp's own default:
               mcpp toolchain default llvm

⚠️ 检查在**编译开始之前**运行,这正是声明它的全部意义。

── 3. 规则一:每层恰好一个供给者 ────────────────────────────

此前两个包供给同一层时,**图遍历顺序里第一个静默胜出** —— 那个顺序既不是
作者写的,也不是他能预测的 —— 而落选者的 `[build]` 段仍然进入命令行,
于是一份 C 库的头配另一份 C 库的实现。

C 库、内核接口、C++ 运行时是**互斥的选择**,不是可叠加的贡献;
`[build] runner` 早已按同一条规则处理。判据是失败模态:选错不会让链接失败,
会得到一个能跑、偶尔崩的程序。

── 4. std-module* 从 [package] 移入 [build] ────────────────

模块源是这个包的一个翻译单元 —— 它以包的 include 目录与定义被编译,
引擎自己的注释早就这么写了。放在 `[package]` 下损失的恰恰是位置所决定的那件事:
`[build]` 可条件化而 `[package]` 不可,于是一个在多种 C 库之上供给同一 C++
运行时的包,无法为不同 C 库给出不同的 flag。`-D_GNU_SOURCE` 对 musl 与 glibc
是对的,对 picolibc 是错的,而此前没有拼写能表达这个差别。

`[package]` 写法保留为别名。

── 5. 报告按需暴露 ────────────────────────────────────────

零配置构建的五个层全部解析自同一份载荷,五行 `(payload)` 回答的是无人提出的
问题。默认只列出来源不是编译器载荷的层;`MCPP_VERBOSE=1` 列出全部;
**诊断始终列出它所依据的每一层**,包括平凡的那些 —— 省略证据的错误信息
无法被读者复核。

    Target x86_64-linux-gnu                                     ← 零配置:一行

── 6. Family 去掉 OpenkalLlvm ──────────────────────────────

它命名同一份 llvm 载荷,携带的是一条关于目标侧的事实,而 `mcpp.targetside`
从包的声明中解析该事实。保留枚举项的代价不止是一条死分支:
可用工具链列表按族枚举,一份载荷挂在两个族名下就出现两次,
而安装状态按族记录 ⇒ **第二份被报成未安装,并被推荐给已经装了它的人。**

拼写归一为 `llvm` 保留(e2e 269 守着)。

── 7. 词表 pin 替换用户默认时,说出来 ──────────────────────

⚠️ 让全局默认压过词表 pin 的做法被实测否掉:一个无依赖的工程、全局默认
`llvm@22.1.8`、`--target x86_64-windows-gnu`,**从能构建变成不能构建** ——
clang 单独不携带该目标的 C 运行时,而行所命名的载荷携带。那是升级把一个
可用的构建变成不可用的。

行所 pin 的不是「偏好的编译器」,而是「供给该目标 C 库的载荷」;
用户的默认能否代替它,取决于是否有别的东西供给目标侧 —— 而那要到依赖图
解析之后才知道。因此保留行为,并补上两条它一直欠缺的话:

    Resolved gcc@16.1.0 → x86_64-windows-gnu → …
             target default for x86_64-windows-gnu, replacing your llvm@22.1.8
             — override with `[target.x86_64-windows-gnu] toolchain`

    warning: this project's target side comes from its dependency graph, so
             llvm@22.1.8 would have served x86_64-windows-gnu.

第二条在图已知之后发出 —— 那是「这次替换本可不必」第一次可判定的时刻。

⚠️ 结构性修法是把 pin 的决定与目标侧一样后移。未在本次落地:`tc` 在解析后到
图之间被读写 **39 处**(有效三元组、cross flag、目标 sysroot、MSVC 运行时契约),
在那里重解析会让它们乱序重做。

── 兼容性 ────────────────────────────────────────────────

* `hosted-standard-library` 继续表示 C++ 层;
* `openkal-llvm` 拼写继续解析;
* `[package]` 下的三个 std-module 键继续被接受;
* 实测:**旧引擎(2026.8.24.1)读带 `requires` 的清单构建成功** ——
  TOML 侧忽略未知键,xpkg 侧警告而非报错。已发布的包因此可以先行声明。

── 测试 ──────────────────────────────────────────────────

单元:test_targetside 26 个(五层 × 四来源、能力语法五层全覆盖、
规则一二各自的诊断文本),全套 93 passed / 0 failed。

e2e:新增 280(五层与报告收窄)、281(两条规则各自的拒绝 + 包不得供给 compiler);
268/269 的断言改用 MCPP_VERBOSE 读取全栈 —— 它们的意图不变,
变的是默认报告不再打印载荷层。

⚠️ 本机 e2e 279 条中 26 条红,**逐条与基线二进制对照后全部为既有失败**
(本机全局默认是 llvm,而它们断言 GCC 的 `gcm.cache`)。CI 是判据。

设计文档:.agents/docs/2026-08-24-target-side-design.md
规范:docs/spec/target-side.md(SPEC-002)
使用文档:docs/14-target-side.md + docs/zh/14-target-side.md

* fix(targetside): whether an absent layer prints must not depend on its row

自查发现:`format_layers` 用「到目前为止有没有打印过东西」来决定一个缺席的层
要不要打印,而这让它依赖**行序**。

裸机构建的 `kernel-abi` 是缺席的,并且排在预制的 `c-abi` 之上,于是:

    Target riscv64-none-elf
           c-abi             picolibc-riscv (xim:picolibc-riscv@1.8.12, prebuilt)
           c++-abi           —

缺席的 `kernel-abi` 被吞掉了,而下方同样缺席的 `c++-abi` 打印了 ——
两个同为「这一层没有供给者」的陈述,一个在场一个不在场。

「一台裸机没有内核」正是这份报告要说的话之一。改成两遍:
先判定这次是否展示整栈,再逐行输出。

    Target riscv64-none-elf
           kernel-abi        —
           c-abi             picolibc-riscv (xim:picolibc-riscv@1.8.12, prebuilt)
           c++-abi           —

test: TargetSideReport.AnAbsentLayerAboveAPrintedOneIsStillPrinted;
      test_targetside 27 passed。

* docs(changelog): 2026.8.24.2

* fix(manifest): an unknown layer name from a dependency is a version gap, not a typo

⚠️ 这条由生态 CI 实测暴露,而它让层名词表**对已发布的包永远不可扩展**。

`openkal-llvm-runtime` 声明本次新命名的那一层,被上一版引擎读到:

    error: dependency 'openkal-llvm-runtime': mcpp.toml: error:
           `provides = ["mcpp:compiler-runtime=compiler-rt"]` names no
           capability mcpp knows.

保留前缀是闭集,为的是让**拼写错误成为错误而不是一个被静默禁用的行为**。
在解析期直接拒绝,让这个闭集在第二个、没人打算要的意义上也闭上了:
一个声明了「读者发布之后才被命名的层」的包,**整份清单加载不了**。

── 谁的清单决定答案 ──────────────────────────────────────

* **根工程**的清单里出现未知层名 ⇒ **错误**。那是作者自己的拼写,
  而他正看着这次构建。
* **依赖**的清单里出现未知层名 ⇒ **警告并忽略该层**。那份清单是对着一个更新的
  引擎写的,而「忽略未知的并说出来」正是本引擎对其它每一种未知键已有的做法
  (`warn_unknown_xpkg_keys` 的注释:should not fail outright, only tell the
  user what it ignored)。

⚠️ 警告放在扫描**全部包**的那个循环里,不放在 `warn_unknown_xpkg_keys`:
后者只走到经索引解析的依赖,而 path / git 依赖自带清单,先前一条警告都收不到。

⚠️ 这条规定只对**此后的**引擎生效。一个包若要声明某个层名,其使用者的引擎仍须
不早于该层名被引入的版本 —— 本次修的是「从今往后可扩展」,不是追溯。

test: e2e 281 增两格(依赖声明未知层 ⇒ 构建成功且点名被忽略的层;
      根工程拼错 ⇒ 报错并列出五个层名);unit 93 passed。
spec: SPEC-002 §5.1 拆成两条;docs/14 中英同步。

* fix(diag): the warning must be as useful as the error it replaces

生态 e2e 暴露两处:

1. 警告只说了「不认识」,没有列出**存在的**层名 —— 而它替代的那条错误列了。
   一条比它所替代的错误说得更少的警告,是一条穿着更轻处罚的更差诊断。
   两处警告(依赖清单、任意包)改为复用 `parse_capability` 的原文。

2. e2e 268 的断言写的是**严重级别**,而这个前缀存在的理由是**不静默**。
   ⚠️ 但一条断言若跟着策略改就该说清它现在测什么:改为断言三件事 ——
   拼错的名字被报出、被拼错的那一层**没有被填上**、以及旁边拼对的那一条
   **仍然解析**(一个坏条目只该花掉一层,不是整个包)。

   删掉的那条「必须在编译开始之前被拒绝」不再成立,而它成立的地方仍被守着:
   根工程自己的拼写错误与 `requires` 不满足,都在 e2e 281 里,都在编译之前。

unit 93 passed;e2e 268/269/280/281 全绿。

* fix(targetside): name the payload's C library for its own platform

macOS CI 报出两件事,一件是我的测试写错,一件是这份报告刚刚让一个既有缺陷
变得可见。

── 1. `c-abi glibc (payload)`,在 macOS 上 ────────────────

`resolve` 对没有 env 段的三元组回落到字面量 `glibc`,而 macOS 的规范三元组
正是没有 env 段的。这条一直在,但此前报告只打印「这次构建有话要说」的那几层,
于是一个错误的标签从未被打印出来;把整栈显示出来,错标签就成了错陈述。

    c-abi             glibc          (payload)      ← macOS

改为按平台取名:macOS `libSystem`、Windows `ucrt`、其余 `glibc`;
三元组自己说了 env 的仍以它为准。

⚠️ 这几个名字可以写在引擎里,而包供给的实现名不可以 —— 区别在于载荷是 mcpp
自己分发的,它知道里面装着什么。保留能力语法存在的理由正是守住这条界线。

── 2. `c++-abi` 在 BSD grep 的 ERE 里是非法的 ────────────

    grep: repetition-operator operand invalid

`c++` 在扩展正则里是「重复算子作用于重复算子」,GNU grep 容忍,BSD grep 直接
拒绝。e2e 280 的层名循环改用 `grep -F` 定长匹配。

test: TargetSideResolve.ThePayloadCLibraryIsNamedForItsPlatform;
      unit 93 passed;本机全量 e2e 288 pass / 26 fail,失败集与基线逐条相同。

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.8.24.1

Toggle v2026.8.24.1's commit message
chore: 2026.8.24.1

The target side is resolved once, per layer, after the dependency graph is
known. Three derivations with three different criteria became one value that
every consumer reads, packages name the layer they supply through a reserved
capability namespace, and the build reports what it resolved.

The measured defect this closes: a pure C program crossed to macOS over the
openkal stack handed a Linux shared object to a Mach-O linker, because the
criterion that admitted the build and the criterion that shaped the link line
were different ones and a C program has no C++ runtime.

Claude-Session: https://claude.ai/code/session_01Q4ucduLuSsETHYJ1RkGVVD

v2026.8.21.3

Toggle v2026.8.21.3's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2026.8.21.3 —— 补上 aarch64/x86_64 的 multilib 目录,否则新的 C 库包用不上 (#481)

libdir 列命名的是 multilib C 库的子目录(picolibc 的 <march>/<mabi> 约定)。
那两行此前为空,而在索引里没有对应 picolibc 之前,空是对的。

xim:picolibc-aarch64 与 xim:picolibc-x86 现已存在。实测(列仍为空时):一个
声明 [target.aarch64-none-elf] sysroot = "xim:picolibc-aarch64@1.8.12" 的工程
拿到 'stdio.h' file not found —— 包装好了、是对的,而引擎找不到里面的 profile。

A/B:
  带修复    -isystem …/include/armv8-a/aapcs   头解析成功
  2026.8.21.2   'stdio.h' file not found

⚠️ 填这一列不会让那两个目标默认拿到 C 库:它只在 sysroot 已被解析后才被读,
而目标表对这两行仍不绑定任何 C 库 —— 零 libc 仍是默认,包仍是可选加入。
单元测试断言了这一点。

⚠️ 那条单元测试此前断言 aarch64 的 libdir 为空,而它当时是对的。事实变了,
测试跟着改并在注释里说明为什么。

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.8.21.2

Toggle v2026.8.21.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2026.8.21.2 —— 构建程序可以成功并且仍然被听见 (#479)

mcpp 只在构建程序非零退出时打印抓到的输出,于是一个「做完了正确的事、但用户
需要知道一件事」的程序没有通道。写到 stderr 的提示在最需要它的那些成功构建上
一个字都不打印;让构建失败也不对,因为构建是对的。

- 新指令 mcpp:warning= + 类型化 API mcpp::warning(),协议 4 → 5
- 新增 Scope::Advisory:每条指令都要回答「谁看到这个值」,而警告的答案是
  用户 —— 不是六个既有值中的任何一个
- ⭐ tag 非空是承重的:构建程序的结果被缓存,命中时不再运行,一条只活在运行
  路径上的提示会出现一次然后再也不出现,读起来像「问题已解决」
- kCacheEpoch 刻意不递增:旧条目里没有 warning 行,而写它的程序当时也发不出
  一条,所以旧条目仍然正确;递增会让所有工程白跑一遍

新增 tools/lint-ci-assertions.sh:三条关于「断言写在哪」的规则,由三次同源
事故导出。它打印而不失败 —— 规则的价值在于被读到。⚠️ 工具头部写明它抓不到
其中一次事故:那次的缺陷是断言「缺失」,而 linter 检查写下来的东西。

修 ci-macos.yml 一处真实的同形缺陷:nm 失败时打印「0 (good)」。

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.8.21.1

Toggle v2026.8.21.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(freestanding): x86_64-none-elf,以及驱动拒绝执行的那次链接 (#472)

* feat(freestanding): x86_64-none-elf, and the link that the driver refuses

A target row is normally two table entries. This one needed engine code, and
the reason is a property of clang rather than of the instruction set.

clang selects a toolchain from the triple. It has a BareMetal toolchain for
arm, aarch64 and riscv, which reaches `ld.lld` directly; it has none for
x86_64, so every spelling of a bare x86_64 triple falls through to the generic
GCC toolchain, whose linker is the HOST'S `g++`:

    g++: error: unrecognized command-line option
         '-fuse-ld=/…/llvm/22.1.8/bin/ld.lld'

Measured for `x86_64-none-elf`, `x86_64-unknown-none-elf`,
`x86_64-unknown-none`, `x86_64-elf`, `x86_64-none-none` and
`x86_64-unknown-unknown`, and unchanged by `-fuse-ld=lld`, `--ld-path=`,
`--gcc-toolchain=` or `-B`. Only putting `linux` in the OS position changes it,
and that adds eight host `-L` paths to a bare-metal link.

Neither outcome is acceptable: a host `g++` makes the row work on one host and
nowhere else, and host search paths on a freestanding link are the hermeticity
this engine exists to keep. So the ISA-profile row carries an `lldEmulation`
column, and when it is set the engine drives the link with `ld.lld` itself.
The flag vocabulary changes with the tool — `-Map=` rather than `-Wl,-Map=`,
`-m elf_x86_64` rather than `--target=` — and the driver-only flags are dropped
rather than translated, because the first attempt left two behind:

    ld.lld: error: unknown argument '-nostdlib++'
    ld.lld: error: unknown argument '-Wl,--disable-new-dtags'

The column is empty for the riscv and aarch64 rows: their driver already
reaches lld, and changing a working link to make three rows look alike is how a
regression is introduced. Their link lines are byte-identical to before.

A second new column, `extra`, carries `-mno-red-zone`. That is not a preference
either. The System V ABI's 128-byte red zone is safe on a hosted system because
the kernel switches stacks for interrupts; on bare metal the processor pushes
the interrupt frame into it, and the interrupted leaf function resumes with its
locals overwritten — no fault, no diagnostic, and only when an interrupt
happens to land inside a leaf. There is no bare-metal x86_64 program for which
the red zone is safe.

Verified: `tests/e2e/137_x86_64_zero_libc_target.sh` asserts the image is
x86-64, loads where the script says, has zero undefined symbols, names no
dynamic loader, produced a link map through a linker-native flag, went through
`ld.lld`, and carries `-march=x86-64` and `-mno-red-zone`. The 92 unit tests
pass unchanged.

Also verified outside this repository: openarch's probe — one source file —
builds and runs on riscv64, aarch64 and x86_64 with byte-identical output.
That is the first evidence its interface abstracts rather than merely fits two
similar RISC machines.

* docs(plan): 第 3、4 节按 0.4.0 与 2026.8.21.1 的实际落地修订

⚠️ 两处本方案原先判断错了,记在文档里而不是悄悄改掉:

1. §4 断言 x86_64 目标行「不是代码,是模拟器载荷」。实测下来它需要引擎代码 ——
   clang 没有 x86_64 的 BareMetal 工具链,于是每一种裸 x86_64 triple 的链接都落到
   宿主的 g++ 上。

2. §3 给的目录树是「根为虚拟 workspace、接口在 spec/ 成员里」。那个形状会让根上的
   跨编译对所有成员扇出,把 aarch64 汇编喂给 riscv 汇编器。混合式的根修好了它。

以及一处排序错误:第 5 项(x86_64)原先排在最后,理由是它被外部依赖卡住。它却是
唯一一项改变了对已完成工作之信心的 —— 第三台机器挖出三条两台 RISC 机器合起来也
看不见的东西。「被卡住」和「价值低」在列表上看起来一样,而它们不是一回事。

* fix(features): `mcpp test` compiled sources a feature had not activated

Same package, same manifest, two commands, different source sets:

    mcpp build --target riscv64-none-elf   skips src/kal/**   (correct)
    mcpp test  --target riscv64-none-elf   compiles it, and dies on
                                           'openkal/abort.h' file not found

The header arrives through that feature's `[feature-deps]`; with the feature
inactive it is not there, and neither are the files that include it.

⭐ TWO FAMILIES REACH THIS CODE AND THEY WANT OPPOSITE THINGS.

  gtest         lists `*/googletest/src/gtest_main.cc` in base `sources` AND
                under `features.main`. The package provides the file
                unconditionally; the feature is a gate over it, and the
                dev-dependency track's per-test main detection must SEE it in
                order to prune it per test.

  riscv-virt-rt names `src/kal/**` under `features.openkal` and nowhere else.
                Those files are not part of the package without the feature.

The engine gated the whole exclusion on "is this a test build", which is right
for the first family and wrong for the second.

⚠️ THIS IS THE FOURTH ATTEMPT, AND THE THIRD WAS ABANDONED ON A MISTAKEN
READING. It was recorded as failing because "gtest's base entry is a glob that
MATCHES the file rather than the same string". Measured against the descriptor
the index actually carries — `compat.gtest.lua` lines 71-73 and 90 — the two
entries are byte-identical. The reason had been written from memory without
opening the file.

What actually defeated that attempt was WHEN the criterion was applied: it
tested membership against `bc.sources` after `drop()` had already removed the
entry, so the test could only ever be false.

The fix snapshots the base globs BEFORE the drop and uses them only in test
mode: a glob in both places is a gate and stays visible; a glob in one place is
a provider and gets the `!` exclusion. The exclusion is the whole mechanism —
`src/kal/**` is never in `bc.sources` at all, because that package declares no
`sources` and its files are matched by the inferred `src/**`, so erasing the
string erases nothing.

Verified, all three of the plan's criteria:

  1. riscv-virt-rt: `mcpp test` no longer compiles src/kal/**; both commands
     exit 0.
  2. mcpp's own 92 unit tests still link and pass (the gtest family).
  3. tests/e2e/138_feature_sources_gate_vs_provider.sh covers both families
     with two minimal packages and no ecosystem dependency.

⭐ (3) was checked the way a regression test has to be: run against the engine
before this commit it fails with exactly the symptom above; against the engine
after it, it passes. The defect survived this long because no test covered both
families at once, and each of the three earlier attempts broke the one it did
not cover.

* fix(freestanding): 直连链接下的共享库,用话说清楚而不是丢一条 ninja 规则

直连链接路径定义 cxx_link / c_link / cxx_archive,不定义共享库规则 —— 没有加载器
的目标没有东西去加载它。缺这条检查时,失败长这样:

    ninja: error: build.ninja:88: unknown build rule 'cxx_shared'

对一个在清单里写下 kind = "shared" 的人,这句话指的是一条内部规则。

⚠️ 补上那条规则比省掉它更糟:ld.lld -shared 会成功,产出一个那台机器上没有任何
东西能加载的对象 —— 静默的无意义,而不是响亮的拒绝。

e2e 137 加了第 7 条断言,同时检查两件事:拒绝时提到了原因,且**没有**提到内部
规则名。

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

Back | FazBrowse Home | New Git URL