Resolve the target side once, and let every translation unit read it - #515
Merged
Conversation
⭐ 与 2026.8.25.x / 2026.8.26.1 是同一族的下一层。那两批修的是「谓词问错了」
与「答案没接到决定上」;这一批里,答案接上了一个消费者,而它有五个。
## ⭐⭐ 编译侧的谓词是 2026.8.26.1 在链接侧修掉的那条的孪生兄弟
`hostflags.cppm` 问 `!crossTargetFlag.empty()`——「命令行上有没有 `--target=`」——
而它自己的注释写的是「目标侧来自图」。同机、同编译器、同目标,只差写不写
`--target`,编译线少六个 token:
--no-default-config -nostdinc++
-isystem <payload>/include/c++/v1
-isystem <payload>/include/<triple>/c++/v1
-isystem <glibc>/include
-isystem <linux-headers>/include
⇒ 头文件来自一个库,目标文件链自另一个库。两侧现在读同一个
`plan.targetSide.cAbi.prebuilt()`,由 prepare 记在 `Toolchain` 上供三个生产者共用。
⚠️ `--no-default-config` 从该条件里拆出来无条件发:它不是载荷头文件集合的一部分,
而 cfg 按 post_install.cppm 自己的说法是「per-machine, per-install-path artifact」。
这也解释了报告者那个手写 `<triple>-clang++.cfg` 的 workaround 为什么有效。
⚠️ e2e 295 写的就是这条恒等式,而它只比对 `^ldflags` ⇒ 恒等式在下一行不成立而测试
看不见。现在两条都比,并断言两侧非空。
## ⭐⭐ 载荷目录名是 LLVM 词汇,查找用的是 mcpp 词汇,且失配静默
`include/<triple>/c++/v1` 由 LLVM 的构建写下(`x86_64-unknown-linux-gnu`),
`tc.targetTriple` 是 mcpp 的(`x86_64-linux-gnu`)。两者只在三元组是探测来的时候
恰好相同。查找是 `if (exists) push_back`,找不到就什么也不发生 —— 而那个目录里
只有 `__config_site`,它的缺席产生的报错读起来像载荷坏了。两种拼法都试。
## ⭐⭐ 由图供给的目标侧只到达一个编译单元(mcpp#514 §A)
提供 `mcpp:` 层的包发布的是整个目标编译时所依据的头文件集合,而它以 publicUsage
的形态沿依赖边传播 ⇒ 根与 provider 自己的单元拿得到,兄弟依赖包拿不到。结果是
一次构建里两种口味的 BMI(`reference to 'space' is ambiguous`)。
目标侧解析后,`fromGraph()` 的层的 publicUsage 并入每一个包的 privateBuild;
std 模块的命令行改读同一个集合,不再自己推一遍。
## ⭐⭐ 缓存键描述了编译器,没描述它被指向的头文件集合(mcpp#514 §B)
`driverIdentity` 按设计覆盖不了它 —— `normalize_driver_output` 故意抹掉路径,
好让一个条目能被两个 home 共享。新增 `targetHeaderSet` 轴,取自已解析的
linkmodel,store-相对化。不 bump kCacheEpoch:旧条目是 miss 不是不可用。
## ⭐ home 发现有第四份拷贝,而且会伸到别的 home 去
`active_home_xpkgs()` 自己重推一遍 home(漏掉自包含安装那一档);
`find_sibling_package` 无条件回落 `~/.xlings/data/xpkgs` ⇒ 密闭构建可以从另一棵树
取载荷,结果直接进每条编译命令的 `-isystem`。前者改读 `mcpp::home::root()`,
后者删除 —— 找不到会说话,找错了不会。
## 新增 `[build] private_include_dirs`
指出 include_dirs 中在本包边界处停住的条目。publicUsage 此前整份接过
privateBuild,于是内嵌带内部头覆盖层的库(musl 的 src/include 定义
hidden/weak/weak_alias)会把那些宏发给每个消费者。openkal-musl 的
port/include/features.h 自己写下了这个缺口和它的第一优解。
⚠️ 它是 include_dirs 的子集而不是第二个列表:两类目录的相对顺序是承重的,
而两个 TOML 数组表达不了一个顺序。
## 判据
- 单元 93 passed / 0 failed(新增 3 条,均确认真的在跑,非 SKIP)
- e2e 295 扩到 cxxflags:修复后两工具链四条全绿;未修二进制上退出码 1 并列出六个丢失 token
- e2e 304(新):目标侧到达兄弟依赖包 —— 未修二进制上变红
- e2e 305(新):编译用/发布用分开 —— 未修二进制上变红
- 三条 e2e 均带分母(CDB 行数),空 CDB 不会假绿
Closes #514
Sunrisepeak
force-pushed
the
feat/target-side-reaches-every-unit
branch
from
August 27, 2026 01:40
9427f15 to
31fa043
Compare
它在写下它的那台机器上编过了,在 Windows CI 上编不过 —— 而 Windows 是这个项目 一半的面唯一可见的地方。改成固定名 + 先 remove_all:gtest 在一个二进制里串行 跑测试,一个名字就够。
⚠️ ⚠️ 这条不是本 PR 引入的 —— `mcpp_ref=main` 的对照 run 报同一处错。 它是索引把 `xim:glibc` 在同一系列内移动之后,**任何**从源码构建 mcpp 的 全新 CI 机器都会中的一条,今天开始出现。修在这里,因为它卡住了本 PR 的门。 ## 机制 RuntimeBinding 带的是**声明的**身份;xlings 把载荷目录按请求**解析成**的版本命名。 索引把 `xim:glibc@2.44` 解析到 `2.44.2` 的那一刻,两者分开: error: selected RuntimeBinding glibc@2.44 requires payload '…/xim-x-glibc/2.44', but it is not installed $ ls …/xim-x-glibc/ → 2.44.2 ⭐ `runtime_binding.cppm` 早一个版本就记下了同一形状(「observed in CI as runtime=glibc@2.39 with libc.so.6 resolving to the managed 2.44 payload」) 并做了归一 —— 但只在 SubOS 视图存在之后。而这一处跑在**第一次**装工具链时, 那时还没有视图可读。 ## ⭐ 是「精化」不是「按目录顺序挑」,而这个区别是那条拒绝还站得住的全部理由 `2.44.2` 是请求 `2.44` 解析出来的东西:它的版本**分量**以请求的分量开头。⚠️ 逐**分量**比,不是逐字符:`2.4` 不是 `2.44` 回答的那个请求。按字符串前缀比 会说它是,然后把一个错的 C 库交给构建而什么也不说。⚠️ 若有两个载荷都精化了这个请求,这里**不做选择** —— 原样报拒绝。 「这个请求的解析结果」必须是**一个**载荷才谈得上是个答案。 ## 判据(5 条,四个方向) - 精确版本在场时优先(不会被精化抢走) - 唯一精化被采纳 - `2.4` 不匹配 `2.44`(逐分量) - 两个精化 → 仍然拒绝,且消息说明为什么 - 什么都没装 → 仍然拒绝
⚠️ ⚠️ 这是那条 `glibc@2.44 ... not installed` 的**主因**,而它在索引里被逐字 预告过。`xim-pkgindex` 的 `pkgs/g/glibc.lua` 记着一对提交: fb289b8 glibc: withdraw 2.44.2 until the client that can resolve it ships 557e862 glibc: restore 2.44.2 now that the client resolving it has shipped 以及那次失败的原文: -- released xlings pinned `glibc@2.44`, payload directories are named -- after the version, and so a clean environment installed 2.44.2 and -- then found nothing answering to the binding -- error: selected RuntimeBinding glibc@2.44 requires payload -- '<home>/.../xpkgs/xim-x-glibc/2.44', but it is not installed -- on every NEW environment and on none that already existed. -- The index is DATA and the client is a PROGRAM: the consumer ships first. ⭐ 那个消费者是 **xlings 2026.8.27.1**(openxlings/xlings#567):它改为从索引文件 读默认 runtime binding,而不是读一个编译进客户端的常量。mcpp 钉的还是 8.17.2, 所以每一台冷缓存的 CI 机器都停在上面 —— **在 main 上和在任何分支上一样**。 ## ⭐ 与前一条提交的关系(两条,不是一条的两半) | | 覆盖谁 | |---|---| | 本条(pin) | **新环境**:xlings 从索引读绑定,直接绑到 2.44.2 | | 前一条(`select_glibc_payload_lib`) | **已存在的环境升级**:subos 记下的绑定还是 `2.44`,而载荷已经是 `2.44.2`,且 SubOS 视图尚未建立 —— `runtime_binding.cppm:224` 早一个版本就记过这个形状 | 两者在不同的层,谁也替代不了谁;去掉后者,一台已经跑过一次的机器在升级后仍会停住。 ## 判据 `.github/tools/check_version_pins.sh` 是这件事的唯一真源检查器,它一次列出了 全部 7 个不同步的点(3 个 workflow + 2 个 composite action + 2 个 release 段), 同步后退出码 0。**这正是它存在的理由** —— 注释里说过,手工维护的清单曾漏掉 两个 composite action,让 CI 的沙箱在 0.4.30 上停了很久没人发现。
This was referenced Aug 27, 2026
ninja 通道会给需要引号的路径加引号,于是同一个 flag 到达时是 `"-fprebuilt-module-path=C$:\Users\..."` —— 锚定的模式匹配不到它, 比对随后就栽在这个过滤器本来要去掉的那个 token 上。 在 windows-x86_64 上实测:它把一条本该绿的恒等式变成红的,而原因与被编译的 东西毫无关系。改成子串匹配。⚠️ Linux 侧仍然能在未修二进制上变红(已复验)。
第一版把解析写在了工具链 post-install 里。工具链于是装得上了,而**编译线**的载荷
发现(`probe.cppm` 的 `payload_root_for_binding`)自己拼了一遍同样的查找,继续找不到
—— 而它的失败**连版本都不提**:
bits/os_defines.h:39: fatal error: features.h: No such file or directory
因为 glibc 的 include 目录压根没被加上。在 openkal-musl 的 CI 上实测,就在
post-install 那一处修好之后。
⭐ ⇒ 抽成 `mcpp::xlings::paths::payload_dir_for_version`,两个调用方都读它。
这正是这个仓库反复付过代价的那个形状(#233/#240/#242/#344):**同一个决定推导两遍,
修好一处会让另一处更难被发现**,因为症状换了一副样子。
## 判据
单测从断言「那个包装函数」改为断言**共享的解析器**本身,四个方向:
精确优先 · 唯一精化被采纳 · 两个精化不是答案 · `2.4` 不匹配 `2.44`(逐分量)。
包装函数那五条保留,它们覆盖的是「拿不到 lib 目录时仍然拒绝」这一层。
`build_program.cppm` 的开篇写着这条不变量 ——「`tc` 永远是一个宿主目标的工具链」 —— 而原生分支直接返回主 `tc`。对除此之外的每个字段它都成立(原生构建里编译器 就是宿主编译器);`cAbiPrebuilt` 是「同一个编译器」与「同一个目标侧」第一次分开的 那个字段。⚠️ **这是一条不变量,不是任何已测失败的修复。** 它是在追 openkal-musl CI 上那条 `features.h: No such file` 时写下的,而**它不是那条的成因** —— 在 origin/main 和 本分支上实测,gcc 的 std 模块两边都是零个 `-isystem`/`-idirafter`:那条工具链是 通过 post-install 重写的 specs 到达它的 C 库的,真正的缺陷在于**那些 specs 指名的是 哪一个 glibc 载荷**。 保留它,是因为这条不变量值得为真:一个在构建机上编译并运行的助手程序不该继承目标的 C 库出处,而下一个分开的字段将在这里找不到规则。
⚠️ ⚠️ **bump 是对的方向,但在这次过渡里它制造了一个新的失败模态,而且比原来那个更糟。** ## 实测出来的因果 `acquire_xlings_binary` 会在 pin 前移时**更新沙箱里那份 xlings** (`fallback/xlings_binary.cppm:55`)。而沙箱那份才是解析 runtime binding 的那个 —— `.github/actions/bootstrap-mcpp/action.yml` 自己的注释就写着这件事: The SANDBOX xlings is a separate copy: mcpp vendors it into ~/.mcpp/registry/bin at `self init` and never revisits it … and it — not the system binary — is what resolves dependencies. 于是在一次 CI 里发生的是: 1. 引导用的已发布 mcpp(沙箱 xlings 8.17.2)绑 `glibc@2.44`,装出 `2.44.2` 2. 我的构建把沙箱 xlings 升到 2026.8.27.1,绑定**前移**到 `glibc@2.44.2` 3. 而已经装好的是 `2.44` ⇒ 请求比已装的**更新** ⭐ 第 3 步正是 `payload_dir_for_version` **正确地不覆盖**的方向:接受一个更旧的载荷 去回答一个更新的请求,等于静默地用上索引明说「IT IS NOT THE ONE TO INSTALL」的那份 —— 它带着 `mcpp#484` 的 preload 缺陷。**拒绝是对的。** ## 保留的是哪一半 `payload_dir_for_version` 保留:它覆盖的是**请求比已装的更旧**那一侧 (绑定说 `2.44`,装好的是 `2.44.2`),那是安全的方向,也是这次事件最初的形状。 pin 的前移留给一次**独立的**变更:它需要与「谁来装那个新绑定指名的载荷」一起做, 而那不是本 PR 的题目。
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的 版本命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进 自己沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要 `2.44`,构建在编译任何东西之前就停住。⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。 在 `main` 上和在任何分支上一样(已用 `mcpp_ref=main` 的对照 run 证实)。 ⭐ 待一个能解析它的 mcpp 发布之后移除:2026.8.27.1 接受「版本精化了请求」的已装载荷 (`payload_dir_for_version`),从它引导就不需要这一步了。在那之前,缺的那个载荷 直接装上。 共享的 `bootstrap-mcpp` action 覆盖 6 个 workflow;另外两个自己引导的 (`ci-fresh-install`、`ci-aarch64-fresh-install`)单独插入。
Sunrisepeak
pushed a commit
to mcpplibs/openkal
that referenced
this pull request
Aug 27, 2026
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本
命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己
沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要
`2.44`,构建在编译任何东西之前就停住:
error: selected RuntimeBinding glibc@2.44 requires payload
'.../xpkgs/xim-x-glibc/2.44', but it is not installed
⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。
索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则:
「The index is DATA and the client is a PROGRAM: the consumer ships first。」
## ⚠️ 我先试过 bump xlings,撤回了
把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉
(mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到
`glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧:
接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说
「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。
⇒ 保持 xlings 不动,把缺的那个载荷直接装上。
⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受
「版本精化了请求」的已装载荷,从它引导就不需要这一步。
Sunrisepeak
pushed a commit
to mcpplibs/openkal-linux
that referenced
this pull request
Aug 27, 2026
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本
命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己
沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要
`2.44`,构建在编译任何东西之前就停住:
error: selected RuntimeBinding glibc@2.44 requires payload
'.../xpkgs/xim-x-glibc/2.44', but it is not installed
⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。
索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则:
「The index is DATA and the client is a PROGRAM: the consumer ships first。」
## ⚠️ 我先试过 bump xlings,撤回了
把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉
(mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到
`glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧:
接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说
「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。
⇒ 保持 xlings 不动,把缺的那个载荷直接装上。
⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受
「版本精化了请求」的已装载荷,从它引导就不需要这一步。
Sunrisepeak
pushed a commit
to mcpplibs/openkal-macos
that referenced
this pull request
Aug 27, 2026
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本
命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己
沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要
`2.44`,构建在编译任何东西之前就停住:
error: selected RuntimeBinding glibc@2.44 requires payload
'.../xpkgs/xim-x-glibc/2.44', but it is not installed
⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。
索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则:
「The index is DATA and the client is a PROGRAM: the consumer ships first。」
## ⚠️ 我先试过 bump xlings,撤回了
把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉
(mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到
`glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧:
接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说
「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。
⇒ 保持 xlings 不动,把缺的那个载荷直接装上。
⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受
「版本精化了请求」的已装载荷,从它引导就不需要这一步。
Sunrisepeak
pushed a commit
to mcpplibs/openkal-windows
that referenced
this pull request
Aug 27, 2026
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本
命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己
沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要
`2.44`,构建在编译任何东西之前就停住:
error: selected RuntimeBinding glibc@2.44 requires payload
'.../xpkgs/xim-x-glibc/2.44', but it is not installed
⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。
索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则:
「The index is DATA and the client is a PROGRAM: the consumer ships first。」
## ⚠️ 我先试过 bump xlings,撤回了
把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉
(mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到
`glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧:
接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说
「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。
⇒ 保持 xlings 不动,把缺的那个载荷直接装上。
⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受
「版本精化了请求」的已装载荷,从它引导就不需要这一步。
Sunrisepeak
pushed a commit
to mcpplibs/openkal-opensbi
that referenced
this pull request
Aug 27, 2026
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本
命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己
沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要
`2.44`,构建在编译任何东西之前就停住:
error: selected RuntimeBinding glibc@2.44 requires payload
'.../xpkgs/xim-x-glibc/2.44', but it is not installed
⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。
索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则:
「The index is DATA and the client is a PROGRAM: the consumer ships first。」
## ⚠️ 我先试过 bump xlings,撤回了
把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉
(mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到
`glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧:
接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说
「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。
⇒ 保持 xlings 不动,把缺的那个载荷直接装上。
⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受
「版本精化了请求」的已装载荷,从它引导就不需要这一步。
Sunrisepeak
pushed a commit
to mcpplibs/openkal-uefi
that referenced
this pull request
Aug 27, 2026
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本
命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己
沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要
`2.44`,构建在编译任何东西之前就停住:
error: selected RuntimeBinding glibc@2.44 requires payload
'.../xpkgs/xim-x-glibc/2.44', but it is not installed
⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。
索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则:
「The index is DATA and the client is a PROGRAM: the consumer ships first。」
## ⚠️ 我先试过 bump xlings,撤回了
把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉
(mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到
`glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧:
接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说
「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。
⇒ 保持 xlings 不动,把缺的那个载荷直接装上。
⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受
「版本精化了请求」的已装载荷,从它引导就不需要这一步。
Sunrisepeak
pushed a commit
to mcpplibs/openkal-musl
that referenced
this pull request
Aug 27, 2026
`xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本
命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己
沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要
`2.44`,构建在编译任何东西之前就停住:
error: selected RuntimeBinding glibc@2.44 requires payload
'.../xpkgs/xim-x-glibc/2.44', but it is not installed
⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。
索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则:
「The index is DATA and the client is a PROGRAM: the consumer ships first。」
## ⚠️ 我先试过 bump xlings,撤回了
把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉
(mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到
`glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧:
接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说
「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。
⇒ 保持 xlings 不动,把缺的那个载荷直接装上。
⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受
「版本精化了请求」的已装载荷,从它引导就不需要这一步。
新轴的注释承诺「键要能跨 MCPP_HOME 存活」,而只做 `<store>` 一档的规则兑现不了它。
本机自己的工具链走 `CLibMode::Sysroot`,它唯一的编译 token 是
--sysroot=/home/<user>/.mcpp/registry/subos/default
—— 在 HOME 底下而不在 `<store>` 底下。⇒ 每个条目都带上了这台机器的 home,两台装着
同样载荷的机器互相命不中。⚠️ 不是错的答案,是**不可共享**的答案 —— 而
`normalize_driver_output` 正是为了保住这条性质才故意丢掉路径信息的。
补第二档 `<home>`,和 `fill_package_config` 已有的 `<store>`/`<pkg>` 同形。
从 storeRoot 推导而不再传一个参数:两者必须是同一个 home,多一个参数就多一种
它们描述两台机器的写法。
⭐ 顺带把这个轴分不分得开什么写进注释,免得下一个读者重新推一遍:
载荷带版本号 ⇒ 两个载荷两个键(#514 §B,本轴存在的理由);一个 home 下的两个 subos
⇒ 两个键(此前没有);两个 home 下同名的 subos ⇒ 仍是一个键(与此前一致,由整工程
指纹的第 11 项区分)。
⚠️ 已有的两个单测**直接给轴赋值**,所以都没跑到相对化那段 —— 只做 `<store>` 也全绿。
新增 `TheHeaderSetCarriesNoAbsoluteHome`,判据是**home 不出现**而不是「有 `<home>`
前缀」(后者在前缀后面还拖着绝对路径时照样通过)。A/B 实测:移掉这一档后它失败,
逐字打印上面那行。
另:候选去重时 `index` 改为**跟着 `p` 一起动**。两者描述同一个包,今天只有同一个
`pkgIndex` 能走到那个分支,所以还差不出来;绑在一起是为了将来也差不出来。
`payload_dir_for_version(root, "")` 里 `root / ""` 就是 `root`,而它**是**一个 目录 ⇒ 精确匹配那一支把**容器**交了出去,调用者会把它当载荷用。⚠️ 今天两个调用者都在到达这里之前拒了空版本(`probe` 靠 `at + 1 >= size()`, `post_install` 有显式的 `version.empty()` 判断)—— 这恰恰是没有任何东西会撞上它的 原因。函数是导出的,第三个调用者不会知道这条约束写在别处。⇒ 闸放在性质成立的地方, 而不是放在每个调用者那里。 A/B 实测:移掉这一行后 `AnEmptyRequestIsNotTheContainer` 失败;测试自带分母 (同一棵树上真实的请求仍要解析得出来),空结果才不会和「目录读不了」同读数。
## 旧引擎怎么读这个键 —— 实测,不是推断
在 **2026.8.26.2** 上跑:
* 出现在**依赖**清单里 ⇒ 静默接受,退 0
* 出现在**根**清单里 ⇒ `warning: [build] has unsupported key
'private_include_dirs' (ignored)`,退 0
⇒ 包可以先用上它,不必等消费者升级。这条写进中英两份文档,因为它决定了
openkal-musl 现在能不能发 —— 能。
⚠️ **唯一不成立的地方**:已发布 `xim` 描述符的 `target_cfg` 块里,不认识的子键是
**硬错误**(那段代码自己的注释就写着 "Unknown sub-keys stay a HARD ERROR here"),
会让整份清单加载失败。文档明说:索引下限指向认识它的引擎之前别写进那里。
## 消息是承诺,而我把它改得不准了
`target_cfg` 的未知键报错列出「期望哪些键」,我加了 `private_include_dirs` 却没有
把它加进那份列表 —— 于是错误信息会把一个**已经被接受**的键说成不存在。补上。
⚠️ **载荷版本解析整条没写进去** —— 而它是这一版里用户最先撞上的一条(干净机器 上引导直接停住)。补上,含判据:精化是逐**分量**比而不是逐字符,两个都精化时返回 空而不是按目录序挑,反方向不接受。 订正:`targetHeaderSet` 写的是「store-相对化」,自我 review 时实测发现只做这一档 盖不住最常见的那台机器(`--sysroot=<home>/registry/subos/default` 在 HOME 底下), 已改成 `<store>`/`<home>` 两档。顺带把这个轴分得开什么写清楚。
## e2e 304:提供层的包同时带私有目录
`openkal-musl` 就是这个形状 —— `provides = ["mcpp:c-abi=musl"]` **加上**内部头
覆盖层。层的目录到达**每一个**单元,所以在这里误发布覆盖层比在别处都宽:musl 的
`hidden`/`weak` 宏不只落到它的消费者,而是落到整个构建的每一个翻译单元。
两者按构造是相容的(目标侧取的是提供者的 `publicUsage`,而那已经是过滤后的集合)
—— 「按构造」正是需要一条断言的地方。反向对照实测:把 fixture 里的
`private_include_dirs` 拿掉,判据当场检出
/tmp/.../dep/src/sibling.cppm ← 兄弟依赖包
/tmp/.../src/main.cpp ← 根
也就是把那句「爆炸半径」从注释变成了读数。⭐ 另加控制:提供者自己的单元必须仍然
带着它,否则「没泄漏」和「整个丢掉了」同读数。
## e2e 305:glob 在展开之后比对
文档承诺「条目支持 `*` glob 并在展开之后比对」,而没有判据。加 `gen/*` 两条边:
提供者两个目录都要有,消费者一个都不能有。⭐ 判据落到 `gen/one`/`gen/two` **按名**
而不是 `gen`—— 只查 `gen` 的话,一个拿未展开拼写去比的实现照样通过。
反向对照实测:把 glob 从 private 列表里拿掉,两条都变红并各自点名。
`target/` 是**恢复出来的缓存**,键里含 `src/**` 的哈希。源码一改就 miss, `restore-keys` 照样把最近的旧树递回来(分层恢复本来就是为此),构建再把产物写进 一个**新的** `<triple>/<fingerprint>/` 目录,和已有的并排。于是 `find … | head -1` 返回的是遍历先到的那个 —— 不是最新的,也不是这次构建的。⚠️ 本机与 CI 双侧实测。本机这棵树上该命令挑到 `007bc0f2b78f7fa5`,而构建刚写的是 `148448dec6c4a7a7`。CI 上挑到的陈旧二进制,其 ELF 解释器指向 runner 上已经不存在的 glibc 载荷: …/bin/mcpp: cannot execute: required file not found (exit 127) —— 读起来像**这个 commit 构建坏了**,而完全不是。连着七次绿之后两次红、同一个陈旧 指纹:触发条件只是「源码改动大到让恢复出来的树里留下两个目录」。 ⭐ **mtime 不能当判据。** 缓存恢复会把所有文件的时间戳写成解压时刻,所以恰恰在出问题 的那棵树上「最新」是没有意义的 —— `ls -t | head -1` 在本仓库挑到过陈旧指纹。 **缺席伪造不了**:先删掉,构建之后还在的就是刚链出来的。对象和 BMI 仍在缓存里, 代价是一次链接。 ⭐ 判据是**恰好一个**而不是「至少一个」:两个就说明删漏了一棵树,这一步要消除的 歧义又回来了。⚠️ 不用 `mapfile` —— 这个 job 也在 macos 上跑,那里的 /bin/bash 是 3.2,没有这个 builtin。 本机实测:删前 4 个候选 → 删后 0 → 重建后 COUNT=1,且路径正是新指纹 `31ec1ef4ce2afcd5`,`--version` 正常。⚠️ 同型写法在别的 workflow 里还有(ci-macos / ci-macos-e2e / ci-target-matrix 的 `| head -1`,以及若干 `-printf|sort -rn` 的 mtime 形态)。它们今天是绿的,这次改动 只碰变红的这一处;规则记在这里。
⚠️ ⚠️ **它一次都没有执行过,而且它的前提本身做不到。** ## 没执行 我先前把日志里的 `glibc payloads present: ` 读成了它的输出。那是**回显的脚本正文** —— 展开后的输出一次都没出现过。守卫 `[ -x "$HOME/.mcpp/registry/bin/xlings" ]` 在 那个位置为假:mcpp 的 registry 要到第一次运行 mcpp 才建出来。⚠️ 同一个坑今天第三次:**判据是子串匹配,而被匹配到的是脚本自己。** ## 前提做不到 `xlings install glibc@2.44` 会把请求**解析成** `2.44.2`,建出来的目录就叫 `2.44.2` —— 它**不可能**造出旧绑定要找的那个 `2.44` 目录。这一步的整个想法 (「把绑定指名的那个载荷装上」)对这个包管理器无法成立。 ## 真正修好它的是上一个 commit `openkal cross-build` 在桥**之前**就已经绿了:02:10 / 02:41 / 02:57 三次,而桥是 03:15 的 6e310aa。⇒ 止血的是 `b3735fc`(撤回 xlings pin 前移),不是这座桥。 ⇒ 留着它 = 留一段永不执行的 CI 代码,配一条声称自己承重、并让下一个人去「等发布后 移除」的注释。删掉。⚠️ 生态那七个仓库里也有同一段,同样是空转;它们下一轮改用正式版时一并删。
The released 2026.8.27.2 fixes, upstream, the defect this release works around
from the client side. Its own commit message states it:
a stale bootstrap pin is a claim about the ecosystem, not neutral setup.
The aarch64 job bootstrapped v2026.8.10.1. That client has `glibc@2.44`
compiled in as the default runtime binding, so once the index's `latest`
moved past it the job installed the newer payload and then refused to use
it.
It also fixes a defect in 2026.8.27.1, which is why that version is skipped: the
subos code asked the index for `glibc` by bare name, which is ambiguous on an
ordinary home, and the resolver's error was read as "the index cannot answer" and
turned into the pinned fallback. The mechanism shipped dead and the binding still
came out right, because the pinned value and the index agreed that day. Two
constants now answer the two questions: `DEFAULT_RUNTIME_PACKAGE` names the
runtime and cannot carry a namespace because it is the payload directory's name;
`DEFAULT_RUNTIME_QUERY` asks the index and must carry one.
The two halves are complementary rather than redundant. A binding that follows
the index stops going stale; `payload_dir_for_version` in this release accepts an
installed payload whose version refines a request an OLDER client already
compiled in, and older clients continue to exist.
⚠️ I attempted this bump earlier and reverted it, correctly at the time: the
version then available moved the binding FORWARD, to `glibc@2.44.2` against an
installed `2.44`, which is the direction `payload_dir_for_version` refuses --- and
must refuse, since accepting it would silently use the payload the index states is
not the one to install.
The comments in `bootstrap-mcpp/action.yml` that record measurements taken on the
2026.8.17.2 assets keep that version, because that is the version they were taken
on. A comment stating a measurement states which build it came from.
check_version_pins.sh OK: xlings pins all at 2026.8.27.2
unit test_xlings_version_pin 5 passed
The bump is correct and this release cannot carry it. Measured on CI rather than
predicted:
Downloading xim:glibc@2.44
error: toolchain post-install fixup: selected RuntimeBinding glibc@2.44.2
requires payload '…/xpkgs/xim-x-glibc/2.44.2', but it is not installed
Two versions of one decision meet inside a single run. The mcpp that BUILDS this
one is the released 2026.8.25.2, whose compiled-in pin is 2026.8.17.2, and it
installs the payload its own binding names: 2.44. The vendored xlings that
COMPUTES the binding is the one this branch pins, which asks the index and gets
2.44.2. The request is then newer than what is installed.
That is the direction `payload_dir_for_version` refuses, and it must: accepting a
payload older than the request would silently use the one the index states is not
the one to install --- the preload defect of mcpp#484.
⚠️ THIS IS NOT A STALE CACHE. The sandbox cache key includes the xlings version
for exactly this reason, so the bump is a cache miss and the sandbox was fresh.
The mismatch is between the engine doing the building and the xlings it was told
to vendor.
⭐ IT IS SELF-HEALING, AND ONLY IN ONE ORDER. Once 2026.8.27.1 is released, a
bootstrap from it carries the new pin, computes 2.44.2, and installs 2.44.2. The
bump therefore belongs in the release AFTER this one, which is the rule the index
states in its own source: "The index is DATA and the client is a PROGRAM: the
consumer ships first."
The engine-side half of the same problem stays in this release:
`payload_dir_for_version` accepts an installed payload whose version REFINES a
request an older client already compiled in, so older clients stop being stranded
by an index move in the safe direction.
§7.5 是落地之后回填的。§2.12 的头文件草案里有两处,读的时候没看出问题,写第一份实现 时第一次运行就暴露了,而且两处都是**改规范而不是改实现**。 ⓵ `openkal.space` 从两个操作收敛成一个。本规范面向的环境里没有一个把 「克隆地址空间」和「在其中启动上下文」当作一对原语:Linux 的 clone 是一个动作。要求 实现分开,它只能启动一个上下文、把它停住、再自建一条信令通道 —— clause 7.1 的判词是 **形状有错,不是实现有错**。分开的形式还有一个答不上来的问题:clone 之后 start 之前 改了内存,子上下文看见哪一份。 ⓶ `openkal.net` 交出的是被拥有的连接,流从它借。草案直接交出 `kal_stream` 并称其 被拥有,而那在 clause 7.2 下无法实现:流句柄就是环境传输操作接受的东西,在描述符系统 上是一个关掉就被复用的数字,没有地方放代际。`openkal.fs` 早就回答过同一个问题。 ⓷ 顺带更正 §4.1:`_LIBCPP_HAS_TERMINAL` 的结论与那里相反。设成 0 只是「对一个坏掉 的 port 的正确描述」;它门控的是 isatty,而 isatty 不工作是因为端口层用 TCGETS 回答 musl 用 TIOCGWINSZ 问的问题。修端口层才是修法。 §2.12 的两份草案加了取代说明而不是改写 —— 草案是「当时提出了什么」的记录。
§3.6 说这条「零设计成本,回报最大」。实施之后两句都要更正。 **前提只在四个 target 里的一个成立。** 机制靠「没被引用的定义会被丢掉」,那要 `-ffunction-sections` + `-Wl,--gc-sections`,而只有 `cfg(os = "linux")` 设了; macOS 与 Windows 那两节根本没有 ldflags。⚠️ 我当时只跑了宿主一行,并据此在清单 注释里写下「这两个 flag 已经在位」—— 对我测的那个 target 为真,对其余三个为假。 **闭包应当一次问清,而不是跟着失败一条条加。** 逐族查目录之外的引用者:五个 fd 族 为 0;`network` 经 syslog/nscd_query/mq_notify 通到 `getpwnam`;`fork` 经 forkpty/wordexp/daemon/clone.c 通到 **`pthread_create`**。⇒ fork 的闭包终点是线程 实现,network 的终点是口令函数,两个都不能排。 ⭐ **不是拒绝,是还不可能**:排 network 族要等每个 target 都收集未引用 section, 那是三条链接线的改动,可以单独测。代价照直说 —— 今天用 socket 的程序仍然构建成功、 仍然运行期失败。 另加一条:`pipe` 落到 `kal_process_channel` 上之后,裸机后端上每个程序都链接失败 (不管要不要 pipe)。同一条规则四行之外就写在 `kal_random_fill` 上 ⇒ 判据改成 符号类别 `w` 而不是 `U`。 §3.6 加了取代说明而不是改写 —— 它是「当时是怎么想的」的记录。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #514. Also supplies the engine facility openkal-musl#13 had named in its own source as the one it needed.
Full analysis:
.agents/docs/2026-08-27-openkal-native-path-three-issues.md, carried by this pull request.In one sentence
This is the next layer of the family
2026.8.25.xand2026.8.26.1belong to. Those fixed a predicate that asked the wrong question and an answer that reached no decision. Here the answer reached one consumer and it has five.Six defects
2026.8.26.1fixed on the link side.!crossTargetFlag.empty()becomesplan.targetSide.cAbi.prebuilt()include/<triple>/c++/v1is spelled in LLVM's vocabulary and looked up in mcpp's, andif (exists)makes the miss silentpublicUsagenow merges into every package'sprivateBuildtargetHeaderSetaxisactive_home_xpkgs()omitted self-contained installs;find_sibling_packagefell back to~/.xlingsunconditionally[build] private_include_dirs— a package may distinguish what it is built from from what it publishes① measured
Same machine, same compiler, same target, differing only in whether it was spelled out:
⇒ Headers from one library, objects linked from another.
--no-default-configis split out of that condition and emitted unconditionally. It is not part of the payload's header set, and a cfg file is bypost_install.cppm's own words "a per-machine, per-install-path artifact". This also explains why the reporter's hand-written<triple>-clang++.cfgworked around #514: it was read only because this token had gone missing.e2e 295states exactly this invariant and compared^ldflagsalone, so the identity failed on the next line and the test could not see it. It now compares both.② measured
include/<triple>/c++/v1holds one file:__config_site. Its absence reads as a broken payload, and this repository has misdiagnosed it more than once.⑥ measured
The criterion is refinement, not a pick by directory order: components are compared component-wise, so
2.4does not answer2.44; two payloads both refining one request yield nothing; and the reverse direction is refused, because accepting a payload older than the request would silently use the one the index states is not the one to install.Criteria
ASSERTthat it took effecte2e 295cxxflags; four checks across two toolchains; exit 1 on the unfixed binary, listing the six lost tokense2e 304(new)e2e 305(new)What the pre-merge review found
Four further defects, all mine, each with an A/B showing the criterion goes red without the fix:
MCPP_HOME; a<store>-only rule cannot deliver that, because the ordinary toolchain's only compile token is--sysroot=<home>/registry/subos/default. Both existing tests set the axis directly, so neither exercised the relativization at all.root / ""isroot, and it is a directory. Both callers reject an empty version before arriving — which is exactly why nothing would have caught it.openkal-muslis that shape. Removing the key in the fixture put the overlay onto the sibling package and the root.Ecosystem cross-validation
Nine repositories, each carrying implementation rather than a CI change, all building mcpp from this branch:
openkal-kitopenkal.processumask;copy_file_range;pipe__config_sitereconciliation, and the cross-validation this repository never hadGreen is not the criterion; the
under review:line is — checked per job.Compatibility
kCacheEpochis not bumped. An older entry is a miss, not a hazard:inputs_matchcomparesinputs.toolchainas one object.private_include_dirsdegrades on an older engine. Measured on 2026.8.26.2: silently accepted in a dependency's manifest, a warning in a root manifest, exit 0 either way. The one place it does not hold is a publishedximdescriptor'starget_cfg, where an unrecognised sub-key is fatal — documented.Toolchain::cAbiPrebuiltdefaults totrue, so every host-targeting build behaves exactly as before.glibc@2.44while the newly-pinned vendored xlings computes the binding as2.44.2, and the request then exceeds what is installed. It is self-healing in one order only, so it belongs in the release after this one — the rule the index states in its own source: the consumer ships first.