feat/RFC: mcpp 外部 Hook 机制 —— 以 build.finished 结果通知为首个事件
v1 聚焦用户级外部 Hook:mcpp 在构建结束后调用用户配置的程序,并提供结构化构建结果。插件安装与分发作为后续扩展方向。
1. 问题
mcpp 当前没有面向外部工具的命令完成通知机制。
桌面通知、声音提示、构建统计等集成需要获知:
- 构建是否成功;
- 最终退出码;
- 构建耗时;
- 是否由用户取消;
- workspace 构建何时整体结束。
现有 build.mcpp 和 mcpp::action 负责构建前配置、代码生成和构建图节点,作用域位于构建过程内部;外部结果通知需要位于最外层 CLI 生命周期边界,在最终退出码确定后执行。
2. 提案
在用户全局配置中增加 [hooks.<id>]:
[hooks.niulai]
on = "build.finished"
run = "mcpp-niulai"
args = ["--volume", "80"]
statuses = ["success", "failure"]
timeout_seconds = 10
enabled = true
v1 定义一个事件:
mcpp build 结束后,mcpp 将构建结果以版本化 JSON 写入 Hook 的 stdin,然后执行配置的外部程序。
核心性质:
- Hook 由用户显式配置,默认不存在;
- Hook 以独立进程运行;
- 命令和参数不经过 shell;
- Hook 失败不改变构建退出码;
- workspace 构建只通知一次;
- 多个 Hook 彼此独立;
- 未配置 Hook 时现有行为不变。
3. 设计
3.1 配置
| 字段 |
类型 |
默认值 |
含义 |
[hooks.<id>] |
table |
— |
Hook 的唯一标识 |
on |
string |
— |
订阅的事件 |
run |
string |
— |
要执行的程序 |
args |
string array |
[] |
传递给程序的 argv |
statuses |
string array |
全部状态 |
匹配的结果状态 |
timeout_seconds |
integer |
10 |
最长运行时间;0 表示不限时 |
enabled |
boolean |
true |
是否启用 |
run 只表示可执行程序:
run = "mcpp-niulai"
args = ["--volume", "80"]
解析规则:
- 绝对路径直接使用;
- 裸程序名依次从
$MCPP_HOME/bin 和 PATH 查找;
- 带目录部分的相对路径不接受;
args 每个元素对应一个独立 argv;
- 不调用 shell,不进行 shell 展开。
Hook 配置只从:
读取。项目 mcpp.toml 和依赖包不能注册用户级 Hook。
3.2 事件语义
build.finished 在最外层 mcpp build 完成后触发。
| 状态 |
定义 |
success |
构建退出码为 0 |
failure |
构建结束且退出码非 0 |
cancelled |
mcpp 捕获到用户取消操作 |
事件行为:
- 普通单包构建触发一次;
- workspace 构建整体结束后触发一次;
- 构建准备、编译或链接失败均产生
failure;
- Hook 中再次调用 mcpp 时不递归触发;
mcpp build --configure-only 不产生 build.finished;
- 无法捕获的进程强制终止不保证产生事件。
statuses 在启动 Hook 前过滤:
表示仅在构建失败时执行。
3.3 输入协议
mcpp 向 Hook stdin 写入 UTF-8 JSON:
{
"schemaVersion": 1,
"kind": "mcpp.hook-event",
"kindVersion": 1,
"event": "build.finished",
"mcpp": {
"version": "2026.8.24.1"
},
"command": {
"name": "build",
"mode": "normal"
},
"result": {
"status": "failure",
"exitCode": 1,
"durationMs": 12840
}
}
版本规则:
schemaVersion 版本化信封结构;
kindVersion 版本化事件内容;
- 同一版本内可以增加可选字段;
- 删除字段或改变字段语义需要提升对应版本。
durationMs 只统计 mcpp 主命令,不包含 Hook 自身执行时间。
Hook 工作目录为当前项目根目录。Hook 不需要解析 mcpp 的终端文本。
3.4 执行
执行顺序:
mcpp build
→ 确定构建状态、退出码和耗时
→ 筛选匹配的 Hook
→ 启动 Hook 并写入 JSON
→ 等待完成或超时
→ 返回原始构建退出码
执行约定:
- Hook stdout/stderr 由 mcpp 捕获;
- Hook 成功时不输出额外文本;
- Hook 无法启动、非零退出或超时时向 stderr 输出 warning;
- 一个 Hook 失败后继续执行其他 Hook;
- Hook 返回值不覆盖主命令退出码;
- Hook 应相互独立,不依赖执行顺序;
- 事件只由最外层 mcpp 进程产生。
例如:
构建退出码 = 1
Hook 退出码 = 2
mcpp 最终退出码 = 1
3.5 管理命令
增加:
mcpp hook list
mcpp hook test <id> --status <success|failure|cancelled>
mcpp hook enable <id>
mcpp hook disable <id>
示例:
$ mcpp hook list
ID EVENT STATUSES PROGRAM STATE
niulai build.finished success,failure mcpp-niulai ready
$ mcpp hook test niulai --status failure
Testing hook 'niulai' with build.finished/failure
Hook finished successfully
enable 和 disable 更新全局配置中的 enabled 字段。
提供一次性禁用入口:
以及环境变量:
嵌套 mcpp 进程继承该禁用状态。
3.6 插件扩展
未来插件可以携带:
- Hook 可执行程序;
- Hook manifest;
- 运行所需资源;
- 插件自身配置。
插件安装完成后,将其声明注册为 [hooks.<id>]。例如“牛来”可以作为订阅 build.finished 的参考插件,根据 result.status 播放不同提示音。
插件的发现、安装、升级和分发不属于 v1 Hook 的实现范围。
4. 错误路径
| 场景 |
处置 |
on 或 run 缺失 |
禁用该 Hook并报告配置错误 |
| 未知事件 |
禁用该 Hook并列出支持的事件 |
未知 statuses 值 |
禁用该 Hook并列出支持的状态 |
timeout_seconds < 0 |
禁用该 Hook并报告有效范围 |
| 程序不存在 |
warning,保持主命令退出码 |
| 程序无法启动 |
warning,保持主命令退出码 |
| Hook 非零退出 |
warning,继续其他 Hook |
| Hook 超时 |
终止 Hook并 warning |
| stdin 写入失败 |
终止 Hook并 warning |
| Hook 输出内容 |
捕获,不写入 mcpp stdout |
| 主命令失败 |
正常执行匹配 failure 的 Hook |
--no-hooks / MCPP_NO_HOOKS=1 |
不执行任何 Hook |
单条 Hook 配置错误只禁用该 Hook,不影响其他 Hook 和主命令。
5. 测试与实施
测试覆盖:
- 未配置 Hook 时构建行为不变;
- 成功构建产生一次
success;
- 失败构建产生一次
failure;
- 取消构建产生一次
cancelled;
- workspace 构建只产生一次事件;
statuses 正确过滤;
enabled = false 不执行;
args 保持 argv 边界;
- Hook 非零退出不改变构建退出码;
- Hook 超时后主命令正常返回;
- Hook 输出不污染 stdout;
- 嵌套 mcpp 调用不递归;
--no-hooks 和 MCPP_NO_HOOKS=1 生效;
--configure-only 不触发;
hook list/test/enable/disable 与配置一致;
- Windows、Linux、macOS 使用相同配置和协议。
实施拆分:
| PR |
内容 |
| 1 |
Hook 配置模型、校验、事件与 JSON 协议 |
| 2 |
build.finished 执行器、超时、递归保护、退出码保持与 E2E |
| 3 |
mcpp hook 管理命令、文档和三平台覆盖 |
参考插件可在 Hook v1 稳定后独立实现,不阻塞核心 Hook 合入。
6. 开放问题
- Hook 错误输出应直接显示完整 stderr,还是只显示摘要并把完整内容写入日志?
mcpp hook enable/disable 是否直接修改 config.toml,还是只提供诊断与测试命令?
- Hook JSON 是否直接复用现有
mcpp.wire 信封实现,还是仅保持相同的版本字段约定?
cancelled v1 是否只覆盖可捕获的 Ctrl+C,其他平台信号按 failure 处理?
- 多个 Hook v1 是否串行执行,后续再考虑并行?
7. 参考
方向认可后可按 §5 拆分实施。
feat/RFC: mcpp 外部 Hook 机制 —— 以
build.finished结果通知为首个事件1. 问题
mcpp 当前没有面向外部工具的命令完成通知机制。
桌面通知、声音提示、构建统计等集成需要获知:
现有
build.mcpp和mcpp::action负责构建前配置、代码生成和构建图节点,作用域位于构建过程内部;外部结果通知需要位于最外层 CLI 生命周期边界,在最终退出码确定后执行。2. 提案
在用户全局配置中增加
[hooks.<id>]:v1 定义一个事件:
mcpp build结束后,mcpp 将构建结果以版本化 JSON 写入 Hook 的 stdin,然后执行配置的外部程序。核心性质:
3. 设计
3.1 配置
[hooks.<id>]onrunargs[]statusestimeout_seconds100表示不限时enabledtruerun只表示可执行程序:解析规则:
$MCPP_HOME/bin和PATH查找;args每个元素对应一个独立 argv;Hook 配置只从:
读取。项目
mcpp.toml和依赖包不能注册用户级 Hook。3.2 事件语义
build.finished在最外层mcpp build完成后触发。successfailurecancelled事件行为:
failure;mcpp build --configure-only不产生build.finished;statuses在启动 Hook 前过滤:表示仅在构建失败时执行。
3.3 输入协议
mcpp 向 Hook stdin 写入 UTF-8 JSON:
{ "schemaVersion": 1, "kind": "mcpp.hook-event", "kindVersion": 1, "event": "build.finished", "mcpp": { "version": "2026.8.24.1" }, "command": { "name": "build", "mode": "normal" }, "result": { "status": "failure", "exitCode": 1, "durationMs": 12840 } }版本规则:
schemaVersion版本化信封结构;kindVersion版本化事件内容;durationMs只统计 mcpp 主命令,不包含 Hook 自身执行时间。Hook 工作目录为当前项目根目录。Hook 不需要解析 mcpp 的终端文本。
3.4 执行
执行顺序:
执行约定:
例如:
3.5 管理命令
增加:
示例:
enable和disable更新全局配置中的enabled字段。提供一次性禁用入口:
以及环境变量:
嵌套 mcpp 进程继承该禁用状态。
3.6 插件扩展
未来插件可以携带:
插件安装完成后,将其声明注册为
[hooks.<id>]。例如“牛来”可以作为订阅build.finished的参考插件,根据result.status播放不同提示音。插件的发现、安装、升级和分发不属于 v1 Hook 的实现范围。
4. 错误路径
on或run缺失statuses值timeout_seconds < 0failure的 Hook--no-hooks/MCPP_NO_HOOKS=1单条 Hook 配置错误只禁用该 Hook,不影响其他 Hook 和主命令。
5. 测试与实施
测试覆盖:
success;failure;cancelled;statuses正确过滤;enabled = false不执行;args保持 argv 边界;--no-hooks和MCPP_NO_HOOKS=1生效;--configure-only不触发;hook list/test/enable/disable与配置一致;实施拆分:
build.finished执行器、超时、递归保护、退出码保持与 E2Emcpp hook管理命令、文档和三平台覆盖参考插件可在 Hook v1 稳定后独立实现,不阻塞核心 Hook 合入。
6. 开放问题
mcpp hook enable/disable是否直接修改config.toml,还是只提供诊断与测试命令?mcpp.wire信封实现,还是仅保持相同的版本字段约定?cancelledv1 是否只覆盖可捕获的 Ctrl+C,其他平台信号按failure处理?7. 参考
build.mcpp构建程序~/.mcpp/config.toml全局配置方向认可后可按 §5 拆分实施。