Skip to content

Latest commit

 

History

History
120 lines (85 loc) · 5.77 KB

File metadata and controls

120 lines (85 loc) · 5.77 KB

SPEC-003:退出码契约

规范编号 SPEC-003
标题 mcpp 的进程退出码:分类、语义与稳定性承诺
状态 评审中 v1.0
版本 1.0
最后修改 2026-09-01
对应实现 mcpp >= 2026.9.1.1
相关设计文档 .agents/docs/2026-08-08-machine-readable-output-protocol-design.md §R4、.agents/docs/2026-08-31-issue540-seven-audit-findings.md §4
相关 issue #379、#540

0. 这份规范存在的原因

机器可读输出协议的设计记录(§R4)在实测四个退出码之后写下:

光接管 parse error 不够,还要把 usage / runtime / internal 的 rc 映射写成契约, 并覆盖异常边界 —— 否则客户端仍然要靠猜。这条现在是 docs/specs/ 的内容,不是 代码。

被指派的那份契约一直没有写。docs/50-machine-output.md 落地了其中的 usage / internal 一半(270127),runtime 的一半 —— 也就是命令跑了并且 失败时返回的 1 —— 既不在那张表里,也不在别处。#540 由此把表读成「不完整」,并 提出补 4;而 4 恰恰是那几个带信封的命令给不出的码。

两件事都是同一个缺口的症状:没有一处说明 mcpp 一共会返回哪些码。 这份规范是那 一处。

1. 适用范围

本规范约束 mcpp 可执行文件自身的进程退出码。

约束:

  • 子进程(编译器、链接器、ninja、xlings、build.mcpp)的退出码。它们由 mcpp 解释, 不会直接透传;
  • mcpp test 所运行的测试二进制的退出码。测试失败在 mcpp 这一层是一次运行期失败, 按 §2 归入 1;
  • 库层 API 的返回值。status_severity() 这类返回 0..3 的函数是严重性排序, 与退出码无关,禁止被读作退出码。

2. 码表 已实现

类别 含义 通道
0 成功 命令完成了它承诺的事 正常输出走 stdout
1 运行期失败 命令跑了、请求合法、结果是失败 人类可读的原因走 stderr
2 用法错误 未知选项、不支持的选项值、缺少必需参数 stderr
4 环境未就绪 全局配置加载 / 首次初始化失败($MCPP_HOME 不可写、config.toml 损坏、引导 xlings 失败) stderr
70 内部错误 未捕获异常。EX_SOFTWARE stderr
127 未知命令 第一个位置参数不是一个子命令 stderr

41 的分界是谁需要被修:4 说明 mcpp 自己的家还没有准备好,任何命令都会 撞上同一堵墙;1 说明这一次请求失败了,而 mcpp 是可用的。把二者合并会让「我的工程 有问题」和「我这台机器上的 mcpp 有问题」同读数。

701 的分界是这是不是一个缺陷:70 一律意味着 mcpp 有 bug,值得开 issue;1 通常不是。

3. 规则

3.1 分类必须稳定 已实现

一个已经发布的失败场景禁止在后续版本里改变它所属的类别。新增类别可以引入新 的码;把既有场景从 1 挪到 4(或反向)是破坏性变更。

3.2 用法错误必须先于副作用 已实现

返回 2 的路径禁止产生任何副作用。一个还不知道自己会被要求做什么的请求,不该 已经写过磁盘。

3.3 退出码禁止用于协议识别 已实现

客户端禁止用退出码判断「这个 mcpp 支不支持某项功能」。理由见 docs/50-machine-output.md §1:在该协议出现之前发布的每个版本上,未知选项本身就是 一次错误,而它当年走的是 stdout + 退出码 1。唯一跨版本成立的判据是解析 stdout

3.4 1 可以与 stdout 上的信封同时出现 已实现

一次失败可以同时是一份文档。mcpp xpkg parse 对一份违反名字形态的描述符会把 判定作为 JSON 打到 stdout 并且退 1

因此客户端禁止把非零退出当作「没有输出」而跳过解析。这与 §3.3 是同一条规则的两 个方向:退出码不携带「有没有输出」的信息。

3.5 非零退出必须在 stderr 上有原因 已实现

任何非零退出必须在 stderr 上留下至少一行说明。空 stderr 加非零退出是缺陷。

4. 当前实现与本规范的差异

无。§2 的六个码是 src/ 中出现的全部进程退出码。

2026-09-01 穷举核对,记录方法与读数,因为「我数了一遍」不是判据:

$ grep -rhoE "return [0-9]+;" src/ --include=*.cppm --include=*.cpp | sort -u
return 0;  return 1;  return 2;  return 3;  return 4;  return 5;
return 9;  return 19; return 20; return 23; return 127; return 1024;

逐个归类:

属于退出码 出处
0 1 2 127 全仓;127cli.cppm
4 是,11 处,全部同一个原因 config::load_or_init 失败:index_management.cppm×6、doctor.cppm×2、pack/pipeline.cppmcli/cmd_toolchain.cppmpm/commands.cppm 各 1
70 main.cpprc = 70(EX_SOFTWARE),两处
3 runtime_validation.cppmstatus_severity() —— 严重性排序
4 5 9 19 20 1024 runtime/elf.cppm 的 ELF 重定位类型表(R_RISCV_COPY / R_X86_64_COPY / …)
23 toolchain/msvc.cppm 的版本解析

4 同时出现在两栏,这正是为什么这张表按出处而不是按数字归类:同一个字面量在 一个文件里是退出码,在另一个文件里是 EM_RISCV 的重定位类型。仅凭 grep "return 4" 数出来的 13 处里,有 2 处不是退出码。

5. 变更记录

版本 日期 变更
1.0 2026-09-01 首版。落地 2026-08-08 协议设计文档 §R4 指派而未写的契约;补上 14,并说明为什么 4 不属于 docs/11 那张按信封命令划定的表(#540)。