Skip to content

依赖包 build.mcpp 的 mcpp::action 产物未排在该包自身编译之前(竞态,间歇性失败) #534

Description

@Sunrisepeak

概述

依赖包 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,编译边 || 它)。这样文档推荐的声明式写法在依赖包里也能用,而不是只在根工程能用。


相关

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions