Repository navigation
feat: 让依赖的 kind="bin" 产物可被消费者访问(codegen 工具链缺口) #355
Description
问题
一个包无法把自己构建出的可执行文件交给它的消费者。 这让"库自带代码生成器"这一在 C++ 生态里非常普遍的形态在 mcpp 中无法表达。
具体场景:gRPC
mcpplibs.grpc(grpc-m)刚进入 mcpp-index。gRPC 的使用必然涉及代码生成——用户写 .proto,需要 protoc 与 grpc_cpp_plugin 生成 *.pb.cc / *.grpc.pb.cc 再编译。这两个工具本身就是 gRPC/protobuf 源码树里的 bin target,包在构建时完全有能力产出它们。
但消费者拿不到:
mcpp::dep_dir("grpc")/MCPP_DEP_<NAME>_DIR([enhancement] build.mcpp 契约补 MCPP_DEP_<NAME>_DIR(依赖 payload 路径;数据资产包场景) #241)给的是依赖的源码/payload 目录,不是构建产物目录;- 依赖的
kind = "bin"target 不会为消费者构建,也没有任何途径定位。
build.mcpp 本身是完全够用的(mcpp::generated() / mcpp::include_dir() 都在),唯一缺的就是"工具在哪"。
生态惯例:库自带工具
这不是 gRPC 特有的需求,C++ 生态在这一点上相当一致:
| 生态 | 形态 |
|---|---|
| CMake / vcpkg / Conan | find_package(Protobuf) 导出 imported executable target protobuf::protoc;gRPC 导出 gRPC::grpc_cpp_plugin |
| Bazel | @com_google_protobuf//:protoc 就是一个可依赖的构建目标 |
| Rust / prost-build | 早期依赖系统 protoc,现已自带;方向是往"库自带"走 |
npm grpc-tools |
直接分发 protoc + 插件二进制 |
同类需求不止 gRPC:flatbuffers(flatc)、Qt(moc/uic/rcc)、protobuf 单独使用、任何自带 IDL 编译器的库,都是同一个形状。
现有替代方案为什么不够
1. 把生成产物签入仓库(grpc-m 目前的做法)
对模板和 example 合适——开箱即用、零工具依赖。但对真实用户不成立:他们的 .proto 会改,每次都要手工重新生成,而且必须自己保证 protoc 版本与包里的 protobuf 运行时严格匹配(版本错配是运行期而非编译期错误)。
2. 做成 xim: 预编译包 + [xlings] deps
可行(与 xim:make / xim:cmake / xim:nasm 同一层),但代价明显:
- 需要为每个工具单独打包、托管、跨三平台维护——而这些二进制本来就能由包自己构建出来;
- 版本会漂:
xim:grpc-tools@X与mcpplibs.grpc@Y是两个独立的版本轴,用户可能装出不匹配的组合,而 protoc 与 protobuf 运行时版本错配正是最难查的一类问题; - 语义上也不对:它把"这个库的一部分"变成了"一个宿主工具"。
建议的形态
与 MCPP_DEP_<NAME>_DIR 对称:
// build.mcpp
import mcpp;
int main() {
auto protoc = mcpp::dep_bin("protobuf", "protoc");
auto plugin = mcpp::dep_bin("grpc", "grpc_cpp_plugin");
// 调用它们生成 *.pb.cc,然后
mcpp::generated("gen/helloworld.pb.cc");
mcpp::generated("gen/helloworld.grpc.pb.cc");
mcpp::include_dir("gen");
}对应环境变量 MCPP_DEP_<SANITIZED_NAME>_BIN_DIR,沿用 MCPP_FEATURE_ 那套 sanitizer,与 #241 保持一致。
需要设计决策的点
-
触发构建的条件。 依赖的
bintarget 默认不该被构建(多数消费者不需要 grpc_cpp_plugin,而它意味着额外的 libprotoc ~157 TU)。可能的形态:消费者显式声明需要哪个 bin,或由 feature 门控。 -
交叉编译下必须是 HOST 产物。 这一条我认为是本需求里最关键的语义:
build.mcpp按文档"运行在 host 上,即使--target是别的三元组"。代码生成器同理——mcpp build --target x86_64-windows-gnu时,protoc 必须是能在当前机器上跑的 host 二进制,而不是 target 二进制。所以这不能简单复用普通依赖的构建产物,需要一条"为 host 构建该依赖的 bin"的路径(概念上接近 CMake 的IMPORTEDhost tool 或 Bazel 的 exec configuration)。 -
与
[xlings] deps的边界。 我理解[xlings] deps面向的是外部宿主工具(make/cmake/nasm),而本需求是索引内的包自己产出的工具。两者不重叠,不过文档里值得写清楚各自的适用面。
影响面
在这条落地之前,任何"自带代码生成器"的库都只能退回到"签入生成产物"或"另外维护一个 xim 工具包"。对 gRPC 这种 codegen 是必经步骤的库,这直接决定了用户体验:今天用户要么用签入的示例产物,要么自备 protoc + grpc_cpp_plugin 并自行保证版本匹配。
参考:mcpplibs/grpc-m 的 README「Code generation」一节记录了当前的临时做法与原因。
Activity
feat: 依赖产出的 host 工具、构建图节点、规则包(架构文档步 2–6)
test: 覆盖 Form B 描述符的两处解析缺口
refactor: 边图查询收敛成 mcpp.build.dep_graph,而不是再手写一遍
refactor: 边图查询收敛成 mcpp.build.dep_graph,而不是再手写一遍
fix: 真实案例(grpc-m/protobuf)暴露的三个 bug —— 合成 e2e 一个都没抓到
feat(build.mcpp): 机制架构落地 —— 指令表收敛、协议契约、host 工具、构建图节点、规则包 (2026.8.5.1) (…
docs: #359 的架构设计 —— 两个缺口是同一个形状
fix(link): 命令长度上限从架构上消掉,不再补第八个洞 (2026.8.5.4) (#361)
Sunrisepeak commentedon Aug 5, 2026
已实现并发布 —— 2026.8.5.1(#356)。
形状与本 issue 提议的对称形式一致:
底层是
MCPP_DEP_<PKG>_BIN_<TOOL>,与 #241 的MCPP_DEP_<NAME>_DIR同一个通道。几点实现上的取舍,记在这里:
build.ninja的 link unit。后者时序上不成立:build.mcpp在 prepare 期运行,而 plan 与build.ninja都在其之后。required_features:平时它是「特性没开就不产出这个 target」的门;当这个 target 被作为 host tool 点名索取时,它反转成子构建的激活输入。同一个字段,同一个含义,只是解析方向相反。[tools.overrides],给已经有现成二进制的人一条绕过构建的路。端到端验证:
compat.protobuf暴露protoc,tests/examples/protobuf-protoc真的用它生成并编译.pb.ccbuild.mcpp只剩 3 行文档:
docs/05-mcpp-toml.md§2.14、docs/07-build-mcpp.md。本 issue 里没有覆盖的部分(依赖声明条数、
.proto通配符、目录级 rerun)另开在 #359。feat(build): 库可以替用户拉起工具链,构建程序可以声明「一组文件」(#359) (2026.8.6.2) (#366)