Skip to content

Commit e187d3f

Browse files
Resolve the target side once, and let every translation unit read it (#515)
* 2026.8.27.1 —— 目标侧被解析出来了,只发给了一个编译单元 ⭐ 与 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 * fix(test): 新的 hostflags fixture 用了 MSVC 没有的 getpid/<unistd.h> 它在写下它的那台机器上编过了,在 Windows CI 上编不过 —— 而 Windows 是这个项目 一半的面唯一可见的地方。改成固定名 + 先 remove_all:gtest 在一个二进制里串行 跑测试,一个名字就够。 * fix: 请求的 glibc 版本与装好的 glibc 版本,是两种词汇 ⚠️⚠️ 这条不是本 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`(逐分量) - 两个精化 → 仍然拒绝,且消息说明为什么 - 什么都没装 → 仍然拒绝 * chore(xlings): pin 2026.8.17.2 → 2026.8.27.1,索引已经在等这个客户端 ⚠️⚠️ 这是那条 `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 上停了很久没人发现。 * fix(e2e295): 归一化用了 `^` 锚定,而 Windows 上那个 token 是带引号的 ninja 通道会给需要引号的路径加引号,于是同一个 flag 到达时是 `"-fprebuilt-module-path=C$:\Users\..."` —— 锚定的模式匹配不到它, 比对随后就栽在这个过滤器本来要去掉的那个 token 上。 在 windows-x86_64 上实测:它把一条本该绿的恒等式变成红的,而原因与被编译的 东西毫无关系。改成子串匹配。⚠️ Linux 侧仍然能在未修二进制上变红(已复验)。 * fix: 「请求的版本 → 装好的目录」有两个调用方,而我先只修了一个 第一版把解析写在了工具链 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 目录时仍然拒绝」这一层。 * fix(prepare): build.mcpp 的宿主工具链不该继承目标侧的 C 库出处 `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 库出处,而下一个分开的字段将在这里找不到规则。 * Revert "chore(xlings): pin 2026.8.17.2 -> 2026.8.27.1" —— 它把绑定移到了已装载荷的前面 ⚠️⚠️ **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 的题目。 * ci: 把引导阶段绑定指名的 glibc 载荷装给它(过渡步骤) `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`)单独插入。 * fix: 头集轴把这台机器的 home 写进了键(自我 review 实测) 新轴的注释承诺「键要能跨 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` 能走到那个分支,所以还差不出来;绑在一起是为了将来也差不出来。 * fix: 空版本请求会把包目录本身当成载荷返回 `payload_dir_for_version(root, "")` 里 `root / ""` 就是 `root`,而它**是**一个 目录 ⇒ 精确匹配那一支把**容器**交了出去,调用者会把它当载荷用。 ⚠️ 今天两个调用者都在到达这里之前拒了空版本(`probe` 靠 `at + 1 >= size()`, `post_install` 有显式的 `version.empty()` 判断)—— 这恰恰是没有任何东西会撞上它的 原因。函数是导出的,第三个调用者不会知道这条约束写在别处。⇒ 闸放在性质成立的地方, 而不是放在每个调用者那里。 A/B 实测:移掉这一行后 `AnEmptyRequestIsNotTheContainer` 失败;测试自带分母 (同一棵树上真实的请求仍要解析得出来),空结果才不会和「目录读不了」同读数。 * docs: 新键在旧引擎上的行为(实测),并补上被我改得不准的那条消息 ## 旧引擎怎么读这个键 —— 实测,不是推断 在 **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` 却没有 把它加进那份列表 —— 于是错误信息会把一个**已经被接受**的键说成不存在。补上。 * docs(changelog): 补回漏掉的那条修复,并订正一句已经过时的说法 ⚠️ **载荷版本解析整条没写进去** —— 而它是这一版里用户最先撞上的一条(干净机器 上引导直接停住)。补上,含判据:精化是逐**分量**比而不是逐字符,两个都精化时返回 空而不是按目录序挑,反方向不接受。 订正:`targetHeaderSet` 写的是「store-相对化」,自我 review 时实测发现只做这一档 盖不住最常见的那台机器(`--sysroot=<home>/registry/subos/default` 在 HOME 底下), 已改成 `<store>`/`<home>` 两档。顺带把这个轴分得开什么写清楚。 * test: 两个键会在同一个包里相遇,而没有任何判据覆盖这件事 ## 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 列表里拿掉,两条都变红并各自点名。 * ci: 挑「刚构建出来的那个 mcpp」,而它挑的是走到的第一个 `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 形态)。它们今天是绿的,这次改动 只碰变红的这一处;规则记在这里。 * Revert "ci: 把引导阶段绑定指名的 glibc 载荷装给它(过渡步骤)" ⚠️⚠️ **它一次都没有执行过,而且它的前提本身做不到。** ## 没执行 我先前把日志里的 `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 代码,配一条声称自己承重、并让下一个人去「等发布后 移除」的注释。删掉。 ⚠️ 生态那七个仓库里也有同一段,同样是空转;它们下一轮改用正式版时一并删。 * chore(xlings): pin 2026.8.17.2 -> 2026.8.27.2 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 * Revert "chore(xlings): pin 2026.8.17.2 -> 2026.8.27.2" 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. * docs(plan): 回填实施结果 —— 写实现改掉了两处接口设计 §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 的两份草案加了取代说明而不是改写 —— 草案是「当时提出了什么」的记录。 * docs(plan): 回填 ⑤ 的实施结果 —— 排除表从十八条缩到五条 §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 加了取代说明而不是改写 —— 它是「当时是怎么想的」的记录。 --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent 078631d commit e187d3f

28 files changed

Lines changed: 5011 additions & 74 deletions

.agents/docs/2026-08-27-openkal-ecosystem-design-plan.md

Lines changed: 1958 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/2026-08-27-openkal-native-path-three-issues.md

Lines changed: 1426 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/openkal-cross.yml

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,52 @@ jobs:
128128
# decisions about compile and link flags; an optimisation level
129129
# changes none of them and a release self-build is most of the budget
130130
# of a job on a two-core runner.
131+
# ⚠️⚠️ REMOVE ANY mcpp ALREADY UNDER `target/` FIRST, so that after the
132+
# build there is exactly ONE and it is the one this step produced.
133+
#
134+
# `target/` is a RESTORED CACHE whose key hashes `src/**`. A source
135+
# change misses the key, `restore-keys` hands back the nearest older
136+
# tree anyway — that is the point of the layered restore — and the
137+
# build then writes its output into a NEW `<triple>/<fingerprint>/`
138+
# directory beside the ones already there. `find … | head -1` returns
139+
# whichever directory the walk reaches first, which is not the newest
140+
# and is not this build's.
141+
#
142+
# ⚠️ MEASURED, LOCALLY AND ON CI. On this developer's own tree the
143+
# command picks `007bc0f2b78f7fa5` while the build just wrote
144+
# `148448dec6c4a7a7`. On CI it picked a stale binary whose ELF
145+
# interpreter names a glibc payload the runner no longer has:
146+
#
147+
# …/bin/mcpp: cannot execute: required file not found (exit 127)
148+
#
149+
# — which reads as a broken build of THIS commit and is nothing of the
150+
# kind. Two runs, same stale fingerprint, after seven green ones: the
151+
# trigger is simply a source change large enough to leave two
152+
# directories in the restored tree.
153+
#
154+
# ⭐ mtime CANNOT BE THE CRITERION HERE. A cache restore writes every
155+
# file at extraction time, so "newest" is meaningless on exactly the
156+
# tree where this goes wrong — `ls -t | head -1` has picked a stale
157+
# fingerprint in this repository before. Absence can't be faked:
158+
# delete them, and whatever exists afterwards was linked just now.
159+
#
160+
# The objects and BMIs stay cached — this costs one link.
161+
find target -type f \( -name 'mcpp' -o -name 'mcpp.exe' \) -delete 2>/dev/null || true
131162
"$MCPP" build --dev
132-
BUILT=$(find target -type f \( -name 'mcpp' -o -name 'mcpp.exe' \) | head -1)
133-
[ -n "$BUILT" ] || { echo "::error::mcpp did not build"; exit 1; }
163+
# ⚠️ EXACTLY ONE, not "at least one". Two would mean the delete above
164+
# missed a tree and the ambiguity this step exists to remove is back.
165+
#
166+
# ⚠️ NO `mapfile`. This job also runs on macos, whose `/bin/bash` is
167+
# 3.2 and has no such builtin — a bashism here would fail on one row
168+
# of the matrix for a reason unrelated to what the row tests.
169+
find target -type f \( -name 'mcpp' -o -name 'mcpp.exe' \) > /tmp/mcpp-built.txt
170+
COUNT=$(wc -l < /tmp/mcpp-built.txt | tr -d ' ')
171+
if [ "$COUNT" -ne 1 ]; then
172+
echo "::error::expected exactly one built mcpp, found $COUNT"
173+
sed 's/^/ /' /tmp/mcpp-built.txt
174+
exit 1
175+
fi
176+
BUILT=$(cat /tmp/mcpp-built.txt)
134177
BUILT=$(cd "$(dirname "$BUILT")" && pwd)/$(basename "$BUILT")
135178
echo "MCPP_UNDER_TEST=$BUILT" >> "$GITHUB_ENV"
136179
"$BUILT" --version

CHANGELOG.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,100 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.27.1] — 2026-08-27
7+
8+
目标侧被解析出来了,只发给了一个编译单元。完整分析见
9+
[`.agents/docs/2026-08-27-openkal-native-path-three-issues.md`](.agents/docs/2026-08-27-openkal-native-path-three-issues.md)
10+
11+
**`2026.8.25.x`/`2026.8.26.1` 是同一族的下一层。** 那两批修的是「谓词问错了」
12+
与「答案没接到决定上」;这一批里,答案**接上了一个消费者,而它有五个**
13+
14+
### 修复
15+
16+
- **⭐⭐ 编译侧的谓词,是 `2026.8.26.1` 在链接侧修掉的那条的孪生兄弟。**
17+
18+
`hostflags.cppm` 问的是 `!crossTargetFlag.empty()` ——「命令行上有没有
19+
`--target=`」——而它的注释写的是「目标侧来自图」。同一台机器、同一个编译器、
20+
同一个目标,只差写不写 `--target`,编译线少了**六个 token**:
21+
22+
```
23+
--no-default-config -nostdinc++
24+
-isystem <payload>/include/c++/v1
25+
-isystem <payload>/include/<triple>/c++/v1
26+
-isystem <glibc>/include
27+
-isystem <linux-headers>/include
28+
```
29+
30+
⇒ 头文件来自一个库,目标文件链自另一个库。两侧现在读同一个
31+
`plan.targetSide.cAbi.prebuilt()`。⚠️ `--no-default-config` 从这个条件里
32+
**拆了出来无条件发** —— 它不是载荷头文件集合的一部分,而 cfg 文件按
33+
`post_install.cppm` 自己的说法是「per-machine, per-install-path artifact」。
34+
35+
⚠️ `e2e 295` 写的就是这条恒等式,而它只比对 `^ldflags`,所以恒等式在**下一行**
36+
不成立而测试看不见。现在两条都比。
37+
38+
- **⭐⭐ 载荷目录名是 LLVM 词汇,而查找用的是 mcpp 词汇 —— 而且失配是静默的。**
39+
40+
`include/<triple>/c++/v1``lib/<triple>` 由 LLVM 的构建写下,带的是
41+
`x86_64-unknown-linux-gnu`;`tc.targetTriple` 是 mcpp 的
42+
`x86_64-linux-gnu`。两者只在三元组是**探测来的**时候恰好相同。查找是
43+
`if (exists) push_back`,所以找不到就什么也不发生 —— 而那个目录里只有一个文件,
44+
`__config_site`,它的缺席产生的报错读起来像载荷坏了。两种拼法现在都试。
45+
46+
- **⭐⭐ 由图供给的目标侧,只到达了一个编译单元(mcpp#514 §A)。**
47+
48+
提供 `mcpp:` 层的包发布的是**整个目标**编译时所依据的头文件集合,而它今天以
49+
`publicUsage` 的形态**沿依赖边**传播。于是根与 provider 自己的单元拿得到,而
50+
**兄弟依赖包**拿不到 —— `nlohmann.json` 不在 `openkal-llvm-runtime` 的下游,
51+
它在它旁边。结果是一次构建里两种口味的 BMI,任何同时导入两者的 TU 在第一个
52+
模板实例化处炸掉(`reference to 'space' is ambiguous`)。
53+
54+
⭐ 目标侧解析之后,`fromGraph()` 的层的 `publicUsage` 并入**每一个**包的
55+
`privateBuild`;`std` 模块的命令行也改读同一个集合,不再自己推一遍。
56+
57+
- **⭐⭐ 缓存键描述了编译器,没有描述它被指向的头文件集合(mcpp#514 §B)。**
58+
59+
A 轴上的每一项都在描述**编译器**,没有一项描述它编译时所依据的**** ——
60+
而两者是分开安装的。`driverIdentity` 按设计也覆盖不了它:
61+
`normalize_driver_output` **故意**抹掉路径,好让一个条目能被两个 home 共享。
62+
新增 `targetHeaderSet` 轴,取自已经解析好的 `linkmodel`,并**分两档相对化**:
63+
`<store>``<home>`。⚠️ 只做 `<store>` 一档不够 —— 最常见的那台机器走
64+
`CLibMode::Sysroot`,它唯一的编译 token 是 `--sysroot=<home>/registry/subos/default`,
65+
在 HOME 底下而不在 store 底下,于是每个条目都会带上这台机器的 home。分得开什么:
66+
两个载荷(路径里带版本号)⇒ 两个键;一个 home 下的两个 subos ⇒ 两个键;
67+
两个 home 下同名的 subos ⇒ 仍是一个键(由整工程指纹的第 11 项区分)。
68+
⚠️ 不 bump `kCacheEpoch` —— 旧条目是 miss 而不是不可用。
69+
70+
- **⭐⭐ 请求的版本和载荷目录的版本是两套词汇,而每个查找都按请求那套拼。**
71+
72+
RuntimeBinding 带的是**声明的**版本(`glibc@2.44`),而 xlings 把载荷目录按这个
73+
请求**解析成**的版本命名(`2.44.2`)。索引在同一序列内挪动一次包,所有按声明版本
74+
拼目录名的查找就同时失效 —— 在每一台****机器上,在任何已存在的机器上都不出现。
75+
76+
两个查找点各自拼过一遍,所以修好一个还剩另一个,而第二处的失败**根本不提版本**:
77+
glibc 的 include 目录只是没被加上,用户读到的是 libstdc++ 头文件里的
78+
`features.h: No such file`。⇒ 收敛到一个 `payload_dir_for_version`
79+
80+
**判据是「精化」,不是「按目录序挑一个」**:`2.44.2` 的版本**分量**以请求的分量
81+
开头。`2.4` 回答不了 `2.44`(逐分量比,不是逐字符)。两个载荷都精化同一个请求时
82+
返回**** —— 「这个请求的解析结果」得是唯一一个才配叫答案,而按目录序挑正是这里
83+
每个调用者都拒绝做的猜测。反方向(拿更旧的载荷回答更新的请求)不接受。
84+
85+
- **⭐ home 发现有第四份拷贝,而且会伸到别的 home 里去。**
86+
87+
`active_home_xpkgs()` 自己重推了一遍 home(漏掉自包含安装那一档);
88+
`find_sibling_package` 找不到时**无条件回落** `~/.xlings/data/xpkgs`
89+
后者意味着一次密闭构建可以从**另一棵树**取载荷,而结果直接进每条编译命令的
90+
`-isystem`。前者改为 `mcpp::home::root()`,后者删除 —— 找不到会说话,找错了不会。
91+
92+
### 新增
93+
94+
- **`[build] private_include_dirs`** —— 指出 `include_dirs` 中在本包边界处停住的
95+
条目。`publicUsage` 此前整份接过 `privateBuild` 的目录,于是一个内嵌了带内部头
96+
覆盖层的库(musl 的 `src/include` 定义 `hidden`/`weak`/`weak_alias`)会把那些宏
97+
发给每一个消费者。⚠️ 它是 `include_dirs`**子集**而不是第二个列表:两类目录的
98+
相对顺序是承重的,而两个 TOML 数组表达不了一个顺序。
99+
6100
## [2026.8.26.2] — 2026-08-26
7101

8102
已经解析出的答案,没有被用来做决定。完整分析见

docs/05-mcpp-toml.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ module_extensions = [".ixx"] # Extra extensions used by module INTERFACES (
176176
build_program_timeout = 1800 # Seconds a build.mcpp may run; 0 = no limit (§ below)
177177
include_dirs = ["include", "third_party/include"] # Header search paths
178178
include_dirs_after = ["*"] # Header dirs searched AFTER system dirs (-idirafter)
179+
private_include_dirs = ["vendor/src/include"] # Of `include_dirs`, the ones a consumer must NOT get
179180
c_standard = "c11" # Standard for C source files (default c11)
180181
cflags = ["-DFOO=1"] # Extra C compile flags
181182
cxxflags = ["-DBAR=2"] # Extra C++ compile flags (do not put -std=... here)
@@ -190,6 +191,44 @@ jobs = "auto" # Concurrent compiles: a positive number, or "
190191
bmi_schedule = "auto" # Module-edge scheduling: auto (= off) | on | off (§ below)
191192
```
192193

194+
`private_include_dirs` names the entries **of `include_dirs`** that stop at this
195+
package's own boundary: this package compiles with them, and a consumer never
196+
receives them.
197+
198+
Almost every package publishes exactly the set it is built from, which is why
199+
`include_dirs` alone was enough for a long time. The shape where the two differ
200+
is a package that vendors a library with an *internal header overlay*. musl
201+
reaches its own declarations through `src/include`, whose headers define
202+
`hidden`, `weak` and `weak_alias` — names that mean something only to musl's own
203+
sources. Publishing that directory hands those macros to every consumer, and a
204+
consumer that uses `hidden` as an ordinary identifier stops compiling for a
205+
reason it has no way to see.
206+
207+
```toml
208+
[build]
209+
# ⚠️ The relative ORDER of the two kinds is load-bearing: the internal overlay
210+
# must precede the public headers for this package's own build. That is why
211+
# this is a SUBSET of `include_dirs` rather than a second list — two arrays
212+
# cannot express one order.
213+
include_dirs = ["port/include", "musl/src/include", "musl/include"]
214+
private_include_dirs = ["musl/src/include"]
215+
```
216+
217+
Entries take the same `*` glob convention as `include_dirs`, and are matched
218+
after expansion — so a glob may name exactly the directories it expands to. An
219+
entry that is not among this package's `include_dirs` withholds nothing and is
220+
reported as such rather than passing in silence.
221+
222+
**On an older engine the key is ignored, never fatal.** Measured on 2026.8.26.2:
223+
in a dependency's manifest it is accepted silently, and in a root manifest it
224+
warns — `[build] has unsupported key 'private_include_dirs' (ignored)` — and the
225+
build continues. So a package may adopt the key without waiting for its
226+
consumers to upgrade; those on an older engine simply keep receiving the
227+
directory as they did before. The one place this does **not** hold is a
228+
published `xim` descriptor's `target_cfg` block, where an unrecognised sub-key
229+
is a hard error that fails the whole manifest — do not put this key there until
230+
the index floor names an engine that knows it.
231+
193232
`include_dirs_after` (#249) lists header directories that are searched **after**
194233
the toolchain's system directories (emitted as `-idirafter` on GCC/Clang, as
195234
trailing `/I` under the MSVC dialect, and as plain `-I` for NASM

docs/zh/05-mcpp-toml.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ module_extensions = [".ixx"] # 模块**接口**额外使用的扩展名(见
166166
build_program_timeout = 1800 # build.mcpp 的运行上限(秒);0 = 不限(见下节)
167167
include_dirs = ["include", "third_party/include"] # 头文件搜索路径
168168
include_dirs_after = ["*"] # 排在系统目录之后搜索的头文件目录(-idirafter)
169+
private_include_dirs = ["vendor/src/include"] # `include_dirs` 中不发布给消费者的那些
169170
c_standard = "c11" # C 源文件的标准(默认 c11)
170171
cflags = ["-DFOO=1"] # 额外 C 编译参数
171172
cxxflags = ["-DBAR=2"] # 额外 C++ 编译参数(不要放 -std=...)
@@ -178,6 +179,35 @@ jobs = "auto" # 并发编译数:正整数,或 "auto"(见下
178179
bmi_schedule = "auto" # 模块边调度:auto(= 关)| on | off(见下节)
179180
```
180181

182+
`private_include_dirs` 指出 **`include_dirs`**在本包边界处停住的那些条目:
183+
本包用它们编译,消费者永远收不到。
184+
185+
绝大多数包发布的就是它编译时用的那一套,所以长期以来只有 `include_dirs` 就够了。
186+
两者不同的形状只有一种 —— 一个包**内嵌了带内部头覆盖层的库**。musl 通过
187+
`src/include` 到达它自己的声明,而那些头定义了 `hidden``weak``weak_alias`,
188+
这些名字只对 musl 自己的源码有意义。把那个目录发布出去,等于把这些宏交给每一个
189+
消费者;而一个把 `hidden` 当普通标识符用的消费者会编不过,且看不出原因。
190+
191+
```toml
192+
[build]
193+
# ⚠️ 两类目录的**相对顺序**是承重的:本包自己构建时,内部覆盖层必须排在公共头之前。
194+
# 这正是它被设计成 `include_dirs` 的**子集**而不是第二个列表的原因 ——
195+
# 两个数组表达不了一个顺序。
196+
include_dirs = ["port/include", "musl/src/include", "musl/include"]
197+
private_include_dirs = ["musl/src/include"]
198+
```
199+
200+
条目支持与 `include_dirs` 相同的 `*` glob 约定,并在**展开之后**比对 ——
201+
所以一个 glob 可以恰好指名它展开出的那些目录。若某条目不在本包的 `include_dirs`
202+
里,它什么也没扣下,mcpp 会把这件事说出来而不是让它悄悄通过。
203+
204+
**旧引擎会忽略这个键,而不会因此失败。** 在 2026.8.26.2 上实测:出现在依赖的清单里
205+
时被静默接受;出现在根清单里时给一条警告 —— `[build] has unsupported key
206+
'private_include_dirs' (ignored)` —— 构建照常继续。所以一个包可以先用上这个键,
207+
不必等消费者升级;还在旧引擎上的消费者只是像以前一样继续收到那个目录。**唯一不成立
208+
的地方**是已发布的 `xim` 描述符的 `target_cfg` 块:那里不认识的子键是硬错误,会让
209+
整份清单加载失败 —— 在索引下限指向认识它的引擎之前,不要把这个键写进那里。
210+
181211
`include_dirs_after`(#249)列出**排在工具链系统目录之后**搜索的头文件目录
182212
(GCC/Clang 发射为 `-idirafter`;MSVC 方言退化为排在末尾的 `/I`,NASM 汇编
183213
单元退化为普通 `-I`——两者都没有对应 flag,也都没有需要保护的系统头搜索链)。当目录是解压后的源码 tarball 根目录、且其中的文件名会与标准头冲突时,

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.8.26.2"
3+
version = "2026.8.27.1"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/build_program.cppm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,15 @@ std::vector<std::string> host_base_flags(const mcpp::toolchain::Toolchain& tc,
291291
// undefined __cxa_* / __gxx_personality_v0.
292292
opt.cfgBypass = mcpp::toolchain::HostFlagOptions::CfgBypass::LinuxOnly;
293293
opt.clangStdlibSelect = true;
294+
// ⭐ `cAbiPrebuilt` is left at its default (true), and that is a statement
295+
// rather than an omission: `tc` here is always HOST-targeting (see this
296+
// function's header), so the helper's C library is the payload's whatever
297+
// the project's target side turns out to be.
298+
//
299+
// ⚠️ It also corrects a latent defect. The predicate this replaced was
300+
// `!tc.crossTargetFlag.empty()`, and a host toolchain resolved for a cross
301+
// build could carry one — in which case the helper lost the payload's own
302+
// headers for a reason that had nothing to do with it.
294303
// binutils -B so the driver finds ld/as (GCC; musl and MinGW ship their own).
295304
opt.binutilsPrefix = !mcpp::toolchain::is_musl_target(tc)
296305
&& !mcpp::toolchain::is_mingw_target(tc);

0 commit comments

Comments
 (0)