Skip to content

feat/RFC: mcpp 外部 Hook 机制 —— 以 build.finished 结果通知为首个事件 #496

Description

@helantianshen

feat/RFC: mcpp 外部 Hook 机制 —— 以 build.finished 结果通知为首个事件

v1 聚焦用户级外部 Hook:mcpp 在构建结束后调用用户配置的程序,并提供结构化构建结果。插件安装与分发作为后续扩展方向。

1. 问题

mcpp 当前没有面向外部工具的命令完成通知机制。

桌面通知、声音提示、构建统计等集成需要获知:

  • 构建是否成功;
  • 最终退出码;
  • 构建耗时;
  • 是否由用户取消;
  • workspace 构建何时整体结束。

现有 build.mcppmcpp::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 定义一个事件:

build.finished

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/binPATH 查找;
  • 带目录部分的相对路径不接受;
  • args 每个元素对应一个独立 argv;
  • 不调用 shell,不进行 shell 展开。

Hook 配置只从:

$MCPP_HOME/config.toml

读取。项目 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 前过滤:

statuses = ["failure"]

表示仅在构建失败时执行。

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

enabledisable 更新全局配置中的 enabled 字段。

提供一次性禁用入口:

mcpp --no-hooks build

以及环境变量:

MCPP_NO_HOOKS=1

嵌套 mcpp 进程继承该禁用状态。

3.6 插件扩展

未来插件可以携带:

  • Hook 可执行程序;
  • Hook manifest;
  • 运行所需资源;
  • 插件自身配置。

插件安装完成后,将其声明注册为 [hooks.<id>]。例如“牛来”可以作为订阅 build.finished 的参考插件,根据 result.status 播放不同提示音。

插件的发现、安装、升级和分发不属于 v1 Hook 的实现范围。

4. 错误路径

场景 处置
onrun 缺失 禁用该 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-hooksMCPP_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. 开放问题

  1. Hook 错误输出应直接显示完整 stderr,还是只显示摘要并把完整内容写入日志?
  2. mcpp hook enable/disable 是否直接修改 config.toml,还是只提供诊断与测试命令?
  3. Hook JSON 是否直接复用现有 mcpp.wire 信封实现,还是仅保持相同的版本字段约定?
  4. cancelled v1 是否只覆盖可捕获的 Ctrl+C,其他平台信号按 failure 处理?
  5. 多个 Hook v1 是否串行执行,后续再考虑并行?

7. 参考


方向认可后可按 §5 拆分实施。

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