| 项 | 值 |
|---|---|
| 规范编号 | SPEC-007 |
| 标题 | 构建插件:配置、施工与校验的分工,运行时与规划期的义务 |
| 状态 | 草案 v0.7 |
| 版本 | 0.7 |
| 最后修改 | 2026-10-01 |
| 对应实现 | 逐条标注。未注明版本的「已实现」条款对应 mcpp >= 2026.9.26.1;注明 mcpp#702 的条款对应 mcpp >= 2026.9.26.2;注明 mcpp#707、#708、#709、#711 的条款对应 mcpp >= 2026.9.27.1;注明 mcpp#723 的条款对应 mcpp >= 2026.9.28.1;注明 mcpp 2026.9.28.2 的条款对应该版本;§9 与注明 mcpp#734 的条款对应 mcpp >= 2026.9.28.3 |
| 相关设计文档 | .agents/docs/2026-09-26-compile-database-and-issue-699-design.md(§5)、.agents/docs/2026-09-28-ecosystem-design-and-optimisation-plan.md(WS1、WS3)、.agents/docs/2026-09-28-build-cost-foreign-toolsets-and-library-surface-design.md(§3、§4) |
| 相关 issue | mcpp#699、mcpp#701、mcpp#702、mcpp#703、mcpp#707、mcpp#708、mcpp#709、mcpp#711、mcpp#734、mcpp#755 |
| 使用文档 | 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 继承之)不访问网络: 从缓存完成,或以指出缺失内容的消息失败。(作者义务;环境传递 已实现) -
R3.8 action 需要的环境变量与工作目录必须用
env(name, value)与cwd(dir)声明 (协议 13),禁止写成命令中的 shell 语法(NAME=value cmd、cd dir &&),因为 R3.1 不假定 shell。引擎的 action 包装器在运行命令前设置它们:cwd按声明包的根目录解析;声明的 输入、输出与 stamp 在规划时解析为绝对路径,不受cwd影响;命令参数原样传给命令,其中的 相对路径相对于cwd。变量的值属于这条边的命令行,值改变时该 action 重新运行。两者都未 声明的 action,其命令行与协议 12 逐字节相同。(已实现,mcpp#708)
- 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)两个或更多来源为同一目的地各自声明deploy时,规划期不再把它当作错误拒绝:被声明的来源此时可能尚未生成,其内容无法比较。 引擎把它们合并为一条施工边,将每个来源都列为该边的输入;施工时(mcpp stage)逐字节核对 这些来源,字节相同则放置,不同则该边失败,消息点名每一个来源与该目的地。(已实现, mcpp#723) - R4.3 Windows 的可执行文件没有运行路径。
mcpp run通过PATH使用运行时搜索目录,mcpp pack把闭包需要的 DLL 放到程序旁(已实现)。链接之后,引擎把程序直接或间接导入的、 位于其运行时搜索目录中的非系统 DLL 放到程序旁,使从构建目录直接启动的程序同样能找到它们; 闭包的求解与mcpp pack相同,DLL 在其目录中被替换后下一次构建再次放置。(已实现, mcpp#702)一个目的地只有一个写者:本条的放置以 R4.2 与工具链耦合运行时 DLL(toolchain- coupled)合并而成的部署清单为唯一权威,禁止写入该清单已经放置的名字。遇到清单已放置 的名字时,本条只比较该名字现有文件与运行时搜索目录中同名文件的字节,相同则不作声张,不同 则以警告点名这一差异,禁止覆盖清单已放置的文件。规划时在运行时搜索目录中找到的 DLL 同样是推导出的来源,让位于清单中声明的同名目的地,差异由本条的放置报告。(已实现, mcpp#723)这一清单是运行时放置解析器的答案(SPEC-006 §3.7.1):声明优先于工具链,工具链 优先于推导;MSVC C++ 运行时的名字按集合规则决定,不按搜索次序,且其差异不在每次链接时 警告,而由解析器作为打包缺陷说明一次。规划时尚不存在、由prepare在构建中填充的目录里的 运行时名字,由本条的放置以同一个解析器决定。(已实现,mcpp 2026.9.28.2) - R4.5 一条构建边在成功时有话要说(本条的放置比较出差异,或在两个目录提供的同名 DLL 之间
作了选择),必须写入该边的通告文件(构建目录下的
.mcpp-advice/<该边的输出>.advice), 而不是写到只在构建失败或-v时才显示的输出。构建成功后,引擎报告本次运行过的边的通告, 每个事实在一个进程中只报告一次,随后删除这些文件;完整构建路径与快速路径由同一个函数报告。 规划时对同一事实的第二次陈述禁止存在。(已实现,mcpp 2026.9.28.2) - 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 规划不构建宿主工具(SPEC-005 R2.5)。全局工具库中已有的工具照常使用;没有的工具
被推迟,规划产生一条 note
MCPP_BUILD_DATABASE_HOST_TOOL_DEFERRED,点名工具与其所属包, 请求它的构建程序收到该工具将被发布的路径。插件应当在 action 中运行宿主工具,而不是在 构建程序中运行,使规划不依赖工具是否已经构建。(引擎部分 已实现,mcpp#707;此前规划 构建宿主工具,构建失败时降级为警告,mcpp#702;「应当」为作者义务)
- R6.1 插件驱动的工具与 SDK 必须声明为载荷(
[xlings]或[feature-xlings]),需要 时以目标轴选择器门控,并在构建程序中用mcpp::xpkg_dir取得路径。声明必须放在查询发生 的包上:xpkg_dir为正在构建的包回答;host-module的声明对编入它的每个构建程序可见 (docs/31)。(已实现) - R6.2 插件禁止****未经使用者指定探测宿主路径来寻找工具或 SDK:未声明且未被指定的
依赖不可复现。使用者指定的来源(构建程序的选项、
[xlings.overrides]、MCPP_XLINGS_OVERRIDE_<NS>_<NAME>、config.toml)可以是PATH上的程序,因为那是 一次被陈述并被记录的选择。无人指定时回落到PATH的插件必须报告它在用哪一个程序, 并指出使用者如何陈述它;这种回落应当被移除。(作者义务;0.19.0 起 mcpp-plugins 的rules-spirv、rules-slang以警告保留该回落至 2027-04-01) - R6.5 一个工具的来源必须按同一顺序决定,并且必须被记录(mcpp#755):
- 构建程序陈述的选择;2. 该成员历来读取的环境变量;3. 引擎的覆盖
(
[xlings.overrides]);4. 声明的载荷。构建程序陈述了选择时,禁止请求该载荷——这 正是「构建程序自带工具即不下载」的含义。每一次解析必须以mcpp::decision陈述 主体、来源与值,官方通用库mcpp.plugins.tool实现本条,插件应当使用它而不是各自 实现。(已实现,mcpp 2026.10.1.3;mcpp.plugins.tool为 mcpp-plugins 0.19.0)
- 构建程序陈述的选择;2. 该成员历来读取的环境变量;3. 引擎的覆盖
(
- R6.6 只有部分构建需要的工具,其载荷应当声明
provision = "on-request",并在 构建程序中以mcpp::xpkg_request请求:不需要它的构建因此不下载它。请求了载荷的那次 运行必须在不配置依赖该工具的任何东西的情况下返回(mcpp::xpkg_pending(),或mcpp.plugins.tool的found::pending());引擎安装后会再次运行该程序。(已实现, mcpp 2026.10.1.3) - R6.3 插件的某个特性需要本包的程序在构建机器上运行时,应当在该特性上声明
[features.<f>] tools = ["<bin>"],而不是要求每个消费方在依赖边上重复写tools。启用该 特性的消费方得到该工具,与边上写了tools相同(SPEC-004 §10.2)。(已实现,mcpp#709) - R6.4 一个需要随消费方发布、在消费方的目标上运行的程序(更新器、辅助进程)必须以依赖
边的
artifacts取得(SPEC-004 §10.3),禁止以tools取得:tools为构建机器构建, 交叉构建中得到错误架构的程序。action 以${mcpp.artifact:<依赖>/<目标>}引用它的路径。 (已实现,mcpp#711)
- R7.1 使用协议 N 的指令或角色的插件,必须写明第一个支持协议 N 的 mcpp 版本:
mcpp 2026.9.28.3 起写在清单中,
[package] mcpp = ">=<release>"(R9.8),此前写在文档中。索引测量该插件时,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),而不是整次失败。(作者义务)
插件分三层:引擎提供的 mcpp.core(L1);官方通用库 mcpp.plugins(L2),只基于 L1 编写,
由 feature plugins-core 提供;具体插件(L3),即官方的 mcpp.deps.*、mcpp.rules.*、
mcpp.dist.*、mcpp.tools.* 与第三方的 mcpp.<namespace>.*,基于 L1 或 L1 与 L2。依赖只
向下:L3 依赖 L2 或 L1,L2 依赖 L1,L1 不依赖任何一层。
-
R9.1 引擎提供给构建程序的接口名为
mcpp.core。mcpp是它永久等价的写法:两者导出相同 的符号,构建程序可以使用任一写法或同时使用。等价不是兼容措施,不会被移除。(已实现) -
R9.2
mcpp.core只增不删。一个符号只有在弃用六个月之后才可以移除;含义的改变以新的符号 表达。每个符号标注引入它的协议版本。协议版本与 mcpp 版本的对应如下,构建插件以 mcpp 版本 表达需求(R9.8)。(已实现)协议 首个 mcpp 版本 引入的内容 11 2026.9.12.3 deploy12 2026.9.26.2 prepare角色、runtime_search_dir13 2026.9.27.1 action 的 env与cwd14 2026.9.28.3 mcpp.core、构建信息(R9.3)、mcpp::report(R9.4)15 2026.10.1.3 来源(R6.5、R6.6、R9.9): xpkg_source、xpkg_program、xpkg_request、xpkg_pending、phase、decision、toolchain -
R9.3 构建信息以事实陈述解析出的工具链,不针对任何外部构建系统:
tool(role)(这一行的 工具)、abi_tool(role)(目标 ABI 的原生工具,MSVC ABI 上为工具集的cl、link、lib、ml64与 SDK 的rc、mt)、tool_env()(引擎运行这些工具时的环境变量)、toolset_identity()(不含路径的标识)、msvc_instance_dir()、ninja_program()、cxx_runtime()与msvc_crt_linkage()。每个值取自引擎自身命令行所读的同一来源;不适用时 为空。把这些事实翻译成 CMake、vcpkg 等外部系统的写法是插件的职责。(已实现) -
R9.4 构建程序以
mcpp::report({severity, message, impact, hint})陈述诊断。引擎按自身 诊断的形式呈现它,记入--message-format json,degraded在--strict下使构建失败,缓存 命中的运行再次报告它。mcpp::warning(text)等价于只有消息的诊断。(已实现) -
R9.5
mcpp stage --list <file>在一个进程内放置<源>\t<目的>列表中的每一项,每个目的 地保持单文件放置的语义(R4.2)。程序的 deploy 条目在两条及以上时由一条这样的边放置。 (已实现) -
R9.6 构建程序可导入的模块若以
mcpp.开头,第二段为core、plugins、deps、rules、dist、tools的名字只由命名空间为mcpp的包提供;其他包使用mcpp.<自己的命名空间>.*。 引擎对违反者发出警告,因为它无法判断谁是官方(分支、私有镜像均属正当);mcpp-index 把同一 规则作为收录条件。保留的第二段只在本条增加。(引擎警告 已实现;索引收录条件见 mcpp-index) -
R9.7 构建程序导入的模块若由某个依赖在未启用的 feature 之后提供,错误必须写出包名与 feature 名。只在出错时读取这些单元的模块声明。(已实现)
-
R9.8 包以
[package] mcpp = ">=<release>"声明支持的最旧 mcpp 版本,[workspace.package] mcpp为全部成员声明。低于下限的引擎在其他工作之前停止,写出包名、下限、 自身版本与升级命令;只接受>=形式。(已实现) -
R9.9 构建程序可以陈述构建工具链,仅当该工程的
[toolchain]写了configure = "build.mcpp",且仅在工具链阶段(mcpp::phase()为"toolchain")。该阶段 在目标依赖图解析之前运行,禁止陈述工具链以外的任何指令:引擎拒绝并点名第一条不属于 该阶段的指令。陈述的键与[toolchain]表的键相同(spec、path、prefix、sysroot、family、launcher、tool.<role>、origin),官方通用库mcpp.plugins.toolchain提供构造这些陈述的函数。编译并运行构建程序的是 bootstrap 工具链,因此一个无法使用的 自定义工具链必须仍能让构建程序运行并报告原因。(已实现,mcpp 2026.10.1.3)
| 版本 | 日期 | 变更 |
|---|---|---|
| 0.1 | 2026-09-26 | 首版草案(mcpp#699、#701、#702、#703)。 |
| 0.3 | 2026-09-27 | 随 mcpp 2026.9.27.1:新增 R3.8(action 的 env 与 cwd,协议 13,mcpp#708);R5.3 改为规划不构建宿主工具、缺失的工具以 note 推迟(mcpp#707);新增 R6.3(特性的 tools,mcpp#709)与 R6.4(artifacts 与 ${mcpp.artifact:},mcpp#711)。 |
| 0.4 | 2026-09-28 | 随 mcpp 2026.9.28.1:R4.2 同一目标的多个来源在放置时按内容核对,相同则放置一份,不同则失败并点名全部来源;R4.3 一个目标一个写入者,链接后的放置不覆盖另一写入者放在程序旁的文件(mcpp#723)。 |
| 0.5 | 2026-09-28 | 随 mcpp 2026.9.28.2:R4.3 的部署清单是运行时放置解析器的答案(SPEC-006 §3.7.1),MSVC C++ 运行时按集合规则决定,prepare 填充的目录中的运行时名字由同一解析器决定;新增 R4.5,构建边在成功时的通告,构建后报告一次(2026-09-28 设计 WS1、WS3)。 |
| 0.7 | 2026-10-01 | 随 mcpp 2026.10.1.3(mcpp#755):R6.2 改为禁止未经指定的宿主探测并要求报告回落;新增 R6.5(工具来源的顺序与记录)、R6.6(provision = "on-request" 与 xpkg_request)、R9.9(构建程序在工具链阶段陈述构建工具链);R9.2 的协议表新增第 15 行。 |
| 0.6 | 2026-09-28 | 随 mcpp 2026.9.28.3:新增 §9(mcpp#734),即 mcpp.core 与 mcpp 的永久等价、接口的稳定性与协议表、构建信息、结构化诊断、批量放置、插件模块的名字、缺失模块指出 feature、包的版本下限;R7.1 的版本写在清单中;变更记录移为 §10。 |
| 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 标为已实现。 |