Repository navigation
Bug: 依赖 BMI 缓存命中时,被恢复的包 BMI 的传递 import std 没有进入消费方的构建图 #405
Description
Activity
- changed the title
[-]Bug: 同一 home 用过两个 mcpp 版本后依赖 BMI 全局缓存互相投毒(Bad import dependency)[/-][+]Bug: 依赖 BMI 缓存命中时,被恢复的包 BMI 的传递 import std 没有进入消费方的构建图[/+]on Aug 10, 2026 代码位置
src/build/prepare.cppm:651—bool graph_or_targets_import_std(const mcpp::modgraph::Graph& graph, const mcpp::manifest::Manifest& manifest, const std::filesystem::path& projectRoot) { for (auto& u : graph.units) … // ① 本工程自己的模块单元 for (auto& t : manifest.targets) … // ② target 的入口文件 return false; }
调用点
:5163:bool needsStdModule = graph_or_targets_import_std(scan.graph, *m, *root);它只看本工程。链条是:
依赖 BMI 命中缓存 → 依赖的模块单元不进 scan.graph(这正是缓存要省的事) → needsStdModule = false → plan.stdBmiPath 为空 → ninja_backend.cppm:956 的 `has_std_artifacts` 为 false ⇒ 不发 std 的 stage_file 边 → gcm.cache/std.gcm 不存在 → 被恢复的 imgui.app.gcm 读不到它 import 的 std缓存 miss 时依赖在本地编译,它的单元进了图,① 命中,所以第一次总是好的。
修法建议
谓词需要再加一项:本次构建要消费的依赖 BMI 里,有没有 import std 的。
- (a) 精确:store 时把
imports_std写进entry.json(那一刻 mcpp 正好知道),
hit 时 OR 进needsStdModule。旧条目缺该字段时按true读,
这样已有缓存不会继续坏(代价只是多建一次全局已缓存的 std BMI),也不必动
kCacheEpoch。 - (b) 保守:只要有依赖贡献了恢复的 BMI 就要求 std。更简单、不可能漏,
代价是少数本来不需要 std 的工程也会 stage 一次。
两种都只改这一个谓词及其输入,不动 cache key,因此不会让现有缓存失效。
回归锁
e2e:清空该包的缓存 → 建项目 A(miss,必须 OK)→ 建项目 B(hit,必须也 OK),
消费方源码刻意不写import std。这正是现在会红的那一步,
而现有 e2e 里没有任何一条覆盖它 —— 缓存相关的用例要么消费方自己 import std,
要么只跑一次(永远是 miss)。- (a) 精确:store 时把
- added a commit that references this issue
on Aug 10, 2026 更正:上面那条「代码位置 + 修法建议」的根因是错的
已在
2026.8.10.2(PR #408)修复,但不是按那条建议修的 —— 按它改,改完仍然坏。
这是同一个 issue 上第二次根因写错,所以把证据完整留下。生成物否掉了那条推断
上面的评论说:
needsStdModule = false⇒plan.stdBmiPath为空 ⇒ 不发 std 的
stage_file边。实测复现后grep生成的build.ninja:65: build gcm.cache/std.gcm : stage_file <cache>/std/50c5b8df12f1bf98/gcm.cache/std.gcm 83: build _mcpp_staged_cache : phony <3 个 dep .o> <3 个 dep .gcm> 90: build obj/main.o : cxx_object …/src/main.cpp | obj/main.cpp.ddi.dd || _mcpp_staged_cache 93: build bin/b : cxx_link … obj/main.o obj/std.o第 65 行:边就在那里。 谓词是真的,不是假的 ——
graph_or_targets_import_std读的scan.graph来自scan_packages(packages),
而packages本来就包含依赖包根(prepare.cppm:3784/:4183);
cu.servedFromCache要到:6060才置位,在谓词之后。所以缓存命中与否都不影响它。真根因:一条正确的边,没有任何人依赖它
gcm.cache/std.gcm只作为 implicit input 出现在cu.imports含"std"的
编译边上(ninja_backend.cppm:1215/:1222)。消费方自己不import std
⇒ 零引用 ⇒ ninja 从不执行第 65 行。
(obj/std.o反而可达,因为它是链接边的输入 —— 所以坏的只有 BMI 那一半。)修法
ninja_backend.cppm:1053–1083:staged非空且has_std_artifacts时,把
std与std.compat的 BMI 一并放进_mcpp_staged_cache聚合。那个聚合本来就是为这件事建的 —— 它的 ORDERING 注释写明「用 stage 边替换编译边
会丢掉编译边携带的次序」,当时解决的是模块分区;std BMI 是同一缺陷早一条边,
只是它在填充staged的循环之前就发出去了。不动 cache key、不改 entry schema、不动 epoch ⇒ 现有缓存全部继续有效。
(原建议的 (a)「store 时把imports_std写进 entry.json,hit 时 OR 进needsStdModule」
作用在一个已经为真的谓词上,不会改变任何输出。)回归锁
tests/e2e/212_cached_dep_std_is_ordered.sh,已在 CI 跑过。两个陷阱写进了注释:- 全程不删产物 —— 产物缺失让 ninja 以「图过期」的样子失败,快路径回退完整
prepare,未修的二进制也会绿。 - 只断言退出码不够 —— 一台恰好已有
gcm.cache/std.gcm的机器会假绿,
所以另加图断言:_mcpp_staged_cache : phony那一行必须包含std.gcm。
在真实模板上验过
$ rm -rf ~/.mcpp/build-cache/v1/pkg/mcpplibs/imgui@* $ mcpp new a --template imgui:window && (cd a && mcpp build) # MISS → OK $ mcpp new b --template imgui:window && (cd b && mcpp build) Cached imgui v0.0.6 (9 units) Finished dev [unoptimized + debuginfo] in 0.78s # HIT → OK(修复前必挂)
判据留给下一个人:
grep std.gcm build.ninja三秒就能定死它。
生成物是判据,源码是假设。- 全程不删产物 —— 产物缺失让 ninja 以「图过期」的样子失败,快路径回退完整
- added a commit that references this issue
on Aug 17, 2026
Bug: 依赖 BMI 缓存命中时,被恢复的包 BMI 的传递
import std没有进入消费方的构建图最小复现(一个 mcpp 版本,干净缓存)
根因
imgui包的模块import std。消费方main.cpp只有:它自己不
import std。stdBMI 边带进了构建图 ⇒gcm.cache/std.gcm存在 ⇒ 一切正常。gcm.cache,但没有任何东西把stdBMI 边加进消费方的构建图 ⇒std.gcm根本不存在 ⇒ 被恢复的imgui.app.gcm读不到它要的std。两条都成立,互为佐证:
mcpp build --cache offmain.cpp加一行import std;所以缺的不是 key 的某一轴,而是:恢复一个包的 BMI 时,必须把它传递依赖的
std(以及任何它 import 而消费方图里没有的模块)一并纳入消费方的构建图。为什么它看起来像「升级把我弄坏了」
第一个构建它的人(缓存 miss)永远是好的,之后每个人(缓存 hit)都坏。
所以现象是「以前能跑,现在不行」,而实际上和版本无关 —— 我最初正是被这个顺序
误导,把它归因成跨版本投毒;用同一个版本跑两个项目就能证伪。
影响
任何
import std的索引包,只要消费方自己不import std,第二次起就构建失败,而错误信息(
No such file or directory/Bad import dependency)完全不指向缓存。imgui模板是现成的例子 —— 它生成的main.cpp就不import std。绕过
mcpp build --cache offimport std;~/.mcpp/build-cache/v1/pkg/<ns>/<pkg>@<ver>/(只回到 miss,下一个项目还会再犯)环境
mcpp
2026.8.10.1(2026.8.8.2/2026.8.8.4同样复现,早于最近这批改动)、gcc 16.1.0、x86_64-linux-gnu。