Commit d1b2c11
authored
feat(build.mcpp): 机制架构落地 —— 指令表收敛、协议契约、host 工具、构建图节点、规则包 (2026.8.5.1) (#356)
* feat(build.mcpp): 指令定义收敛成一张表 + 协议版本/缓存 epoch/运行上限
`build.mcpp` 机制的架构地基。设计见
`.agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md`(本次实现步 0 + 步 1)。
## 新增一条 directive 从改 9 处降到改 1 处
一条指令原本要在九个地方定义:`Directives` 字段、`parse_line` 分派、
`write_cache`、`read_cache`、`apply`、`cache_fresh` 的产物存在性校验、
`prepare.cppm` 的 `DirectiveMark`、`markDirectiveTail`、
`foldDirectiveTailIntoPrivateBuild`,再加内置 `mcpp` 模块的类型化包装。
`prepare.cppm:2611` 的注释还**自己承认**这个拆分仍不完整
(「Link/source/fingerprint residues stay at the call sites」)。
这是本仓库反复付过学费的「同一决策在 N 处推导」形态(#233/#240/#242/#344):
它不在你新增指令时失败,而是过一阵子在别的地方失败。
现在一条指令 = 新模块 `src/build/directives.cppm` 里 `kTable` 的**一行**,
解析 / 缓存读写 / 落盘应用 / 声明产物契约 / 私有作用域折叠全部由该行驱动。
**作用域是必填字段**:`include-dir` 之所以私有,是「构建期程序不得静默拓宽包的
公开接口」这条供应链规则,把它变成表里一列意味着下一条指令不回答这个问题就加不进来。
为什么是新模块而不是继续写 `build_program.cppm`:那个文件的匿名 namespace 在
clang 22 + C++20 modules + -O2 下会**误编译自己的邻居**(PR#332 证实一个从未被
调用的新函数就足以破坏 `contract_env`,PR#334 复现)。`mcpp.build.hostprogram`
当初就是为此拆出去的。
## S1 线协议版本
用 `import mcpp;` 的程序在 `main` 之前自动声明 `mcpp:protocol=<N>`(占位符替换
注入,不硬编码 ⇒ 声明值与引擎校验值不可能漂移)。于是:
- 声明的协议**高于**本 mcpp 所理解 → 拒绝执行并给升级提示。原行为是警告后忽略,
那会让「构建成功了但那个 flag 根本没到」静默发生。
- 双方已被证明一致 ⇒ **未知指令即错误**(同版本内只可能是拼写错误)。
手写 printf 的程序什么都不声明,**保留**警告并忽略。这条不对称就是兼容性契约:
那一面冻结在现有 11 条指令上,新能力只在类型化 API 里落地。
## S2 缓存语义 epoch
缓存里的指令是命中时**原样重放**的,所以引擎改变某条指令的**解释**时旧条目必须
失效。纪律照抄 `cache_key::kCacheEpoch`(只在旧条目真不可用时 bump,与 mcpp 版本
号解耦)。一条本 mcpp 不认识的 `d` 记录同样作废整条条目 —— 只重放认识的那部分,
等于应用了程序所要求的一个**真子集**。
## S3 运行上限
默认 600s,超时杀掉并让构建失败,错误**点名是哪个包**并说明怎么改。原先没有任何
上限:死循环或卡在网络读上的构建程序会让整个构建挂死且毫无诊断。
编译刻意不设上限 —— 与 `mcpp test` 同一条不对称纪律(run 有限 / build 无限):
编译跑得久通常正当(首次构建 std 模块就是分钟级),杀掉只会产生莫名其妙的失败。
`capture_exec_deadline` 顺带补上 `cwd` 形参 —— 没有它,加超时会**静默改变**构建
程序相对写入的落点。
## 其它
- 内带 xlings 升级到 2026.8.5.1(13 个 pin 由 check_version_pins.sh 机器校验)
- 版本 2026.8.5.1
## 验证
- 单测 56/56(新增 `BuildDirectives` 21 例:表完整性、解析、协议三分支、
缓存 round-trip、apply 路由、私有折叠)
- e2e:12 个 build.mcpp / 生成源用例全绿,新增 186 号覆盖协议三分支 + 超时 +
缓存四条失效路径
- `03_multi_module` 在本机失败,但已发布的 2026.8.4.1 **同样失败** ⇒ 与本次无关
* fix(ci): bootstrap pin named a version the index no longer serves
`ci-aarch64-fresh-install` has been red on main since 2026-08-03 with
[error] xlings: version '2026.8.3.2' not found for 'mcpp'
[error] available: 2026.8.4.1
`.xlings.json` 的自举 pin 是**自举起点**,故意滞后、不随发布走(docs/09 §4),
所以平时不该动它 —— 但它的一条硬约束是**必须命名一个可安装的版本**。索引已不再
提供 2026.8.3.2,于是那条约束被打破了,这正是必须 bump 的场合(而不是「发版顺手
bump」那种误用)。
改到 2026.8.4.1:错误信息本身证明它可安装,且 ≤ 正在构建的 2026.8.5.1,
check_version_pins.sh 的方向约束满足。
与本 PR 的其余改动无关 —— 是先前就红的,顺手修掉。
* feat: 依赖产出的 host 工具、构建图节点、规则包(架构文档步 2–6)
实现 `.agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md` 的步 2–6,
接在同一文档步 0+1 之后。核心是把 build.mcpp 里「**配置**」与「**施工**」这两件被
混在一起的事分开:程序继续回答「这次构建长什么样」,而「把这批输入变成那批输出」
交给构建图。
## 步 2 — 依赖产出的 host 工具(#355)
一个包能构建出消费者构建期需要的二进制(protoc / grpc_cpp_plugin / flatc / 转译器),
但消费者拿不到:`dep_dir()` 给的是源码树,依赖的 `kind="bin"` target 从不被构建。
**为什么不能是主图里的节点**:时序。build.mcpp 跑在 prepare 内,BuildPlan 还不存在、
build.ninja 更在其后 —— 主图产出的东西对需要它的程序永远来得太晚;叠上交叉编译连
架构都不对。所以走**嵌套的、面向 host 的子构建** + 全局 store(Cargo build-dependencies /
Bazel exec configuration / vcpkg host:true / Conan tool_requires 的形状)。
**为什么便宜**:工具是可执行文件,与主构建零 ABI 接触 ⇒ 子构建可用工具包自己的
toolchain / profile / 依赖解析,不必与消费者一致。
单一版本轴(工具版本=依赖版本,protoc 与运行时错配结构上不可表达)、默认关闭、
成本门复用已有 features+required_features、全局 store 按 版本×host 工具链×feature×
依赖闭包 缓存。逃生舱 `[tools.overrides]` / `MCPP_TOOL_*` 直接指现成二进制并**跳过
构建** —— 每个同类系统都有这一条,且**刻意不进 cache key**。
前提是**工作目录外置**:一次 build 往工程根写 5 处,而「注册表包根共享/可能只读/
绝不写入」是 build_program.cppm 自 G2 起的明文不变量。五处一起搬 —— 只搬一部分比
一处不搬更糟。
## 步 3+4 — `mcpp:action=` 构建图节点
一个原语三种接线(role 只决定输出接到哪):source 进编译集、check 产 stamp 且**默认
与编译并行**、artifact 的输入是链接产物。顺序全由 ninja 的文件依赖决定,不需要 phase
机制 —— 这也是 artifact 不会像朴素 post 钩子那样把自己重复施加一遍的原因。
**必须写出输出文件名**(INV-D):prepare 期就定死源码集/fingerprint/模块图。畸形
action 是硬错误。命令是 argv 而非 shell 字符串,插值只有封闭的四个。
## 步 5 — `host-module = true` 规则包
规则以普通 mcpp 库包分发,消费者 `import mcpp.rules.x;`。有版本、能测试、能发布,
用 **C++** 写 —— 不引入第二门语言(xmake Lua rule / Bazel Starlark)。
**关键**:规则模块与 build.mcpp **同一条命令、同一套 flag** 编译。不是优化 —— BMI 只
对与它在 standard/dialect/编译器身份上一致的编译可用,两次独立解析的构建没有理由
一致,而不一致表现为 `module X CRC mismatch` 而非清楚的错误。
## 步 6 — 生成的 .cppm
核实结论:真正的阻塞点不是 topoOrder 的**顺序**(那只是名字普查 + 发射次序),而是
未扫描的文件**根本没有 graph.units 条目**。解法用代码库已有的答案 ——「声明而非发现」:
action 的 `.provides()/.imports()` 让 mcpp 播下带该声明的占位文件,prepare 期扫描与
生成器将产出的内容一致,build 期由编译器自己的 P1689 复核。
## 顺带修
`.xlings.json` 自举 pin 指向索引已不再提供的 2026.8.3.2 —— `ci-aarch64-fresh-install`
自 2026-08-03 在 main 上就是红的。自举 pin 平时不该动,但它有一条硬约束是必须命名
可安装的版本,这条被打破时正是该 bump 的场合。
## 验证
- 单测 56/56
- e2e 19/21 通过;失败的 `07_static_library`(本机 binutils payload 的 ar 跑不起来)与
`09_path_dependency`(ninja missing dep BMI)在**已发布的 2026.8.4.1 上同样失败** ⇒
环境性,非回归
- 新增 3 个 e2e:187(host 工具:端到端 / 成本门 / 默认关闭 / 错名报可用列表 / override)、
188(三种 role + 增量 + 失败的 check 让构建失败 + 畸形 action 被拒)、
189(规则包:导入生效 / 编辑规则触发重跑 / 缺 lib root 的诊断)
* docs: 记录步 2–6 的实施、五个开放问题的定案与两个硬坑
架构文档补 §10:
- §8 五个开放问题全部定案(封闭命令词表 / JSON 载荷扩展 / check 默认并行 /
mcpp.rules.* 只是约定 / 第 5 问问错了)
- 两个实现坑写进文档而不是留在 commit 里:
· `${mcpp.target_file:}` 必须解析成 build-dir 相对路径 —— ninja 按「边声明的
那个字符串」标识文件,指向同一份字节的绝对路径是**另一个节点**
· 规则包的 BMI 不能走子构建 —— 两次独立解析的构建没有理由在 standard/dialect/
编译器身份上一致,而不一致的表现是 CRC mismatch 不是清楚的错误
- 步 6 的核实结论:问的问题本身错了。阻塞点不是 topoOrder 的顺序,而是未扫描的
文件根本没有 graph.units 条目;解法用代码库已有的「声明而非发现」
* test: 覆盖 Form B 描述符的两处解析缺口
#355 需要 xpkg (.lua) 解析器读两个它此前**根本没读**的键,而两处都是静默的
——没读的键就只是「这个特性不存在」:
- `targets.<x>.required_features`:描述符因此无法表达让可选 host 工具变得
可负担的成本门(compat.protobuf 的 protoc 会带进 libprotoc 的 ~157 个 TU,
只要运行时的消费者绝不能编译它)。没有这个门,target 要么总是构建、
要么根本拿不到。
- `deps` 的值此前只能是版本**字符串**,于是 Form B 描述符压根没有语法去向
自己的依赖请求工具。
之前的 e2e 只走了 Form A(path 依赖)路径,这两处没有任何覆盖。第三个用例钉住
「未知 dep 键要被记录而不是吞掉」——半配置的依赖且毫无信号,正是既有的
per-feature 键记录机制要防的那种失败。
* docs(zh): 同步 host 工具、构建图节点、规则包三节
docs/zh/05 §2.14 与 docs/zh/07 的 dep_bin / action 两节 —— 与英文版一一对应。
* test(e2e): 钉住「改 action 的命令」也会生效
与「改 action 的输入」是两条不同的路径,必须都成立,否则改了生成器的调用方式会被
静默忽略:
- 改**输入** → ninja 直接看到,重跑那条边
- 改 **build.mcpp** → ninja 跟踪的东西一样没变;是 build.mcpp 自己重跑(源码在它
的缓存键里)、重新声明出一条命令不同的 action,ninja 再因为 rule 的 command
变了而重跑
只测前者会让后者的回归完全隐形。
* fix(tool-store): upstream key 走传递闭包,与注释声称的一致
注释写的是「recursive (Merkle)」,实现只遍历了直接边 —— 注释**多声称了一个它没有
的性质**,这比没有注释更糟。
两个选择:改注释、或让代码兑现。选后者,因为传递闭包确实更对:对索引包来说直接边
够用(冻结的版本改不了自己的依赖),但 **path 依赖可以** —— 往下两层改一处,工具的
直接依赖列表纹丝不动,于是 store 里留下一个陈旧的二进制。那是**静默的错误产物**,
本项目为这一族问题付过不止一次学费,而闭包遍历几乎不要钱。
* refactor: 边图查询收敛成 mcpp.build.dep_graph,而不是再手写一遍
自审时发现的问题,而且正是本 PR 通篇在反对的那个模式:我给 tool store 的 upstream
key **手写了一遍图遍历**,而 prepare.cppm 里**已经有**一个走同一张边图的递归遍历
(`self(self, …)`,构建缓存的 per-package key),带 memo 和环检测 —— 我那份是它的
一个更弱的副本。
核实后:`dependencyEdges` 有 14 处读者,其中大多数问的是同两个问题 ——「X 直接依赖
谁」和「X 的传递闭包」。两个 build.mcpp 调用点(发 `MCPP_DEP_<NAME>_DIR` 的那两处)
已经漂成了近乎逐字重复的两份,而 #355 正要再加第三个变体。
新增 `src/build/dep_graph.cppm`:
- `direct_dependencies` / `transitive_dependencies`(按边类型模板化 ——
`DependencyEdge` 是 prepare_build 里的局部结构体,把它搬出来的代价远大于这次要
还的债)
- `name_spellings`:一个依赖可被寻址的两种拼写(canonical 与去 namespace 的尾段)。
不是图查询,但放这里理由相同:三处各自展开过同一个 rfind 分割,第四处正要出现。
**刻意不迁移**构建缓存那条递归 fold:它不是闭包查询 —— 它对每个节点算一个值(该包
完整的 cache key)、把环当**硬错误**而不是跳过、还顺带穿了一个 taint 标记。把它塞进
通用遍历要么丢掉这些性质,要么让抽象一路长到只描述一个调用者。**回答不同问题的两次
遍历不是重复。**
验证:单测 57/57;125/145(正是覆盖 MCPP_DEP_*_DIR 的两个)与 111/186/187/188/189
全通过。
* fix: 合入前深度自审发现的四个问题(含一处内存安全、一处 workspace 回归)
## 1. 内存安全:内置 mcpp 模块的 action 缓冲区越界
`action::add()` 把上界**硬编码成 4096**,却被 `provides_[1024]` / `imports_[1024]`
调用 —— 最多越界写约 3KB。这段代码会被编进**每一个**用户的 build.mcpp,是本次改动
里最严重的一处。
修法:上界改成**参数**(`sizeof buf` 在调用点取)。一个「不挨着它所约束的数组」的
上界,正是会越界的那种形状。同时:
- `command_` 原本 8192 却被 4096 的上界截半,现在按实际容量走
- 缓冲区放大到真实生成器调用的量级(protoc 带一堆 -I 的命令行很长)
- 溢出不再静默截断:置 `overflow` 标记,引擎给出**点名容量限制**的诊断,而不是让
作者去找一个其实没错、只是太长的「拼写错误」
## 2. workspace 回归:成员的 target/ 与 mcpp.lock 落到了 workspace 根
`workRoot` 在 `root` **尚未定稿**时就取了值 —— workspace 那段会把 `root` 改成选中的
成员(`root = memberDir`),于是成员的 `target/`、`mcpp.lock`、`.mcpp/`、
`compile_commands.json` 全落到 workspace 根。
CI 抓到了(macOS 与 Linux 的 `35_workspace` 报 "hello binary not found",
`120_ws_root_indices` 报 "expected x.widget2 lock entry")。修法:把 `workRoot` 的
推导移到 workspace 段**之后**,并在原处留注释说明为什么不能在那里取。
教训记下来:我本地的回归面**太窄** —— 只跑了 build.mcpp 相关的用例,而这次改动动的是
「mcpp 往哪写」,workspace 才是它最敏感的形态。
## 3. 并发:两个工程会共用同一个工具子构建的 scratch 目录
tool store 是**全局**的,所以两个工程可能同时要同一个工具。原实现共用
`<entry>/build`,于是它们并发写同一棵 ninja 树,先完成的那个还会
`remove_all` 把另一个的目录端掉。改成按**消费方**哈希隔离;被共享的是发布出来的
二进制,不是 scratch。哈希而非随机,这样重跑能复用自己的 scratch。
## 4. JSON 有效性:除 \n 外的控制字符没转义
`\t` / `\r` 会通过 Windows 路径和日志文本进来,不转义就直接不是 JSON 了。
## 文档
超时那条原文写成了跨平台生效 —— **Windows 上不生效**(进程启动器没有
kill-by-handle 的路径),与 `mcpp test --timeout` 是同一条限制。明说,而不是含糊过去。
## 性能(实测,非断言)
在这台机器上**测不出可分辨的差异**:无 build.mcpp 的工程强制 prepare,15 次中位
OLD 156ms / NEW 140ms(min 62/96,max 181/194)—— 分布高度重叠,两个方向都出现过。
结构上也符合:新增路径全是 O(小),且未被使用时全部惰性(工具请求为空、
`plan.actions` 为空、hostModules 为空则一行都不多跑)。
## 覆盖面的诚实说明
四个新 e2e 都声明 `# requires: gcc`,而 Windows runner **不提供** gcc 能力 ——
所以 action / host 工具 / 规则包这三条路在 Windows 上**未经验证**。它们在未使用时
完全惰性,不会影响既有 Windows 行为,但这不等于验证过。
验证:单测 57/57;`35_workspace` / `120_ws_root_indices` / `90_workspace_test` 三个
先前红的全绿;186–189 与 43/76/30/51/50/171/04/02 全通过。
* fix(actions): ninja 转义与未解析目标 —— 自审第二轮
## 1. action 命令里的 `$` 会被 ninja 吃掉
命令 token 只做了 shell 引号,没做 ninja 转义。**引号救不了它**:ninja 在 shell 被
调用之前就会展开 `$foo`,所以带字面 `$` 的 token(含 `$` 的路径、
`-Wl,-rpath,$ORIGIN`、一段 awk 程序)会被当成变量引用。
改成本文件其余地方一直用的那个配对(与 `include_dir_token` 同序):**先 ninja 转义,
再 shell 引号**。对普通 token 无影响 —— `escape_ninja_chars` 只碰空格 / `$` / `:`,
而 `shell_quote_arg` 对不含元字符的串逐字节原样返回。
这里每个元素**按构造就是一个 argv token**(类型化 builder 一次追加一个),这正是
逐 token 引号成立的前提 —— #331 表明手工拼出来的 flag blob **不**满足这个前提。
顺带把 `escape_ninja_chars` 从匿名 namespace 移出并导出:它此前是内部的,而
ninja_backend 需要它。让它继续内部化就意味着 ninja 转义规则第四份手写副本,而那
正是它们会漂的原因。
## 2. `${mcpp.target_file:拼错}` 静默变成空串
未解析的引擎变量原本会被替换成空字符串,于是生成一条路径为空的边,ninja 在离
错误很远的地方报出来。现在是**硬错误**,并列出本次构建里存在的 target,还提示
「被 required_features 门住的 target 在那些 feature 未激活时不存在」。
## 测试
188 补两条:字面 `$` 能原样到达工具;未知 target 引用报错并**点名**。
两条放在自己的最小工程里 —— `app` 的 main.cpp 故意依赖生成出来的符号,在它上面
换掉 build.mcpp 会让**链接**失败,那说明不了这两条在测什么(我第一版就是这么写的)。
验证:单测 57/57;13 个 e2e 全绿,含先前红的 35_workspace / 120_ws_root_indices。
* test(e2e): 覆盖带 namespace 的包的两种工具寻址拼写
187 里的 toolpkg 没有 namespace,于是它的 canonical 名与去 namespace 的短名是同一个
串 —— 只发出一个环境变量,两种拼写那条路**从未被走到**。
补一个 `myns.tp` 的用例:消费者写 `myns.tp` 或 `tp` 都必须解析到同一个工具,与
mcpp::dep_dir() 两种拼写都接受保持一致。
* test: 让 directive 的路径断言跨平台成立
Windows CI 报 BuildDirectives.TransformsAreAppliedOnceAtParseTime 失败 —— 是**我这个
测试**的 bug,不是产品 bug:
- 它把期望写成了字面 POSIX 串("/pkg/inc"),而 lexically_normal() 在 Windows 上会
转成反斜杠
- 更糟的是 "/abs/inc" 在 Windows 上**根本不是绝对路径**(没有 root name),于是
「绝对路径原样保留」那半条断言在那里测的是相反的东西
改成用同一套 std::filesystem 路径运算构造期望,并用 current_path() 派生出一个在**当前
平台上确实绝对**的输入。这样断言表达的是**行为**(「相对路径按包根解析」「绝对路径原样
保留」),而不是某一个平台的分隔符。
* fix: 真实案例(grpc-m/protobuf)暴露的三个 bug —— 合成 e2e 一个都没抓到
拿 #355 的原始动机案例做端到端验证:让 mcpp 从 compat.protobuf 的源码构建出
protoc,再用它给一个消费者生成 .pb.cc。结果是**跑通了**(见下),但过程里连撞三个
bug —— 全部是我四个合成 e2e 覆盖不到的形态,因为它们用的都是 path 依赖(Form A)。
## 1. Form B(compat 描述符)包根本不能当工具提供方
工具子构建走 `prepare_build`,而它从 `<root>/mcpp.toml` 读 manifest。**compat 形态的
包没有 mcpp.toml** —— 它的 manifest 是解析期从 `.lua` 描述符合成出来的。于是子构建
第一步就死在 `cannot open .../mcpp.toml`。
这不是小众情况:索引里绝大多数是 Form B,**protobuf 就是**。也就是说 gRPC 那条链
(protoc 来自 compat.protobuf)整个走不通。
修法:`BuildOverrides::preloaded_manifest` —— 由调用方把它**已经合成好**的 manifest
交给子构建。这比重新推导也更正确:重新推导可能得到与父构建**不同**的 manifest
(L1 cfg 合并、feature 激活出的依赖那时已经折进去了)。
必须传**未经 feature 激活的那份**(`dep_manifests[i-1]`,而不是 `packages[i].manifest`
——后者是被 `apply()` 改过的副本):子构建会激活自己的 feature 集,从已激活的副本出发
会把同一批 feature 源码折进去两次。
> 顺带一个 GCC modules 约束:`preloaded_manifest` 一开始写成
> `std::optional<Manifest>`,而 `BuildOverrides` 是**导出**结构体 —— GCC 直接写不出
> module cluster(`failed to read compiled module cluster 529: Bad file data`,在
> mcpp.build.execute 导入它时炸)。换成 `shared_ptr<const Manifest>` 让导出布局保持
> trivial 即可,顺带也省掉每次工具构建拷贝一份 manifest。
## 2. `targets.<x>.main` 不展开 `*/` 包装 glob
Form B 包的源码在版本目录下的**包装目录**里,所以描述符写 `*/src/foo.cc` ——
`*` 代表 tarball 顶层文件夹,描述符无从知道它叫什么。`[build] sources` 一直会展开
这种 glob,但 `main` 是按字面路径处理的,于是 ninja 拿到一个带 `*` 的路径,报
`missing and no known rule to make it`。
**#355 之前没有任何路径能走到这里**(依赖的 bin target 从不被构建),所以它一直没被
发现。现在在 manifest 定稿后、任何人读 `t.main` 之前解析掉;匹配不到恰好一个就报错。
## 3. action 的**全部**输出都被当成翻译单元
最自然的生成器形态就是 protoc:同时产出 `foo.pb.cc` **和** `foo.pb.h`。原实现把每个
`role=source` 的输出都塞进编译集,于是两者算出同一个对象路径,撞上
`object path collision after uniqueness pass`。
头文件必须**由这条边产出**(别的 TU 要 include 它),但**绝不能被编译**。新增
`is_compilable_output()` 按扩展名过滤;非源码输出照旧声明给 ninja,只是不进编译集。
## 验证结果:protoc 真的能被 mcpp 从源码构建出来
设计文档里标注的那条**未核实风险**(「libprotoc 能否被 mcpp 无 CMake 构建」)现在有
答案了 —— **能**:
- 上游 `src/file_lists.cmake` 的 `libprotoc_srcs` 有 138 项,与 libprotobuf **零重叠**
- 源码树里**没有** `.h.in` / `.cmake.in`,不需要 configure 步骤
- 全部编过,链接一次失败:缺 `upb_*` 符号 —— libprotoc 的 upb 生成器需要 upb 运行时,
那正是 compat.protobuf **已有**的 `upb` feature。把 target 的
`required_features` 写成 `{ "protoc", "upb" }` 即可 —— **这正是成本门机制该起的作用**,
不需要引擎改动
- 端到端跑通:`protoc` 建出 → `dep_bin` 拿到路径 → action 调用它生成 `demo.pb.cc`/`.pb.h`
→ 编译链接 → 程序输出 `NAME=mcpp`
- store 命中实测:第二次 `rm -rf target` 后构建 **1.41s**,不重建 protoc
## 测试
188 补一条 companion 输出(`.cc` + `.h` 同时声明)的用例 —— 就是撞出 bug 3 的形状。
单测 57/57;11 个 e2e 全绿(`09_path_dependency` 在**已发布的 2026.8.4.1 上同样失败**,
环境性)。
* docs(355): 记录真实案例验证结果 —— libprotoc 可构建,风险解除
设计文档 §11 标注的未核实风险(libprotoc 能否被 mcpp 无 CMake 构建)现在有答案:
**能**。138 个 TU 全部编过,源码树无 .h.in/.cmake.in,唯一缺口是 upb 运行时,
而那是 compat.protobuf 已有的 feature —— 用 required_features 就能表达,零引擎改动。
⇒ Phase 2 的 prebuilt provider 确认为纯优化,不是必需路径。
另记三个只有真实案例(Form B 包)才暴露的 bug,与一个 GCC modules 约束
(导出结构体里不要放大的值类型)。
* docs(355): 状态改为已实施1 parent 3f35d01 commit d1b2c11
39 files changed
Lines changed: 5321 additions & 287 deletions
File tree
- .agents/docs
- .github
- actions
- bootstrap-mcpp
- setup-macos-llvm
- workflows
- docs
- zh
- src
- build
- manifest
- platform
- pm
- tests
- e2e
- unit
Lines changed: 477 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 871 additions & 0 deletions
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
155 | | - | |
| 155 | + | |
156 | 156 | | |
157 | 157 | | |
158 | 158 | | |
| |||
292 | 292 | | |
293 | 293 | | |
294 | 294 | | |
295 | | - | |
| 295 | + | |
296 | 296 | | |
297 | 297 | | |
298 | 298 | | |
| |||
363 | 363 | | |
364 | 364 | | |
365 | 365 | | |
366 | | - | |
| 366 | + | |
367 | 367 | | |
368 | 368 | | |
369 | 369 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
126 | | - | |
| 126 | + | |
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
121 | | - | |
| 121 | + | |
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
| |||
255 | 255 | | |
256 | 256 | | |
257 | 257 | | |
258 | | - | |
| 258 | + | |
259 | 259 | | |
260 | 260 | | |
261 | 261 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| |||
288 | 288 | | |
289 | 289 | | |
290 | 290 | | |
291 | | - | |
| 291 | + | |
292 | 292 | | |
293 | 293 | | |
294 | 294 | | |
| |||
358 | 358 | | |
359 | 359 | | |
360 | 360 | | |
361 | | - | |
| 361 | + | |
362 | 362 | | |
363 | | - | |
| 363 | + | |
364 | 364 | | |
365 | | - | |
| 365 | + | |
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
| |||
440 | 440 | | |
441 | 441 | | |
442 | 442 | | |
443 | | - | |
| 443 | + | |
444 | 444 | | |
445 | 445 | | |
446 | 446 | | |
| |||
622 | 622 | | |
623 | 623 | | |
624 | 624 | | |
625 | | - | |
| 625 | + | |
626 | 626 | | |
627 | 627 | | |
628 | 628 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
0 commit comments