日期:2026-07-22 基线:HEAD =
af25d18(mcpp 0.0.101),工作树 clean 方法:每条 issue 的断言都回到 HEAD 源码逐行核对;#256、#257 的关键行为在本机用 mcpp 自带工具链(llvm 20.1.7 / 22.1.8 / gcc 16.1.0 / 系统 clang 18.1.3)实证,不依赖 issue 正文的转述。 架构判据沿用批次总账 §5.2:一数据模型、多文法、单一收敛漏斗;同一决策不许两处推导;外加一条本批次凸显的:失败必须响,不许静默降级。
| # | 主题 | 裁决 | 断言准确度 | 归属 | 优先级 |
|---|---|---|---|---|---|
| #261 | windows scan-deps cmd /c 撞 8191 |
真 bug(构造性必现) | 全部属实 | mcpp | P0 |
| #257 | clang 路径 purview #include 不触发重建 |
真 bug(correctness,静默错误结果) | 全部属实,且是代码里已登记的 follow-up | mcpp | P0 |
| #259 | llvm 装完 exec 127 | 症状真,归因错;顺带暴露 3 个 mcpp 侧真 bug | 现象属实,根因假设被证伪 | 需重定向(见 §5) | P1 |
| #258 | [target.'cfg(os)'.build].flags |
合理的特性请求,且内含 1 个真 bug(键被静默丢弃) | 全部属实 | mcpp | P1 |
| #254 | per-OS 段按宿主而非 target 拼接 | 真 bug(设计层不自洽),作者已在 #253 设计文档 §2.3 自记 | 全部属实 | mcpp | P2 |
| #256 | clang 20/22 模块 BMI 崩溃 | 上游 clang 回归,非 mcpp 缺陷;三条 ask 均合理 | 本机完整复现 | LLVM 上游 + mcpp 文档/CI | P2(canary P1) |
一句话总览:六条里五条成立,一条(#259)方向要改;而且四条(#257/#258/#259 + #261 的 cmd /c)共享同一条失效模式——引擎在遇到自己没准备好的情况时选择静默降级,详见 §7。
src/build/ninja_backend.cppm:646-677 的 cxx_scan 规则三分支:
if (msvcDeps) { // /scanDependencies $out —— 文件参数,无重定向
} else if (plan.scanDepsPath.empty()) { // GCC:-fdeps-file=$out —— 文件参数,无重定向
} else { // Clang:clang-scan-deps
if constexpr (mcpp::platform::is_windows) {
append(" command = cmd /c \"$scan_deps -format=p1689 -- "
"$cxx $local_includes $cxxflags $unit_cxxflags -c $in -o $compile_target > $out\"\n");
} else { ... }
}断言逐条成立:
-
cmd /c包裹确实存在,且全仓仅此一处发射到 ninja 规则(:299的另一处是compile_commands.json过滤器的字符串前缀判断,非规则)。 -
触发面精确:windows × clang 工具链 × dyndep 开启 ×
clang-scan-deps存在于clang++旁。GCC(-fdeps-file=$out)与 MSVC(/scanDependencies $out)因为把输出写成参数而不是 shell 重定向,天然不需要cmd /c——这本身就说明重定向是可以避免的。 -
-o可用性我在本机实证(不是查文档):$ clang-scan-deps --help | grep '^ -o' -o <value> Destination of the primary output # 20.1.7 与 22.1.8 均有 $ clang-scan-deps -format=p1689 -o out.ddi -- clang++ -std=c++23 -c m.cppm -o m.o rc=0 size=488 → 合法 P1689 JSON故 issue 提议的替换功能等价且已验证。仓库无任何最低 LLVM 版本闸(
src/toolchain/clang.cppm:246-252的find_scan_deps只做路径探测,不查版本;provider.cppm:88-93直接has_scan_deps = true),实际下限就是自带工具链(20.1.7),-o从 LLVM 17 起存在,兼容面无风险。
cmd /c 是放大器,不是唯一病灶。长度来源是 local_include_flags()(ninja_backend.cppm:102-127)每依赖一个 -I,无上界。去掉 cmd /c 把天花板从 8191 抬到 32767 —— 4 倍余量,对 48 个 -I 够,但对下一个 ffmpeg/opencv 级包不一定够。而且 cxx_object / cxx_module / c_object 在 windows 上同样没有任何长度兜底(rspfile 目前只在链接三规则上,:602 useRsp = separateLinker || is_windows,#247 引入)。今天 scan 先炸,只是因为它的命令行 = 编译命令行 + $scan_deps -format=p1689 -- 前缀 + > $out 后缀,严格更长。
工业界基准很清楚:CMake/Ninja 生成器对所有可能超长的规则统一走 response file(RULE_LAUNCH_* / CMAKE_NINJA_FORCE_RESPONSE_FILE),而不是逐规则救火。仓库自己在 #247 已经建立了这个先例并写下了理由注释(:594-602)——只是没推广到编译/扫描边。
- P0(本周):合并两分支为
-o $out,消灭全仓唯一的cmd /c。改动 8 行,cxx_scan的 POSIX 命令文本也一并简化(去掉> $out),两平台同形——符合"单一收敛漏斗"。 - P1(随后):把
useRsp的判据从"链接规则"上移为"windows 上所有含$local_includes的规则",cxx_scan/cxx_module/cxx_object/c_object统一rspfile。注意 #247 的教训(rsp 内容按 GNU 文法分词,反斜杠是转义符 → 节点名必须generic_string())已经在link_rule里踩过,复用即可。 - 单测:
tests/unit/test_ninja_backend.cpp:516-519已锁链接规则的 rsp 形状,照抄一条锁cxx_scan无cmd /c。
src/build/ninja_backend.cppm:496-502:
const bool posixDepfile = !msvcDeps && !mcpp::platform::is_windows
&& plan.toolchain.compiler == mcpp::toolchain::CompilerId::GCC;于是 clang 上 mmd_flag / mmd_filter 皆空,append_cxx_deps() 落到 append_deps() 而后者对非 MSVC 什么都不发 → cxx_module 与 cxx_object 在 clang 上完全没有 depfile。.inc 对整个构建图不可见。
这个闸不是疏忽,是 0.0.97 主动加的。78bb1c9 "fix(build): scope #235 compile-edge depfile to GCC" 的提交信息与 :486-495 的注释都写着:
Clang's module system (.pcm) does not emit those and its module-TU depfile shape is different; applying the GCC-shaped filter there is untested and could yield a wrong dep set. Until Clang is verified, scope this to GCC —— header/purview rebuild tracking on Clang is a follow-up.
tests/e2e/118_purview_include_rebuild.sh:2 是 # requires: gcc,所以 clang 腿零覆盖。scanner / p1689 都不回读 textual include(p1689.cppm:355 生成 .dep 却从不解析),也不存在任何源文件内容哈希/mtime 兜底——除 depfile 外没有第二道防线。
当年 gate 掉的唯一理由是"clang 的 module-TU depfile 形状未知,GCC 的 awk 过滤器套上去可能出错"。我在本机把两边的形状打出来了:
$ clang++ -std=c++23 -fmodule-output=x.pcm -MMD -MF x.d -c x.cppm -o x.o # 20.1.7 与 22.1.8 同
x.o: x.cppm ops.inc ← 干净的单条 make rule,ninja 直接可吃
$ g++ -std=c++23 -fmodules -MMD -MF x.d -c x.cppm -o x.o # gcc 16.1.0
x.o gcm.cache/x.gcm: x.cppm ops.inc
x.c++-module: gcm.cache/x.gcm ← 正是 awk 过滤器要剥掉的 "reversed rules"
.PHONY: x.c++-module
gcm.cache/x.gcm:| x.o
结论:clang 根本不产生需要过滤的东西。正确修法不是"把 GCC 的过滤器移植到 clang",而是把两件事拆开——
posixDepfile(是否发-MMD+deps = gcc+depfile)→ 放开到 GCC ∪ Clang;mmd_filter(awk 剥离 reversed rules)→ 保持 GCC-only。
代码上就是把 :496-502 的单一 bool 拆成两个谓词。风险极低,且 e2e 118 的 # requires: gcc 可以直接删掉,变成两工具链共同覆盖。
issue 说"Harmless for CI (cold builds) but dangerous locally"。我认为要往上提一档:
- 失效模式是静默给出错误产物,不是报错。构建系统的失效等级里,silent staleness 是最差的一档——它把编译期错误延后成运行期错误,而且怀疑对象总是先落到用户代码上。issue 自己就记录了它已经造成了一次误诊(在 #256 的 clang 调试过程中)。
- 影响面不止
.inc:任何 purview 里的#include(含module;之后再包的私有头)、任何被模块接口文本包含的生成头,在 clang 上都不重建。而 clang 恰是 macOS 默认腿与 windows 托管腿的编译器。 - 从"依赖跟踪必须完备"这条构建系统第一原则看,当前状态是 mcpp 在 clang 上不满足最小正确性契约。0.0.97 当时的取舍(不 regress、留 follow-up)在发布压力下合理,但已经拖了 4 个版本,而实测表明代价只有几行。
| 断言 | 结论 |
|---|---|
ConditionalConfig 无 globFlags 字段 |
属实,types.cppm:276-292 只有 cflags/cxxflags/ldflags/sources/三类 deps |
[target.<pred>.build] 只读四个键 |
属实,toml.cppm:976-987 四次 read_list |
merge_conditional_sources_flags 不碰 globFlags |
属实,prepare.cppm:429-441,加一行 insert 即可 |
xpkg 描述符有per-OS flags |
属实,机制是 xpkg.cppm:888-894 的文本体拼接——OS 段体追加到 base 体后跑同一解析循环,所以 flags 连同其余顶层键"免费"支持 per-OS,且 OS 条目自然排在 base 之后(last-wins 顺序天然正确) |
| 死 glob 告警按构建的源集判定,不看文件系统 | 属实,globFlagHits 只在 apply_glob_flags 内自增,而它只被 all_files 循环调用(scanner.cppm:900/919,all_files 由 modules.sources 展开)→ issue 评论里"磁盘上放锚点文件无效"的实测结论有代码依据 |
features.<n>.flags 在 mcpp.toml 里全局-only |
属实,toml.cppm:320-327 在顶层 [features] 里;[target.*] 没有 features 子键 → OS 轴 × feature 轴在 mcpp.toml 里不可组合 |
无 optional / 允许零匹配的逃生口 |
属实,且两个解析器对未知键都硬错,所以今天写 optional = true 是 parse error 而非被忽略 |
toml.cppm:976-987 的读法是"认得的四个键各读一次",没有未知键拒绝。作者在 [target.'cfg(windows)'.build] 里写 flags = [{...}],今天的行为是:解析成功、零告警、规则凭空消失、构建出一个 flags 缺失的产物。
这与本仓已确立的纪律直接冲突:xpkg 未知 feature 子键进 xpkgUnknownKeys 报 did-you-mean(0.0.100)、[build].flags 条目未知子键硬错、scan_overrides 零命中硬错。条件段是这条纪律的一个破口。即便 #258 的特性最终不做,这个静默吞键也该单独修(#227 封闭文法 allowlist 应覆盖 target.*.build.*)。
- 文法平价是本仓写在设计文档里的原则("一数据模型两文法")。今天的实况是 xpkg 描述符能表达、mcpp.toml 不能——即"manifest 包是二等公民"这个论证站得住。而且不对称的成因是偶然(xpkg 靠文本拼接免费获得,toml 走结构化解析所以要逐键实现),不是有意的能力分级。
- removal 论证成立。
-U之所以救不了,是因为它自身也需要 OS 条件化,递归回同一缺口;issue 正文对此的推理正确。而merge_conditional_sources_flags的 append 位置天然给出 last-wins 覆盖权,这正是表达"windows 上撤销 unix 的-DHAVE_UNISTD_H"所需要的。 - 实施成本:issue 给的 4 处清单准确无误,四处全部复用既有代码(
parse_glob_flags_value的签名(value, ctx, std::vector<GlobFlags>& dst)可直接指向cc.globFlags;下游 scanner/backend/fingerprint 零改动,因为 0.0.101 已经把 globFlags 全量序列化进 per-package fingerprint)。唯一要盯的顺序不变量:merge 必须在makePackageRoot快照buildConfig之前(prepare.cppm:900-909已有注释)——三个调用点:912 / :1871 / :2762都在其前。 - 对 703 个 stub 文件的评估:这不是"包作者偷懒",是引擎缺表达力把语义泄漏成了文件系统结构。0.0.97 刚删掉过同类的 #233-era tu-stub 机制,现在因为另一个缺口在下游重新长出来——这是判断该做的强信号。
issue 评论里提议的 optional=true / ?-前缀 我不建议做,理由是它会引入第二套抑制机制,与 #253 刚确立的路线("条目按条件存在,所以无从告警")相悖——那正是同一决策两处推导。#253 已经证明了正确形状:让规则在不该生效的维度上不进入 globFlags。OS 轴照抄即可,不需要新语义。如果 #258 主体要拖,宁可先只做 §3.2 的静默吞键修复(半天工作量,且能让作者立刻发现自己写的 flags 没生效)。
两轴不自洽,逐条属实:
platform/common.cppm:95-105的xpkg_platform是constexpr+#if defined(_WIN32)/__APPLE__/__linux__—— mcpp 自己被编译时的宿主,无任何运行期输入。xpkg.cppm:888-889osKey = osOverride.empty() ? xpkg_platform : osOverride。osOverride的全部非测试调用点:prepare.cppm:1589 / 1637 / 1814三个构建路径全部走默认(宿主),唯一传 override 的是 lint 命令mcpp xpkg parse --all-os(cmd_xpkg.cppm:132)。overrides.target_triple从未穿进来。cfgpred::context_for(prepare.cppm:76-107)注释明写 "Context = the RESOLVED target's coordinates",三个调用点(:911/:1872/:2763)都传overrides.target_triple。- 影响面比 issue 列的还宽一格:
xpm版本/资产表也走宿主键(xpkg.cppm:711-772的list_xpkg_versions(luaContent, platform),pm/resolver.cppm:108传kXpkgPlatform = xpkg_platform),即交叉编译时连"有哪些版本可选/下哪个 tarball"都按宿主判定,连错误文案都把宿主平台名硬写进去。 - 交叉编译不是纸面能力:
tests/e2e/102_mingw_cross_wine.sh、112_build_mcpp_cross.sh真跑--target x86_64-windows-gnu并用 wine 执行。但没有一条 cross e2e 使用带 per-OSmcpp段的 xpkg 依赖 → 缺口对 CI 完全不可见。
这是教科书级的"同一决策两处推导":平台选择这一个决策,在 xpkg 通道按宿主编译期常量推,在 manifest 通道按 resolved target 运行期推。native 构建下 host == target 使两者巧合一致,于是缺陷被三平台 CI 完全掩盖——恰恰是 0.0.99 复盘里记下的那类隐性架构债(加新语义会变构建失败:#253 刚给 per-OS 段加了 features,这个错误轴上又多挂了一项)。
作者的处理方式是对的:在 #253 设计文档 §2.3 记录、明确不入该 PR、拆成独立 issue 并回引设计节。这是应该保持的工作方式。
issue 说"pm/install 侧需单独辨析",正确但太软。建议直接上类型层约束,否则这类 bug 会反复:
- 引入两个不可互换的类型(如
HostPlatform/TargetPlatform,或一个带enum class PlatformAxis { Host, Target }的强类型 wrapper),让synthesize_from_xpkg_lua/list_xpkg_versions在签名上必须声明自己要哪一轴。今天二者都收std::string_view,混用没有任何编译期阻力。 - 分类结论(已核实,可直接照用):
prepare.cppm:1589/1637/1814三处 = 编进用户构建的源码库包 → target;toolchain/lifecycle.cppm:135= 宿主工具(且注释已写明为何要 host)→ 保持 host;pm/resolver.cppm:108= 用户项目的库依赖 → target;cmd_xpkg.cppm= lint,遍历全 OS。 - 锁定:e2e 加一条"host == target 时拼接结果逐字节不变"的回归,再加一条 mingw-cross 腿消费带 per-OS 段的 xpkg dep。
优先级 P2 合理——今天没有真实用户被咬(交叉编译尚未与 per-OS xpkg 依赖组合使用),但它是能力增长的路障:每加一个 per-OS 键,错误面就扩大一次。
本机 ELF 证据与 issue 完全一致:
xim-x-llvm/22.1.8/bin/clang++ → interpreter …/xpkgs/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2
RUNPATH …/xim-x-llvm/22.1.8/lib : …/xim-x-glibc/2.39/lib64 : …/xim-x-zlib/1.3.1/lib : …
解释器路径是绝对的、机器本地的、安装时写入的(不在发布 tarball 里)。glibc payload 缺失 ⇒ 所有 clang 二进制 exec 127。到这里都对。
issue 的判断是"looks like a missing runtime dependency in the llvm xim package"。核对索引:
-- xim-pkgindex pkgs/l/llvm.lua:25-31
xpm = { linux = {
deps = { "xim:glibc@2.39", "xim:linux-headers@5.11.1", "xim:zlib@1.3.1", "xim:libxml2@2.13.5" },llvm 声明了 glibc,自 8e432d99(#182,首次加 Linux x86_64 支持)起从未变过,与 gcc.lua 的声明形态一致。三份本地 checkout 全同。而且 llvm.lua 自己的 install hook 在 glibc 缺失时是故意 fail-loud 的(:234-239 log.error("glibc payload not found … refusing to write a host-dependent clang cfg"); return false)。
所以"llvm 包漏声明 glibc"不成立。真正的问题一定在声明有、实际没装上,而且没人喊这条链上。目前证据指向三个 mcpp 侧的静默点:
(a) 依赖安装结果被丢弃 —— toolchain/lifecycle.cppm:554-568:
for (auto dep : {"xim:glibc", "xim:linux-headers"}) {
auto depPayload = fetcher.resolve_xpkg_path(dep, /*autoInstall=*/true, &progress);
mcpp::log::debug("toolchain", std::format("dep {} result: {}", dep, depPayload ? "ok" : ...));
}结果只进 debug 日志,不检查、不中断。裸 runner 上 glibc 安装失败,流程原样继续去装 llvm。
(b) llvm 的 post-install fixup 在 glibc 缺失时静默跳过,还把这个状态记成"成功" —— post_install.cppm:364-390:if (!glibcLibDir.empty() && exists(patchelfBin)) { … },glibc 找不到时整段跳过且一句告警都没有(gcc 路径至少有 :358-361 的 warning)。更糟的是随后 fixup_clang_cfg(:249-284)会把 bin/ 下每个 *.cfg 重写成省略 glibc -B/-L/--dynamic-linker/-isystem 的版本——正好覆盖掉 llvm.lua 本来会拒绝写、且会 fail-loud 的那份 cfg;然后幂等标记 .mcpp-fixup.json 把 glibcLib: "" 记录为一次成功的 fixup,后续重装也不会重试。
(c) 装完不验活 —— lifecycle.cppm:578-585 只 std::filesystem::exists(bin)。全仓唯一的 --version 调用在构建期 detect.cppm:56,它把 127 报成 unrecognized compiler output。mcpp doctor(doctor.cppm:239-313)会 readelf -d 查 RUNPATH 缺目录,但从不查 PT_INTERP(全仓 PT_INTERP/interpreter 只出现在 pack.cppm/linkmodel.cppm),而 PT_INTERP 才是致命轴;它给出的提示文案还是"其提供方 xim 包可能被删除了",在"从未装过"的场景下正好误导。
- 把 issue 重定向:标题/正文改成"llvm 安装链在 glibc 运行时缺失时静默降级并记为成功",把"llvm 包漏声明 glibc"的假设撤回(附上
llvm.lua:25-31的证据),同时在 xlings/xim 侧另开一条查"声明了却没装上"的实际原因(bare runner 上xlings install的 dep 解析是否被跳过/失败)。 - mcpp 侧三处照修(与归因无关,无论 xim 侧结论如何都该做):
lifecycle.cppm:562-567检查 dep 结果,失败即 hard error;llvm_post_install_fixup在 Linux 且glibcLibDir为空时告警或硬失败,且不写 fixup marker(对齐 gcc 路径与 llvm.lua 自身的 fail-loud 策略);- 安装末尾加
<frontend> --version冒烟探针,127 直接报"glibc payload missing";doctor补 PT_INTERP 存在性检查。
- 现有 workaround("先装 gcc")继续有效,但它掩盖问题,应在修好后从 opencv-m CI 撤掉。
这条 issue 的价值其实比作者以为的更高:它暴露的不是一个包的元数据错误,而是安装路径上一条完整的静默降级链——三个环节各自"宽容"地放过,合起来产出一个看起来装好了、实际不能 exec 的工具链。
6. #256 —— clang 20/22 模块 BMI 里的 replacement-operator 模板毒化名字查找 【上游 clang 回归,非 mcpp bug,P2;canary P1】
用 issue 正文的 3 文件 repro,逐一跑本机四个编译器:
| 编译器 | 带 4 参 Matx*Matx 模板 |
去掉该模板(对照) |
|---|---|---|
| clang 20.1.7(mcpp 工具链) | 崩溃 rc=1,PLEASE submit a bug report |
ok rc=0 |
| clang 22.1.8(mcpp 工具链) | 崩溃 rc=1,同上 | ok rc=0 |
| clang 18.1.3(Ubuntu 系统) | ok rc=0 | — |
| gcc 16.1.0(mcpp 工具链) | ok rc=0 | — |
崩溃点与 issue 一致:use.cpp:2:46 解析 main 体内 p * 2 时前端崩,而 --precompile 生成 BMI 那步 rc=0。issue 的控制矩阵逐格复现,回归窗口 clang 18 → 20 成立。
- 不是 mcpp 的缺陷。mcpp 只是把标准 C++23 模块命令行交给 clang;
--precompile成功、消费端崩溃,完全在编译器内部。任何构建系统(CMake、Bazel、build2)在同样输入下都会看到同样的崩溃。 - 是 mcpp 的暴露面。issue 指出的"模块包模式(镜像上游头里的
static inline运算符模板 + 平凡真约束,做混合 TU subsumption)会直撞这个形状"是准确的架构观察——这个模式正是 mcpp 生态推荐的 header→module 封装配方,所以对本项目用户的命中率远高于一般 C++ 项目。 - 三条 ask 全部合理,但优先级应重排:
- 上游报告优先(ask 2)。repro 只有 40 行、回归窗口干净(18 ok / 20+22 crash),是一份高质量的 LLVM issue。建议尽快提,并把编号回链本 issue。
- e2e canary(ask 3)升到 P1。理由:这是沉默的能力边界——没有 canary,未来任一次 clang bump 修好或再弄坏,mcpp 都不会知道;而 opencv-m 的经历表明发现成本是"一整轮 bisect + 一次误诊"。canary 很便宜:把 §6.1 的 3 个文件塞进
tests/e2e/,# requires: clang,断言"要么编过、要么给出可识别的诊断",崩溃即红。 - 文档(ask 1):写进模块包指南,一句可操作的规则——"运算符模板的所有模板参数应由第一个实参绑定;否则改为对整个推导操作数类型建模"。作者已经在 opencv-m
42aeb20里给出了可直接复制的改写范式,值得原样收进文档。
- 一个不建议做的方向:让 mcpp 检测子进程 139/SIGSEGV 并给专门提示。信噪比太低(前端崩溃原因千百种),canary + 文档已经覆盖了实际收益。
本批次六条里有四条的实质是引擎遇到没准备好的情况时,选择安静地少做一点事:
| # | 静默点 | 用户看到的 |
|---|---|---|
| #257 | clang 上不发 depfile | 构建"成功",产物用了陈旧 BMI |
| #258 | [target.*.build] 里的 flags 被丢弃 |
构建"成功",flags 从未生效 |
| #259(a) | dep 安装结果只进 debug 日志 | 安装"成功" |
| #259(b) | glibc 缺失时跳过 patchelf 并记 marker 为成功 | 安装"成功",二进制不能 exec |
而本仓在别的地方已经把"不许静默"做得很好:xpkg 未知键 → xpkgUnknownKeys + did-you-mean;scan_overrides 零命中 → 硬错;死 glob → 告警;#253 还专门为告警加了归属点名。也就是说,纪律存在,只是没有被提升为跨模块不变量。
建议把它写成一条明文架构约束(放进批次总账 §5):任何"因为条件不满足而少做一步"的分支,必须要么报错,要么发出用户可见的告警;debug 日志不算。 并在 code review checklist 里加一条对 if (x.empty()) return; / 静默 continue 的审视。这条约束若在 0.0.97 就存在,#257 的 gate 会带一条 clang 上的 warning,#259 的两次静默都会响。
三条 bug 的可见性条件高度相似,都是"只在某一条腿上出现,而那条腿恰好没覆盖":
- #261:windows × clang × 长路径(自有 CI 路径短 → 绿;作为依赖被消费 → 炸)
- #257:非 GCC(e2e 118 明写
# requires: gcc) - #254:host ≠ target(cross e2e 存在,但不消费带 per-OS 段的 xpkg dep)
- #256:clang × 模块 × 该模板形状(opencv-m 的 gtest 套件此前从未用 clang 构建过)
共同点:能力维度的笛卡尔积没有被系统地枚举。建议做一次一次性的覆盖矩阵盘点(工具链 × OS × host/target × 依赖形态 × 消费深度),把空格子显式记录为"已知未覆盖",而不是默认绿即安全。#261 尤其值得注意:它在包自己的 CI 上是全绿的,只有作为依赖被深路径消费时才现形——即"消费者视角"这一整维在 CI 里缺席。
#258 与 #254 都指向同一处结构性差异:xpkg 描述符靠文本体拼接获得能力(任何顶层键自动 per-OS 化),mcpp.toml 靠结构化逐键解析,于是每加一种条件维度都要手工补一遍。这解释了为什么不对称是偶然的而非有意的,也预示了它会继续复发(下一个 per-OS 键、下一个 cfg 维度)。
值得考虑的中期收敛:让 [target.<sel>] 的 build 段与顶层 [build] 段共用同一个解析器(把 ConditionalConfig 的 build 部分换成一个完整的 BuildConfig,解析后按谓词合并),而不是维护一份"条件段支持哪几个键"的手工子集。那样 #258 就不再是一个需要实现的特性,而是自动成立的性质;#227 的封闭文法 allowlist 也会自动覆盖条件段,§3.2 的静默吞键随之消失。
| 顺位 | 动作 | 规模 | 依据 |
|---|---|---|---|
| 1 | #261 去 cmd /c,改 -o $out,两平台合一分支 |
~8 行 | 构造性必现,已阻塞真实 CI |
| 2 | #257 拆 posixDepfile / mmd_filter 两谓词,depfile 放开到 Clang;删 e2e 118 的 # requires: gcc |
~10 行 + 测试 | 正确性;实测证明当年的顾虑不成立 |
| 3 | #259 mcpp 侧三处静默修复 + issue 重定向 | 中 | 静默降级链;归因需先纠正 |
| 4 | #258 四处实施(含 §3.2 的静默吞键修复) | 中 | 解锁 opencv-m windows 腿,消除 703 stub 文件 |
| 5 | #256 上游报告 + e2e canary + 文档一节 | 小 | mcpp 侧只需 canary 与文档 |
| 6 | #261 P1:rspfile 推广到 windows 全部含 $local_includes 的规则 |
中 | 根治长度轴,不再逐规则救火 |
| 7 | #254 host/target 轴收敛(建议同时上强类型) | 中大 | 需独立设计 pass;先做类型再改行为 |
1、2 两条合起来不到 20 行,建议合成一个 PR 立刻发;3、4 各自一个 PR;7 单独走设计文档。
- 源码断言:全部在 HEAD(
af25d18)上按 file:line 逐条回读,未采信 issue 正文的行号转述。 - #256:本机跑通完整 4×2 控制矩阵(llvm 20.1.7 / 22.1.8 / 系统 clang 18.1.3 / gcc 16.1.0 × 有/无 4 参模板)。
- #257:本机对比 clang 与 gcc 的 module-TU depfile 实际字节形状,推翻"clang 形状需要过滤"的原假设。
- #261:本机验证
clang-scan-deps -o在两个自带 LLVM 上均存在且产出合法 P1689 JSON。 - #259:本机
file -L/readelf -d核 PT_INTERP 与 RUNPATH;三份本地 xim-pkgindex checkout 交叉核对llvm.lua的 deps 声明与其 git 历史。 - 未做:在 windows 上实测 8191 截断(无该平台);未实测裸 runner 上 glibc 安装为何失败(需 CI 环境)。