| 项 | 值 |
|---|---|
| 规范编号 | SPEC-007 |
| 标题 | 构建插件:配置、施工与校验的分工,运行时与规划期的义务 |
| 状态 | 草案 v0.2 |
| 版本 | 0.2 |
| 最后修改 | 2026-09-26 |
| 对应实现 | 逐条标注。未注明版本的「已实现」条款对应 mcpp >= 2026.9.26.1;注明 mcpp#702 的条款对应 mcpp >= 2026.9.26.2 |
| 相关设计文档 | .agents/docs/2026-09-26-compile-database-and-issue-699-design.md(§5) |
| 相关 issue | mcpp#699、mcpp#701、mcpp#702、mcpp#703 |
| 使用文档 | docs/30 - build.mcpp、docs/31 - 编写规则包 |
本规范规定构建插件对引擎和对消费方承担的义务,以及引擎为此提供的机制。docs/31 说明怎样编写 插件,本规范规定插件必须满足什么。
规范用语与实现状态标记见 规范索引。只约束插件作者、引擎不做检查的条款标注 「作者义务」。
构建插件是为其他包的构建贡献工作的包,以及它导入消费方构建程序的模块(rule_module、
host-module)。
| 类别 | 例 | 贡献 |
|---|---|---|
| 规则包 | rules-qt、rules-spirv、rules-cuda |
代码生成,设备语言的编译 |
| 依赖适配包 | deps-vcpkg、deps-cmake |
把外部包管理器或外部构建系统的产物接入构建 |
| 分发成员 | dist-wix、dist-apk |
由链接产物生成可安装的分发物 |
项目自己的 build.mcpp 中的同类代码同样适用本规范。
分工。 mcpp 只提供通用机制:指令、action 的角色、stamp、运行时库的搜索与放置。某一个
工具(vcpkg、CMake、Qt)的知识只属于它的插件。插件遇到的缺口若是通用的,由 mcpp 以通用机制
补上(第 3.3 节的 prepare、第 4.1 节的 runtime_search_dir、第 4.3 节的 DLL 放置),而不
由插件绕过。
插件所做的每一件事属于且只属于下表的一类。
| 类 | 定义 | 发生在 | 机制 |
|---|---|---|---|
| 配置 | 决定构建的形状:编译哪些源、用哪些选项、链接什么、运行时在哪里找库 | 构建程序运行时,即规划期 | 指令(第 2 节) |
| 施工 | 产生构建读取的文件:生成源码、编译设备代码、安装依赖前缀、打包 | 构建期 | mcpp::action(第 3 节) |
| 校验 | 判断环境或产物是否满足要求,不产生构建读取的文件 | 构建期 | role = "check" 的 action |
- R1.1 施工必须声明为 action,禁止在构建程序运行期间进行。构建程序可以运行工具 来查询配置所需的答案(版本、选项、路径),禁止借此产生构建读取的文件。(作者义务; action 自 2026.8.5.1 已实现)
- R1.2 校验必须是
checkaction。构建程序发现环境不完整(缺少一个 SDK 模块、缺少 一个库)时,必须用mcpp::warning报告,并输出它能确定的全部配置;禁止因环境不完整 以非零状态退出。编译或链接会在缺失处失败,位于该警告之后。(作者义务) - R1.3 配置必须只取决于构建程序声明的输入:包的清单、声明的载荷,以及
rerun_if_changed、rerun_if_changed_glob、rerun_if_env_changed列出的文件、glob 与 环境变量。配置禁止依赖施工的结果,例如枚举一个安装前缀中由 action 产生的文件:规划 (mcpp emit build-database)与第一次构建都发生在施工之前,依赖施工结果的配置在第一次与 第二次构建之间不同。(作者义务;构建程序的重新运行依据落在一个prepareaction 声明的目录内 时,引擎给出一条警告并点名两者,已实现,mcpp#702) - R1.4 构建程序在规划与构建中必须行为相同。引擎不提供「正在规划」的信号,因为规划
所描述的计划与
mcpp build --configure-only计算的计划相同(SPEC-005 R1.2)。(已实现)
-
R2.1 下表中的配置必须用对应的指令表达;禁止用
link_flag、cxxflag拼写表中 已有指令所表达的内容(例如以-Wl,-rpath,代替运行时搜索目录,以-I代替头文件目录)。 指令由引擎按平台渲染、去重,并进入缓存与构建数据库。(作者义务)配置 C++ 接口 线格式 自 头文件目录 include_dir、include_dir_afterinclude-dir、include-dir-after协议 1 编译选项 cxxflag、cflagcxxflag、cflag协议 1 宏 definecfg协议 1 链接库与库目录 link_lib、link_searchlink-lib、link-search协议 1 其他链接选项(含库的完整路径) link_flaglink-flag协议 8 运行时搜索目录 runtime_search_dirruntime-search-dir协议 12(mcpp#702) 放到程序旁的文件 deploydeploy协议 11 生成的源 generated、source与role = "source"的 actiongenerated、source、action协议 1 重新运行构建程序的依据 rerun_if_changed、rerun_if_changed_glob、rerun_if_env_changed同名 协议 1、2 报告与探测 warning;fact、floor同名 协议 5、7 -
R2.2 相对路径按声明它的包的根目录解析。插件禁止把宿主系统目录(
/usr/include、/usr/lib、/lib、C:\Windows\System32及同类)声明为头文件、链接或运行时搜索目录;SDK 与工具的路径取自声明的载荷(mcpp::xpkg_dir)或依赖边(mcpp::dep_dir、mcpp::dep_bin)。 (作者义务;对图目标的链接,引擎检查-L,mcpp#696 已实现) -
R2.3 插件必须使用现行字段的指令,禁止依赖只为兼容而保留的字段 (
[runtime] library_dirs,docs/04 §2.11)。(作者义务) -
R2.4 链接选项的一个元素按 SPEC-004 §8 读成词,每个词原样到达链接器。插件禁止依赖 引擎内部的转义拼写(例如
'$$ORIGIN')。(已实现,mcpp#703;此前ldflags与link_flag中的$会被宿主 shell 展开)
-
R3.1 action 的命令是 argv,禁止假定 shell。命令调用的工具必须列为输入;命令 自己发现的输入用 depfile 报告。(argv 与 depfile 已实现;工具作为输入是作者义务)
-
R3.2 action 必须在提交时命名它的输出文件,因为引擎在规划期确定源集合、指纹与模块 图。输出文件名在施工前无法得知的工作(安装一个前缀、解开一个 SDK)使用
prepare角色。 (已实现;prepare随 mcpp#702) -
R3.3 角色:
role输出 顺序 source可编译的输出加入声明包的编译集 声明包的每条编译边等待它 object加入链接集 链接边消费它 artifact新文件,输入是链接产物 链接之后 check引擎写的 stamp 与编译并行; blocking = true时声明包的编译边等待它prepare引擎写的 stamp;命令填充它用 output_dir声明的目录,构建按目录引用其内容声明包的每条编译边与计划中的每条链接边(静态库归档除外)等待它 prepare(已实现,mcpp#702)是施工,不是校验,任何只针对校验的策略都不作用于它;构建 输出以PREPARE标注它。它的产物由配置以目录为单位引用(include_dir、link_search、runtime_search_dir),或以link_flag中的完整路径引用;这些名字在配置时确定,内容在施工 时到达。一个prepareaction 必须用output_dir声明它填充的目录(缺少时引擎拒绝该 action);命令成功而该目录不存在,或除该 action 的 stamp 之外不含任何文件时,引擎不写 stamp,并以指出该目录的消息使这条边失败。链接边等待所有prepare,因为每个包的链接全局 指令并入整个计划共用的一份链接意图。 -
R3.4
check只用于校验,禁止用来表示施工。(作者义务) -
R3.5
check与prepare的命令成功后,引擎创建或更新它们的 stamp,使 stamp 新于该 action 的每个输入;命令失败时不写 stamp。命令无需自己写 stamp。(创建自 2026.8.29.1 已实现;更新 已实现,mcpp#702;此前一个已存在的 stamp 不被更新,输入改变一次后该 action 在此后每次构建中都会重新运行) -
R3.6 角色应当以常量书写(
mcpp::roles::source、check、object、artifact、prepare),使不认识该角色的旧引擎在编译构建程序时拒绝它;引擎拒绝未知的角色字符串,并列出 五个角色。(已实现,mcpp#702;此前引擎把未知的角色字符串当作source) -
R3.7 构建期不应访问网络:下载属于安装期(载荷的安装、依赖的解析)。一个必须在构建期 下载的 action(例如由包管理器取得源码)必须在其说明中写明,并在离线构建中 (
--offline或MCPP_OFFLINE=1;前者在进程环境中设置后者,action 继承之)不访问网络: 从缓存完成,或以指出缺失内容的消息失败。(作者义务;环境传递 已实现)
- R4.1 一个目录中的共享库由施工产生(
prepare)或文件名不定时,插件必须用runtime_search_dir声明该目录。引擎把它用于 ELF 与 Mach-O 的运行路径(RUNPATH/rpath, 从不作为-L)、mcpp run的加载路径、mcpp pack的闭包搜索与运行时校验,并把依赖包的 声明传到消费方的可执行文件。(已实现,mcpp#702) - R4.2 一个在配置时已知的文件需要位于程序旁的某个相对位置时(Qt 的平台插件、Vulkan 的
ICD 清单),插件用
deploy。(已实现,协议 11) - R4.3 Windows 的可执行文件没有运行路径。
mcpp run通过PATH使用运行时搜索目录,mcpp pack把闭包需要的 DLL 放到程序旁(已实现)。链接之后,引擎把程序直接或间接导入的、 位于其运行时搜索目录中的非系统 DLL 放到程序旁,使从构建目录直接启动的程序同样能找到它们; 闭包的求解与mcpp pack相同,DLL 在其目录中被替换后下一次构建再次放置。(已实现, mcpp#702) - R4.4 插件禁止在
link_flag中写运行路径(-Wl,-rpath,...),必须使用 R4.1。 (作者义务)
- R5.1 规划运行构建程序,不运行任何 action(SPEC-005 R2.2)。插件遵守 R1.3 时,规划得出 的配置与构建相同。(已实现)
- R5.2 一个包的构建程序在规划中失败时,该包只按其清单描述,并得到一条错误诊断;成员的 其余部分照常描述。插件遵守 R1.2 时,环境不完整不会使构建程序失败。(已实现,mcpp#702)
- R5.3 插件所需的宿主工具在规划中构建失败时,规划继续,构建程序收到该工具将被发布的路径, 并产生一条警告。插件应当在 action 中运行宿主工具,而不是在构建程序中运行,使规划不依赖 工具能否构建。(引擎部分 已实现,mcpp#702;「应当」为作者义务)
- R6.1 插件驱动的工具与 SDK 必须声明为载荷(
[xlings]或[feature-xlings]),需要 时以目标轴选择器门控,并在构建程序中用mcpp::xpkg_dir取得路径。声明必须放在查询发生 的包上:xpkg_dir为正在构建的包回答;host-module的声明对编入它的每个构建程序可见 (docs/31)。(已实现) - R6.2 插件禁止探测宿主路径来寻找工具或 SDK;未声明的依赖不可复现。(作者义务)
- R7.1 使用协议 N 的指令或角色的插件,必须在其文档中写明第一个支持协议 N 的 mcpp
版本。索引测量该插件时,CI 所用的 mcpp 版本移到该版本;索引的
min_mcpp不因此改变。旧引擎 编译该构建程序时因缺少函数或常量而失败,并指出其名称。(编译期失败 已实现) - R7.2 插件禁止依赖引擎内部的拼写与未写入文档的行为(R2.3、R2.4)。(作者义务)
- R8.1 插件必须在它声明支持的每个平台上有一个在该平台运行的判据。
# requires: gcc只在 Linux 成立,不能作为 Windows 或 macOS 的判据。(作者义务) - R8.2 每个判据必须在它所验证的改动之前失败。(作者义务)
- R8.3 插件应当有一个规划判据:它的一个消费方工程,在没有安装其载荷的机器上运行
mcpp emit build-database,得到一份文档,其中该插件只贡献警告,或只使声明它的包缺少 构建程序的指令(R5.2),而不是整次失败。(作者义务)
| 版本 | 日期 | 变更 |
|---|---|---|
| 0.1 | 2026-09-26 | 首版草案(mcpp#699、#701、#702、#703)。 |
| 0.2 | 2026-09-26 | 随 mcpp 2026.9.26.2 落地:R1.3 的警告、R2.1 的 runtime_search_dir、R2.4、R3.3 的 prepare(目录须含文件;链接边等待所有 prepare)、R3.5、R3.6、R4.1、R4.3、R5.2、R5.3 标为已实现。 |