| 项 | 值 |
|---|---|
| 规范编号 | SPEC-005 |
| 标题 | mcpp 输出的构建数据库:内容、取值规则与不写工程目录的保证 |
| 状态 | 评审中 v1.7 |
| 版本 | 1.7 |
| 最后修改 | 2026-09-30 |
| 对应实现 | mcpp >= 2026.9.15.1;v1.3 修改的 R2.5、R3.7、R3.8、R4.1、R5.2 为 mcpp >= 2026.9.26.2;v1.4 修改的 R2.5 为 mcpp >= 2026.9.27.1;v1.5 修改的 R3.7、R3.12、R5.1、R5.2 为 mcpp >= 2026.9.28.1;v1.6 修改的 R2.1、R3.3、R3.4、R3.5、R4.1、R5.2 为 mcpp >= 2026.9.29.5;v1.7 新增的 R3.8a 为 mcpp >= 2026.9.30.2 |
| 相关设计文档 | .agents/docs/2026-09-14-636-build-database-and-the-latest-xlings.md.agents/docs/2026-09-26-compile-database-and-issue-699-design.md |
| 相关 issue | #636, #648, #655, #699, #702, #707, #732 |
| 依据的外部规范 | S1「C++ Build Database: IDE Profile」profile 0.3.0(§7.2 的 generated,Sunrisepeak/mcpp-language-server#28;此前为 0.2.0)与 S2 0.2.0 §3.4,取自 https://github.com/Sunrisepeak/lsp-mcpp-private 提交 b82859d(schema 自提交 28ecd6e 起未变);S2 0.3.0 §3.4 的部分回答(S2-3.4-12、S2-3.4-13,Sunrisepeak/mcpp-language-server#25);JSON Compilation Database |
本规范规定 mcpp emit build-database 输出的文档、文档中每个字段取自构建计划的
哪一部分,以及这条命令对工程目录的保证。文档格式由 S1 与 JSON Compilation
Database 定义,本规范不重复它们的字段定义,只规定 mcpp 作为生产方的义务。消费方
的行为(监视、防抖、超时、把文档补全到 S1 等级 3)不属于本规范。
规范用语与实现状态标记见 规范索引。
| 调用 | 标准输出 |
|---|---|
mcpp emit build-database |
S1 文档 |
mcpp emit build-database --spec compile-commands |
JSON Compilation Database |
以上任一加 --format json |
docs/50 的信封,文档在 data.database |
以上任一加 -o <file> |
无;原本写到标准输出的内容原子地写入 <file> |
- R1.1
--spec的取值为s1(默认)与compile-commands。其他取值是用法错误: 标准输出为空,退出码为 2。已实现 - R1.2 选择器与
mcpp build相同:--target、--toolchain、--profile、--release、--dev、--features、--cap、--accel、--no-accel、--static、--strict、-p/--package、--workspace。同一组选择器下,文档描述的构建计划 与mcpp build --configure-only计算的计划相同;包有测试时,计划包含测试目标与 dev-dependency。已实现 - R1.3 规划过程的叙述写到标准错误,标准输出只有文档或信封。已实现
- R2.1 命令禁止写入工程目录,即根包、工作区成员与 path 依赖的源码树。规划
写入
$MCPP_HOME/cache/build-database/<key>,<key>由工程根与一次规划的成员决定。该目录 是缓存,可以随时删除。已实现 - R2.2 命令不编译:标准库模块被描述而不被编译,也不生成只供链接使用的输入(GCC 的
mcpp-clean-link.specs)。工具链照常被查询(版本、目标三元组、sysroot 等),与mcpp build相同;对没有构建程序的工程,驱动只为这些查询运行。已实现 - R2.3
mcpp.lock从工程根读取,从不写回。规划得出的解析与工程中的锁不一致, 或工程中没有锁而规划会写出一份时,输出警告MCPP_LOCK_WOULD_CHANGE。已实现 - R2.4 根包
[build] generated_files中缺失或内容与声明不一致的文件不被写入, 每个输出一条警告MCPP_GENERATED_FILE_NOT_MATERIALIZED。已实现 - R2.5 构建程序照常运行,工作目录为包根,与
mcpp build相同;构建程序在MCPP_OUT_DIR之外写入的内容不在本保证之内。命令不构建依赖提供的宿主工具 (R2.2):全局工具库中已有的工具照常使用;库中没有的工具被推迟,请求它的构建 程序收到该工具将被发布到的路径,命令输出说明MCPP_BUILD_DATABASE_HOST_TOOL_DEFERRED, 消息点名工具与其所属包。被推迟的工具的包不被规划,它声明的动作不运行。构建程序 若在配置期执行该路径,遇到的情形与工具构建失败时相同(SPEC-007 R5.3)。mcpp build不受影响:它构建宿主工具,构建失败仍使目标失败。已实现 (mcpp >= 2026.9.27.1) - R2.6
mcpp --protocol-version为这条命令声明init-mcpp-home、read-project、network、write-global-cache与exec-build-script,不声明write-project。 已实现
mcpp 输出的 S1 文档满足 S1 等级 2,不输出 ide.options。等级 3 所需的结构化选项由
缺少 options 的一方按 S1 §9 规则 1 从 arguments 推导。
- R3.1
ide.toolchains的键为<family>-<version>-<triple>,其中family是 mcpp 的族名(gcc、llvm、msvc),triple是编译器自身的拼写。键对消费方不 透明,在一份文档内稳定。已实现 - R3.2
family为gcc、clang或msvc。driver为构建调用的驱动的绝对路径;target为编译器自身拼写的目标三元组;构建使用 sysroot 时给出sysroot;stdlib给出name(libstdc++、libc++、msvc-stl或other)与version,不给出module-metadata,标准库模块经 §3.4 的单元解析。已实现 - R3.2a
config-files列出驱动在命令行之外读取的配置文件,空数组表示没有:clang 驱动旁的<驱动名>.cfg,单元带--no-default-config时不列出;GCC 驱动库目录中lib/gcc/<targetTriple>/<版本>/specs,版本目录也可以只写主版本号。取值来自驱动 搜索的目录布局,命令不运行驱动。已实现
- R3.3 每个包一个集合,名为包的限定名(
<namespace>.<name>或<name>)。根包与 工作区被选成员的测试目标的源文件归入集合<包>:test,标准库模块的单元归入集合mcpp:std。工作区按配置规划,与mcpp build相同:每个配置一次规划,成员共用的包 在一个配置中只有一个集合。文档描述多个配置时,每个集合名带前缀<配置>/,<配置>为该配置构建目录的名字;只有一个配置时不带前缀。两次规划描述同一配置的同一集合时 (见 R5.2 的逐成员规划),该集合只出现一次。已实现 - R3.4
visible-sets列出同一次规划的其余所有集合。引擎在一次调用的一张模块图上 解析 import,更窄的闭包会描述一条构建并不执行的规则。已实现 - R3.5
family-name为包名,mcpp:std集合的为mcpp:std;ide.configuration为 profile 名;ide.kind在测试集合为test,在根包与工作区被选成员的集合按其 目标为library、executable或other,在依赖包集合与mcpp:std为library。 已实现 - R3.6 单元的
arguments依次是驱动、集合的baseline-arguments、单元的local-arguments,以及单元自己结尾的-c <source> -o <object>(若有;两个操作数 相对work-directory指向source与object)。baseline-arguments是集合中每个 单元去掉驱动与该结尾后的最长公共前缀。取前缀而不取公共子集,因为参数顺序决定 头文件搜索与宏定义。已实现
-
R3.7 除 NASM 单元与规则声明的设备源文件(
SourceKind::Device)外,构建计划中 的每个编译单元是一个翻译单元;两者都不在 S1 文档与compile_commands.json中 出现,但原因不同——NASM 单元是构建计划的编译单元,只是被逐出翻译单元的集合; 设备源文件从不是构建计划的编译单元(引擎对其扩展名没有编译规则,能编译它的只有 包自己的构建程序,通过一个动作),因而也从不进入这一集合。source、work-directory、arguments、object与compile_commands.json中对应条目的file、directory、arguments、output取自同一条记录,因而逐字相同。work-directory是编译器实际运行的目录——即输出目录target/<triple>/<fingerprint>——对每个工程单元与每种工具链皆然;标准库单元 保留它们本来所在的共享 std 缓存目录(§3.4)。arguments中的每一项是编译器收到 的一个参数,不带任何宿主的引号或转义,不经 shell 即可执行:单元自己的 flag 列表 按 SPEC-004 §8 读成的词列出,引擎为宿主渲染的文本按该宿主的读取规则(POSIXsh或 MSVCRT)还原。提供某个模块的单元,arguments在-c <source>之前带有该 单元的模块接口语言标记(GCC、Clang 方言);MSVC 方言在 Windows 上量出 clang-cl 模式的 clangd 是否接受/interface之前留空。已实现 -
R3.8 工程单元的
provides把单元提供的模块名映射到空字符串,因为这条命令 不执行构建(S1-8-6);requires为单元导入的模块名,分区写全名M:P。private为false,理由同 R3.4。标准库单元的provides例外:把std、std.compat映射到构建会写出的 BMI 在共享 std 缓存中的路径——这条路径由缓存键决定,不需要 真的编译就能得到(§3.4)。ide.toolchains.<id>.build-id给出编译器的构建标识, 取自 mcpp 已经算出的驱动身份(工具链指纹的同一个字段),同一工具链的两次运行 之间保持稳定。已实现 -
R3.8a 一个模块名在一个程序之内标识一个模块,而一个配置可以包含多个程序。同一 配置中两个包提供同一个模块名时(两者不在同一个包的闭包中,#732),两个单元的
provides都列出这个名字;可能导入它的每个单元,其arguments带有构建所用的 绑定:GCC 为-fmodule-mapper=<映射文件>(相对work-directory),Clang 为-fmodule-file=<名字>=<路径>,MSVC 为/reference <名字>=<路径>。只按名字在文档中 查找提供方的消费方,因此可能取到另一个程序的模块;构建本身不受影响。每个名字只有 一个提供方时,文档与此前逐字相同。已实现 -
R3.9
ide.role取自扫描器读到的模块声明形式:声明 ide.role无模块声明 non-moduleexport module M;module-interfaceexport module M:P;module-partition-interfacemodule M:P;module-partition-implementationmodule M;module-implementationscan_overrides声明的单元;P1689 扫描中无法区分实现单元与导入者的单元unknown已实现
-
R3.9a 没有被
sourcesglob 匹配的目标入口源文件(发现的测试、glob 之外的main)与包源文件由同一扫描器读取,注释与原始字符串中的import不是导入。扫描器 拒绝的入口文件(#if块中的import、头文件单元)从未在这条路径上被拒绝,现在也 不被拒绝:requires为其代码中行首的import,ide.role为unknown。入口声明 自身提供模块时,ide.role为unknown,因为构建不为该单元产出 BMI。已实现
- R3.10 构建导入
std时,mcpp:std集合包含std的单元;工具链有std.compat的构建命令时,还包含std.compat的单元。provides分别为std与std.compat,std.compat的requires为std,角色均为module-interface。该规则对工具链 自带的标准库(GCC 的bits/std.cc、libc++ 的std.cppm、MSVC STL 的std.ixx)与 依赖包提供的std.cppm相同。已实现 - R3.11 这些单元的
arguments与work-directory来自 mcpp 构建该模块时运行的 命令:mcpp 为宿主 shell 渲染的命令去掉cd、环境变量赋值与重定向,再撤销引号。 命令中找不到该源文件时,不列出该单元,并输出警告MCPP_BUILD_DATABASE_STD_UNIT_UNDESCRIBED。已实现
- R3.12 一个集合的
ide.generated(S1 0.3.0 §7.2)列出该集合所属包的构建程序以role = "source"的 action 生成的每一个输出,以及该集合的单元以-I命名、位于规划 目录的target/.build-mcpp之下的每一个目录;包的测试集合与其普通集合一样列出这些输出, 因为不经预处理无法知道哪些单元包含一个头文件。每一项给出path(本文档中的路径)、build-path(同一组选择器下mcpp build写入的路径:把规划目录换成工程根,文件存在 与否都给出)与kind。一个输出同时是该集合某个单元的source时kind为source, 否则为header;目录为directory。文件一项另有generator:action 的id、inputs、 作为arguments的命令,以及work-directory(action 声明的cwd,未声明时为构建 目录)。没有这样的输出与目录的集合不带该字段。该字段不进入--spec compile-commands的文档,因为 JSON Compilation Database 的读者拒绝未知的键。命令不运行任何 action (R2.5);由消费方决定是否在其用户同意时运行generator。已实现 (mcpp >= 2026.9.28.1,mcpp#724)
- R4.1 文档为
mcpp build --configure-only在同一组选择器下写入compile_commands.json的条目,差别只在输出路径位于 §2 的工作目录之下;同一文件与 同一输出只有一条条目,文档描述多个配置时,一个文件在每个编译它的配置中各有一条。标准库 模块的单元也在其中,遵循 S1-12-1 的导出规则:S1 文档里mcpp:std集合的每个 单元同样导出为一条compile_commands.json条目。已实现
- R5.1
kind为mcpp.build-database,kindVersion为 1。data含spec({"name": "s1", "version": "0.3.0"}或{"name": "compile-commands"})、database、watch与inputs-fingerprint。已实现 - R5.2 一个成员规划失败只影响它自己,不影响其余成员的集合(#699 第 1 项)。命令
按配置规划被选中的成员;一个配置的规划失败时,该配置的成员逐个规划,因此失败仍归于
各自的成员。规划失败的成员不贡献任何集合,只贡献一条
error诊断,path为该成员的mcpp.toml,相对工作区根目录;诊断码为:不在工程中时MCPP_BUILD_DATABASE_NO_PROJECT;该成员的规划因离线而需要下载时MCPP_OFFLINE_DOWNLOAD_REQUIRED,消息指出需要下载的第一项;其他规划失败为MCPP_BUILD_DATABASE_PLAN_FAILED。data在至少一个被选中的成员规划成功时出现, 并描述每一个规划成功的成员;被选中的成员全部规划失败时,信封不含data。规划成功 的成员中,构建程序失败的包被描述为不含该程序产生的指令(清单自身的配置、工具链、 模块图与标准库单元仍照常描述),diagnostics另有一条error,MCPP_BUILD_DATABASE_PROGRAM_FAILED,path为该包的build.mcpp;后续失败若是 由缺失的指令引起,则按前一条规则使整个成员失败。一项检查若以构建程序的指令为 前提(例如"每个设备源文件都被某个动作消费"),对本轮构建程序失败的包不运行: 该包已经带着这一条PROGRAM_FAILED诊断被描述,不应因指令缺失这一后果本身被 判成第二个失败,把真正的诊断挤出信封。只要diagnostics中有一条error,退出码就是 1,无论data是否出现。已实现(离线诊断码: mcpp >= 2026.9.16.1;成员独立规划、path与构建程序失败的描述:mcpp >= 2026.9.26.2) - R5.3 信封的
effects为read-project与write-global-cache,运行了构建程序时 另有exec-build-script,本次运行启动过网络子进程(索引刷新、安装、git 远程操作, 失败或超时的也算)时另有network。已实现(network:mcpp >= 2026.9.16.1) - R5.4 规划期间启动的子进程不继承调用方读取标准输出的描述符;xlings 子进程有期限, 并随 mcpp 一起结束。已实现(mcpp >= 2026.9.16.1)
- R6.1
watch列出:工作区根与每个源码包的mcpp.toml;mcpp.lock;存在时的build.mcpp;每个源码包的源文件 glob;测试发现的 glob([test] discover,默认tests/**/*.cpp);构建程序声明的输入文件与 glob;$MCPP_HOME/config.toml。位于 工作区根之下的条目写成相对工作区根、以/分隔的路径或 glob;之外的条目写成绝对 路径,其 glob 展开为运行时匹配到的文件。已实现 - R6.2 不监视:环境变量,包括
MCPP_TOOLCHAIN、MCPP_HOME与构建程序声明的 环境变量;存储中的包与 git 依赖的检出,它们的版本或提交写在已监视的清单与锁中。 已实现 - R6.3
inputs-fingerprint形如fnv1a:<16 位十六进制>,是 mcpp 版本、选择器与watch在运行时匹配到的每个文件的路径与内容的摘要;这些输入不变时它不变。它与 构建指纹无关。已实现 - R6.4 命令不写入
watch列出的任何文件,这由 §2 保证。已实现
| 版本 | 日期 | 变更 |
|---|---|---|
| 1.0 | 2026-09-14 | 首版(#636)。 |
| 1.1 | 2026-09-16 | R5.2 增加离线诊断码 MCPP_OFFLINE_DOWNLOAD_REQUIRED;R5.3 的 network 按观测列出;新增 R5.4(子进程不继承调用方描述符,xlings 子进程有期限并随 mcpp 结束)(#648)。 |
| 1.2 | 2026-09-17 | R3.7 陈述 arguments 的每一项是编译器收到的参数,单元 flag 按 SPEC-004 §8 的词列出(#655)。 |
| 1.3 | 2026-09-26 | R2.5:emit 下构建失败的宿主工具是警告。R3.7:work-directory 是输出目录,模块接口单元的 arguments 带语言 flag。R3.8:标准库单元的 provides 指向 std 缓存中的 BMI,工具链带 build-id。R4.1:compile-commands 文档包含标准库单元(S1-12-1)。R5.2:成员各自规划,构建程序失败的包不带其指令地被描述(#699,#702)。 |
| 1.4 | 2026-09-26 | R2.5:命令不构建宿主工具;工具库中没有的工具被推迟,输出说明 MCPP_BUILD_DATABASE_HOST_TOOL_DEFERRED,取代 1.3 的警告 MCPP_BUILD_DATABASE_HOST_TOOL_UNBUILT(#707)。 |
| 1.5 | 2026-09-28 | R3.7:规则声明的设备源不是编译单元,不进入 S1 与 compile_commands.json(#724)。新增 R3.12:集合的 ide.generated 列出规则生成的文件与目录,给出构建写入的路径与生成它的步骤,S1 0.3.0(#724,Sunrisepeak/mcpp-language-server#28)。R5.1:S1 版本为 0.3.0。R5.2:以构建程序的指令为前提的检查不对其构建程序已失败的包运行,失败路径保留已记录的说明(#724)。 |
| 1.6 | 2026-09-29 | 工作区按配置规划,与 mcpp build 相同(R2.1、R3.3、R3.4、R5.2):成员共用的包在一个配置中只描述一次;集合名的前缀由 <成员>/ 改为只在文档描述多个配置时出现的 <配置>/;一个配置的规划失败时逐成员规划。R3.5:被选成员的集合按其目标给出 ide.kind。R4.1:同一文件与输出一条条目。 |
| 1.7 | 2026-09-30 | 新增 R3.8a:同一配置中两个包提供同一个模块名时,两个单元都列出它,可能导入它的单元的 arguments 带有构建所用的绑定(#732)。 |