概述
依赖包 build.mcpp 里用 mcpp::action 声明的生成物,没有被排在该包自身编译之前。action 的产物确实成了 ninja 节点,但该包的编译边上没有任何 order-only 依赖指向它们,于是编译和生成器赛跑 —— 通常是编译先跑,拿不到还没生成的头。
实测环境:mcpp 2026.8.27.2。
复现
一个 Form A 依赖包(wayland 的 fork),build.mcpp 用文档推荐的写法声明生成动作:
// mcpp/server/build.mcpp
mcpp::action a;
a.id = "wayland:server-header";
a.role = "source";
a.arg(scanner).arg("-s").arg("server-header")
.arg(xml.c_str())
.arg((out + "/wayland-server-protocol.h").c_str())
.input(xml.c_str())
.output((out + "/wayland-server-protocol.h").c_str())
.submit();
mcpp::include_dir(out.c_str());
同包的 upstream/src/wayland-server.c 通过 <wayland-server.h> 用到那个生成的头。
结果
$ mcpp test
Compiling freedesktop.wayland-server v1.26.0
error: build failed
.../src/wayland-server.c:334:32: error: 'WL_DISPLAY_ERROR' undeclared (first use in this function)
.../src/wayland-server.c:432:48: error: 'WL_DISPLAY_ERROR_INVALID_METHOD' undeclared
.../src/wayland-server.c:445:48: error: 'WL_DISPLAY_ERROR_INVALID_OBJECT' undeclared
但生成物是存在的 —— 构建失败之后去看:
$ find target -name 'wayland-server-protocol.h'
target/.build-mcpp/deps/wayland-server@1.26.0/out/wayland-server-protocol.h
build.mcpp 跑了、scanner 被构建了、action 也执行了 —— 只是顺序不对。
ninja 里的样子
生成物是一个正常节点:
build /…/target/.build-mcpp/deps/wayland@1.26.0/out/wayland-client-protocol.h : mcpp_action_1 /…/protocol/wayland.xml
include_dir 也生效了,out 目录在 -I 上:
build obj/wayland-server.o : c_object /…/upstream/src/wayland-server.c
local_includes = … -I/…/target/.build-mcpp/deps/wayland-server@1.26.0/out
但这条边没有 ||。全文件统计:
$ grep -cE '^build obj/.*\.o *:.*\|\|' build.ninja
0
$ grep -nE '\|\|.*protocol\.h' build.ninja
(无)
也没有把 action 汇总起来的 phony —— mcpp-requested-goals 只汇总 .o 和最终库,不含 action 节点。所以 ninja 可以(而且确实会)在 mcpp_action_* 之前跑 wayland-server.o。
include_dir 的方向是对的(文档 line 67:"they color only this package's own TUs"),缺的只是顺序。
为什么 protoc 那个例子没暴露
tests/examples/protobuf-protoc 里,生成的 .pb.h 是被根工程自己的源文件用的,不是被 protobuf 包自己的源文件用的。根工程那侧看起来是有序的;缺的是依赖包的 action → 同一依赖包的编译这条边。
影响
任何"自己生成、自己编译"的依赖包都会踩到 —— 而这正是需要 build.mcpp 的典型场景(wayland/wayland-scanner、任何 IDL/协议生成器)。表现是间歇性的:并行度、文件系统速度都会影响输赢,所以可能在一台机器上绿、在 CI 上红。
绕开办法(本次采用)
不声明 action,直接在 build.mcpp 里跑生成器。build.mcpp 在 ninja 写出之前执行,所以天然有序:
if (std::system(cmd.c_str()) != 0) { … return 1; }
mcpp::source((out + "/wayland-protocol.c").c_str());
mcpp::include_dir(out.c_str());
代价是丢掉 action 的增量粒度(用 mcpp::rerun_if_changed(xml) 补回一层)。
建议
让一个包的 action 产物成为该包所有编译边的 order-only 依赖(或者给每个包生成一个汇总 phony,编译边 || 它)。这样文档推荐的声明式写法在依赖包里也能用,而不是只在根工程能用。
相关
概述
依赖包
build.mcpp里用mcpp::action声明的生成物,没有被排在该包自身编译之前。action 的产物确实成了 ninja 节点,但该包的编译边上没有任何 order-only 依赖指向它们,于是编译和生成器赛跑 —— 通常是编译先跑,拿不到还没生成的头。实测环境:mcpp
2026.8.27.2。复现
一个 Form A 依赖包(wayland 的 fork),
build.mcpp用文档推荐的写法声明生成动作:同包的
upstream/src/wayland-server.c通过<wayland-server.h>用到那个生成的头。结果
但生成物是存在的 —— 构建失败之后去看:
build.mcpp跑了、scanner 被构建了、action 也执行了 —— 只是顺序不对。ninja 里的样子
生成物是一个正常节点:
include_dir也生效了,out 目录在-I上:但这条边没有
||。全文件统计:也没有把 action 汇总起来的 phony ——
mcpp-requested-goals只汇总.o和最终库,不含 action 节点。所以 ninja 可以(而且确实会)在mcpp_action_*之前跑wayland-server.o。include_dir的方向是对的(文档 line 67:"they color only this package's own TUs"),缺的只是顺序。为什么 protoc 那个例子没暴露
tests/examples/protobuf-protoc里,生成的.pb.h是被根工程自己的源文件用的,不是被 protobuf 包自己的源文件用的。根工程那侧看起来是有序的;缺的是依赖包的 action → 同一依赖包的编译这条边。影响
任何"自己生成、自己编译"的依赖包都会踩到 —— 而这正是需要
build.mcpp的典型场景(wayland/wayland-scanner、任何 IDL/协议生成器)。表现是间歇性的:并行度、文件系统速度都会影响输赢,所以可能在一台机器上绿、在 CI 上红。绕开办法(本次采用)
不声明 action,直接在
build.mcpp里跑生成器。build.mcpp在 ninja 写出之前执行,所以天然有序:代价是丢掉 action 的增量粒度(用
mcpp::rerun_if_changed(xml)补回一层)。建议
让一个包的 action 产物成为该包所有编译边的 order-only 依赖(或者给每个包生成一个汇总 phony,编译边
||它)。这样文档推荐的声明式写法在依赖包里也能用,而不是只在根工程能用。相关