You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
rm -rf ~/.mcpp/build-cache/v1/pkg/mcpplibs/imgui@0.0.6/
mcpp new a --template imgui:window && (cd a && mcpp build) # OK ← 缓存 miss
mcpp new b --template imgui:window && (cd b && mcpp build) # FAIL ← 缓存 hit
error: build failed
std: error: failed to read compiled module: No such file or directory
std: note: compiled module file is 'gcm.cache/std.gcm'
std: note: imports must be built before being imported
imgui.app: error: failed to read compiled module: Bad import dependency
$ 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(修复前必挂)
Activity
[-]Bug: 同一 home 用过两个 mcpp 版本后依赖 BMI 全局缓存互相投毒(Bad import dependency)[/-][+]Bug: 依赖 BMI 缓存命中时,被恢复的包 BMI 的传递 import std 没有进入消费方的构建图[/+]speak-agent commentedon Aug 10, 2026
代码位置
src/build/prepare.cppm:651—调用点
:5163:bool needsStdModule = graph_or_targets_import_std(scan.graph, *m, *root);它只看本工程。链条是:
缓存 miss 时依赖在本地编译,它的单元进了图,① 命中,所以第一次总是好的。
修法建议
谓词需要再加一项:本次构建要消费的依赖 BMI 里,有没有 import std 的。
imports_std写进entry.json(那一刻 mcpp 正好知道),hit 时 OR 进
needsStdModule。旧条目缺该字段时按true读,这样已有缓存不会继续坏(代价只是多建一次全局已缓存的 std BMI),也不必动
kCacheEpoch。代价是少数本来不需要 std 的工程也会 stage 一次。
两种都只改这一个谓词及其输入,不动 cache key,因此不会让现有缓存失效。
回归锁
e2e:清空该包的缓存 → 建项目 A(miss,必须 OK)→ 建项目 B(hit,必须也 OK),
消费方源码刻意不写
import std。这正是现在会红的那一步,而现有 e2e 里没有任何一条覆盖它 —— 缓存相关的用例要么消费方自己 import std,
要么只跑一次(永远是 miss)。
feat: 图形栈闭合与分发档位 —— 标签、一条没人依赖的边、一个被钉住的 pin (#405, #407) (2026.8.10.2) (…
Sunrisepeak commentedon 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 跑过。两个陷阱写进了注释:prepare,未修的二进制也会绿。
gcm.cache/std.gcm的机器会假绿,所以另加图断言:
_mcpp_staged_cache : phony那一行必须包含std.gcm。在真实模板上验过
fix(pack): 宿主能力清单从解析后的图取,不再从根 manifest 取 (2026.8.10.3) (#409)
fix(link): #416 — link std.o only into units that actually reach `imp…
ci: workspace mcpp bootstrap pin -> 0.0.102 (released + indexed)