| 项 | 值 |
|---|---|
| 规范编号 | SPEC-009 |
| 标题 | 工具链的支持与维护:版本线、默认值、来源、移动与退役 |
| 状态 | 草案 v0.1 |
| 最后修改 | 2026-10-02 |
| 对应实现 | 逐条标注;本版只有规范,多数条款未实现 |
| 相关设计文档 | .agents/docs/2026-10-02-pr-ci-acceleration-and-the-toolchain-specification-design.md(第 IV 部分) |
| 相关 issue | mcpp#669(macOS 27 的链接)、mcpp#685、mcpp#687、mcpp#755 |
| 使用文档 | docs/20 - 工具链 |
本规范定义 mcpp 支持的工具链集合如何随时间变化:哪些版本被支持、默认值由哪一张表给出、载荷从哪里来、 一个默认值如何移动,以及已经记录了默认值的机器如何得知新的默认值。 SPEC-006 定义工具链是什么、如何命名与选择、载荷必须包含什么,本规范不重复这些内容。
本规范不指定任何一个具体版本为默认值。具体版本由一张线表给出(§3);本版发布时线表尚不存在, 默认值仍由两张表分别给出(§3.1)。各节编号与设计过程中的条款编号 TS-n 一一对应。
用语按 RFC 2119:必须 / 禁止(强制)、应当(强烈建议)、可以(可选)。实现状态标记见 规范索引。
族与载荷沿用 SPEC-006 §1 与 §2.1 的定义。本节增加以下术语。
| 术语 | 含义 |
|---|---|
| 族(family) | gcc、llvm、msvc、emsdk、android-ndk |
| 线(line) | 一个族加一个主版本:GCC 16、LLVM 23 |
| 发布(release) | 一条线上的一个上游版本:16.2.0、23.1.3 |
| 载荷(payload) | 一个发布在某个宿主与变体上的可安装目录树:glibc 的 gcc、原生 musl-gcc、交叉的 <arch>-linux-musl-gcc、mingw-gcc、mingw-cross-gcc、llvm |
| 行(row) | 目标矩阵(kKnownTargets)的一行,或一个宿主默认值 |
| 默认(default) | 一行在没有任何声明时解析到的发布 |
| 线表(line table) | 引擎中唯一一张给出每一行默认的表(§3) |
| 自举工具链(self-host toolchain) | mcpp 自己的 mcpp.toml 所声明的工具链 |
发布按下列三个等级分级。
| 等级 | 范围 | 义务 |
|---|---|---|
| Default | 线表为一行所指名的发布 | 该行 CI 宿主上的完整 e2e 套件;该宿主构建 mcpp 时,mcpp 自身的构建;发布产物以它构建 |
| Supported | 每一行上一个 Default,以及线表明确列出的发布 | 线表或其载荷变化时,CI 运行验收程序(SPEC-006 §6.2);回归是缺陷 |
| Available | 索引中的其余所有发布 | 无 CI,接受报告。已知缺陷必须以载荷的能力(.mcpp-toolchain.json)陈述,禁止写成引擎里的版本分支 |
当前:引擎不按发布分级。kKnownTargets 的 tier(verified、preview、planned)是目标行的验证级别,与本节的发布分级是两个概念。
每个族的下限,即 mcpp 接受其 import std 的最老的线,必须写在线表中。
当前:下限是隐含的,没有一处陈述。
每一个默认与每一个行的钉住版本,必须读自引擎中的同一张表。表为每一行写明族、发布、变体与等级; 行的发布不同于其族的 Default 时,必须另写明理由,以及差异在何种条件下终止。
当前:默认值由两张表分别给出,均在 modules/toolchain-model/src/triple.cppm:表 A 是 pins::kFirstRun*(附带重复的 kSuggest* 字面量),
表 B 是 kKnownTargets[].pin。两张表之间只有一处耦合被检查:kFirstRunWinGnu 与 x86_64-windows-gnu 行的一致
(tests/unit/test_windows_defaults.cpp)。
帮助文本、安装建议、错误信息与 mcpp self env --format json 必须由线表格式化。引擎内出现第二处默认值字面量是缺陷。
当前:mcpp self env --format json 的 data.defaultToolchain 取自 pins::host_default_toolchain(表 A)。安装建议使用 kSuggest* 字面量;
两张表之外,另有 21 处默认值字面量分布在九个源文件中。
引擎之外凡写出默认值者(文档、工作流、测试、示例与 mcpp 自己的清单),必须读自 mcpp self env --format json,
或者由一项 CI 检查与它比对。既不读取也不比对的字面量是缺陷。
当前:只有宿主默认值在文档中的四条陈述被比对:.github/tools/check_default_toolchain_docs.py 在每个 CI 宿主上检查该宿主的行,
在 docs/01、docs/20 及其 docs/zh/ 副本中各一条。其余读者既不读取也不比对:mcpp.toml、tests/matrix/expected.tsv 的 175 行、
七个工作流、一个 action、六个 CI 工具、至少八个 e2e 脚本、示例,以及 33 个文档文件中的其余陈述。
索引的 latest 禁止作为默认值。mcpp 钉住精确的发布;移动 latest 不改变 mcpp 构建的任何结果。
在一台宿主上,同一个族的各行应当解析到同一个发布。一行可以落后,但必须在线表中写明理由, 且理由必须在下一次移动时重新评估。
当前:不成立。macOS 与带 MSVC 的 Windows 默认 llvm@20.1.7,而 17 个目标行钉住 llvm@22.1.8。Linux 在 x86_64 之外的架构默认
gcc@15.1.0-musl,其余 gcc 行为 16.1.0。这些落后都没有记录理由。
一个发布进入某一行的 Default,必须以该行所需的每一个载荷在该发布上都存在为前提,且两个镜像上都有(§9)。 同一条 GCC 线的载荷(glibc、原生 musl、交叉 musl、mingw)在此处视为同一条线。
当前:没有程序检查此条件。
载荷必须出自一个上游发布:官方标签,及其官方的源码或二进制归档;载荷的描述必须记录该归档的 sha256(SPEC-006 §4.5)。
当前:索引以 sha256 固定资产(SPEC-006 §4.6),但描述文件尚不记录上游源码的 sha256(SPEC-006 §4.5 未实现)。
载荷必须由检入仓库的配方产出,由一个 CI 作业运行,或由 CI 作业可以运行的脚本运行。人工步骤不属于配方(SPEC-006 §5.1)。
当前:glibc 的 gcc 载荷由人工按 fromsource 配方构建,随后的 strip 与 specs 改写没有任何仓库中的脚本(16.1.0 如此)。
musl-gcc 与 aarch64-linux-musl-gcc 在 xlings-res 中有可派发的构建工作流;mingw-cross-gcc 与 Windows 宿主上的 Canadian cross 是手工的;
mingw-gcc 转存 winlibs 的发布。
打补丁的载荷,即一个上游发布加上尚无发布携带的上游提交,可以发布,当且仅当以下条件全部成立:
- 每一个补丁都是上游发布分支上的一个提交,或是主分支上带有未关闭的回移(backport)的提交,并以哈希引用;
- 它作为该发布的修订发布(
revision = N,资产名为<发布>-r<N>),禁止使用官方资产名,其描述列出全部补丁; - 它只放在需要它的行上,不放在其他行上;
- 它有退出条件:当某个上游发布携带了这些补丁,Default 移到该发布,修订只为已钉住它的使用者留在索引中。
当前:索引已有修订的先例(xim-pkgindex 的 glibc 2.44.3-r1,revision 字段与 -rN 资产名),机制存在;上述四项条件没有程序检查。
引擎禁止以改变平台输入的方式绕开工具链缺陷,例如选择更旧的 SDK、改写 SDK 的文件、替换另一个链接器,除非那就是该行声明的设计。 工具链缺陷由工具链的发布修复,或由 §5.3 的打补丁载荷修复。
当前:mcpp#669 没有以改动平台输入的方式处理:引擎没有为它选择更旧的 SDK、改写 SDK 的文件或替换链接器。全引擎范围的符合性没有核对,也没有检查。
引擎必须从载荷推导工具链能做什么,而不是从版本:标准库模块取自库的清单或布局,扫描器取自驱动,flag 取自在该行上测得的探针。 版本号的比较只在两种情形被允许:一项登记于 §7 的缺陷,或一张语言特性表,其每一行引用发布说明。
当前:标准库模块、扫描器与描述文件的字段按载荷取得(SPEC-006 §4.4、§4.5)。引擎中是否还有按版本号的分支,没有逐处核对;已知的一处是按 cl.exe 横幅的版本选择 std 模块的语言级别(src/toolchain/msvc.cppm)。
在一个线是 Default 的每一行上,引擎必须通过验收程序(一个使用 <memory>、<mutex> 与 <thread> 的程序;import std;import std.compat)与 e2e 套件;
该宿主构建 mcpp 时,还必须通过 mcpp 自身的构建。
当前:e2e 套件在各 CI 宿主上运行;验收程序的矩阵部分实现(SPEC-006 §6.2)。macOS 的 Default llvm@20.1.7 无法构建 mcpp 自身
(其 libc++ 的 std 模块不暴露 directory_iterator 的比较),mcpp 的清单因此以 22.1.8 构建;该行的第三项验收不成立。
每一个编码了发布的输出,必须在移动时被评审:包的 ABI 标签(编译器主版本)、BMI 与缓存的身份(版本进入指纹与每一个缓存键,因此移动不需要更换纪元),以及引用版本的诊断。
当前:ABI 标签带编译器主版本(src/pack/abi_tag.cppm):GCC 16.1 到 16.2 保持 gcc16-libstdcxx16;LLVM 22 到 23 由 clang22-libcxx22
变为 clang23-libcxx23,带旧标签的预制产物被拒绝(src/pack/prebuilt.cppm)。版本已进入指纹(modules/toolchain-model/src/fingerprint.cppm)。评审本身没有清单,也没有检查。
凡因一个编译器缺陷而存在的引擎行为,以及凡因此而采取的 mcpp 自身源码的形状,必须有一个登记项,写明:族、首次观察到的发布、最后验证过的发布、上游报告、
tests/ 下一个在缺陷存在时失败的最小复现,以及依赖它的位置。
当前:没有登记表。这些行为的原因散见于源码注释与早先的记录,不保证每一项都有最小复现。
每一次移动一条线时,必须对新发布运行每一个复现,并在登记项中记录结果。
当前:没有登记表,也就没有可运行的集合。
一个绕开缺陷的做法,必须在每一个 Supported 发布都越过修复之后才移除。
当前:没有此规则;各处绕行的移除时机由修改者判断。
以下条目取自源码注释与早先的记录,待 §7.1 落地后写成正式的登记项。
| 缺陷 | 族与发布 | 位置 |
|---|---|---|
| 一个实例化沿模块导入链被丢弃 | GCC 16.1 | — |
模块接口中的 FILE 类型实体使其后的 #include <cstdio> 失败 |
GCC PR 99000(未关闭) | — |
嵌套的 std::map 成员使 BMI 被截断 |
GCC 16.1 | — |
| 一个新的、被广泛导入且接口含标准类型的模块毒化下游的 BMI | GCC 16.1 | — |
| 新接口单元上的段错误 | GCC 16.1 | src/build/prepare.cppm |
| 完整 BMI 下的误编译,因此 clang 使用两阶段的精简接口 | clang 22.1.8 | src/build/ninja_backend.cppm |
| 模块 purview 中的内联辅助函数导致崩溃 | clang 20.1.7(Windows) | — |
27.0 SDK 在模块下把 INFINITY 与 NAN 留给 <float.h> |
clang 22 与 macOS 27.0 SDK | src/toolchain/hostflags.cppm |
libc++ 20 的 std 缺少 directory_iterator 的比较 |
libc++ 20 | mcpp.toml 的 [toolchain] |
AMDGPUAsmParser.cpp 的内部编译器错误,因此 llvm-dev 以 GCC 15.1.0 构建 |
GCC 16.1 | — |
一个行的使用者将会遇到的新操作系统或新 SDK 发布,必须在存在托管的 runner 镜像时尽早得到一条 CI 腿,早于该镜像成为 runner 的默认。
当前:macOS 27 在 xcode-27 镜像上已有 CI 腿,因 mcpp#669 为已知红色。没有一般性的清单规定哪些宿主版本须有腿。
因外部原因而红色的腿必须带 known_red: '#<issue>',工作流断言(.github/tools/check_workflow_assertions.py)必须保证该 issue 未关闭,
issue 关闭时该腿必须离开已知红色的列表。
宿主平台新版本引出的工具链缺陷,其修复遵循 §5.3 与 §5.4。
当前:状态同 §5.3 与 §5.4。mcpp#669 尚未修复,两条 xcode-27 腿保持已知红色。
一个版本行禁止在它所指名的每一个资产都出现在 GLOBAL 与 CN 两个镜像上、并在两者上经 GET 验证(状态码 200、字节数与 sha256)之前进入索引。
当前:没有程序检查镜像的存在与哈希,顺序由发布者保证。
向 CN 镜像上传大于约 8 MiB 的文件,必须从 CN 网络内的主机进行。
当前:没有检查;该条是一项操作约束。
移动一行的 Default 必须按以下顺序进行。当前:没有程序串联这些步骤;没有线表,步骤 10.6 须同时修改两张表及其全部副本(§3);步骤 10.5 见该节。
该上游发布已经存在。
其载荷由各自的配方构建(§5),通过 SPEC-006 §6 的准入,并各自记录其输入。
资产已在两个镜像上并通过验证(§9)。
索引增加该版本的行;latest 不变。
引擎在每一个将以该发布为 Default 的行上得到验证,且该发布通过下列的门。载荷可以按路径命名(SPEC-006 §2.2.1),因此本步不必等待 10.4。
候选发布 R 与该行当前的 Default D 在同一个 mcpp 提交、同一个 runner 镜像、同一个作业中比较,使比较只跨越发布的变化。 R 通过,当且仅当下列六项全部成立。
| 门 | 判据 |
|---|---|
| G1 | 凡以 D 通过的 e2e 测试,以 R 也通过;凡以 D 运行的测试,以 R 不被跳过 |
| G2 | 该行构建 mcpp 时,mcpp 以 R 构建自身,且该二进制通过套件 |
| G3 | §6.2 的验收程序能够构建并运行 |
| G4 | 运行 §7 的每一个复现,且 G1 至 G3 不需要新的绕行或重塑的源码;需要者使门失败,除非评审接受并附登记项 |
| G5 | mcpp 自身源码与 e2e 模块夹具所扫描出的模块图(每个单元提供与需要的模块),在 R 与 D 下相同 |
| G6 | mcpp 与 bench/ 工程的冷构建和暖构建,以 R 所用时间不超过 D 的 110%(三次运行的中位数),BMI 的体积不超过 110% |
未通过门的发布留在 Available。同一条线的下一个发布成为候选。
当前:没有门的工作流,G1 至 G6 均未实现;CI 不在同一作业中比较两个发布。
一个 mcpp 拉取请求移动线表,以及线表的每一个读者与每一个被检查的副本;随后发布 mcpp。
在该 mcpp 发布之后,索引的 latest 移动。
此前的 Default 成为 Supported,再之前的线成为 Available。
移动通过回退 10.6 撤销。载荷与索引的行保留。
本条承接 SPEC-006 §7 原有的规则:移动 C 库绑定,或某个载荷的 latest,之前,SPEC-006 §6.2 的矩阵必须在新版本上全部通过。
状态:未实现(§6.2 的矩阵部分实现,此顺序没有检查)。
使用者或项目所声明的发布([toolchain]、--toolchain、MCPP_TOOLCHAIN、mcpp toolchain default)禁止移动。
当前:已记录的默认值只由首次运行与 mcpp toolchain default 写入,没有路径因线表的变化而改写它。
mcpp 在首次运行时写下的默认值不是使用者的声明,其记录必须如实表明这一点。
- 在没有已记录默认值的主目录上,首次运行安装并记录线表的答案,一如今日,此外不说任何话。
- 通知只为一种情形存在:主目录里是由更早的 mcpp 的首次运行记录的默认值,而正在运行的 mcpp 的线表为该行指名了更新的发布。
- 此时 mcpp 必须陈述一次:已记录的发布、更新的发布,以及解决这个问题的两条命令。
mcpp toolchain default <更新的发布>移动记录;mcpp toolchain default --keep保留记录。任何一条命令都使该记录成为使用者的声明。 - 通知不阻塞,不提示输入,不重复。使用者作答之前,已记录的发布继续使用。
当前:src/build/prepare/toolchain.cpp:1607 在首次运行时把默认值写入 config.toml 的 [toolchain] default(Windows 的 MinGW 回退另有两处写入),
此后没有任何路径按线表重新评估它,因此移动后的默认值只到达全新的主目录。该键与 mcpp toolchain default <spec> 所写的是同一个键,记录无法区分首次运行与使用者的声明;
mcpp toolchain default --keep 不存在。
已安装的载荷保留;移除它们是使用者的决定。
当前:移除载荷的入口只有 mcpp toolchain remove。
mcpp 自己的清单必须使用其构建所在的每一行的 Default 发布。偏离是线表中的一项,带理由与退出条件。
当前:mcpp.toml 的 [toolchain] 为 default = "gcc@16.1.0"、macos = "llvm@22.1.8"、windows = "llvm@20.1.7"。default 等于 Linux x86_64 的默认,
windows 等于带 MSVC 的 Windows 的默认;macos 偏离该行的默认 llvm@20.1.7,原因见 §6.2,但没有线表项记录理由与退出条件。
程序能够检查的规则必须在 CI 中被检查。
| 检查 | 内容 | 条款 | 状态 |
|---|---|---|---|
| C1 | src/ 与 modules/ 中,线表之外没有默认值字面量 |
§3.2 | 未实现 |
| C2 | 文档中每一处对默认值的陈述,包括 docs/21 的目标行表与 README,与 mcpp self env --format json 一致 |
§3.3 | 部分实现 |
| C3 | 工作流、action 与测试的版本取自线表;能力探针询问族,不询问版本 | §3.3 | 未实现 |
| C4 | mcpp.toml 的 [toolchain] 等于线表或一项已记录的偏离 |
§12 | 未实现 |
| C5 | 一条 known_red 的腿指名一个未关闭的 issue |
§8.2 | 已实现 |
| C6 | §7 的复现在每一个 Default 行上运行 | §7 | 未实现 |
| C7 | §10.5 的门是一个以行与候选载荷为输入的工作流,§10.6 的拉取请求引用它的运行 | §10 | 未实现 |
当前:
- C2:
check_default_toolchain_docs.py只比对宿主默认值的四条陈述(§3.3);docs/21的目标行表与 README 没有覆盖。 - C3:
tests/e2e/run_all.sh仅当musl-gcc/15.1.0已安装时授予musl,仅当mingw-cross-gcc/16.1.0已安装时授予mingw-cross。 默认值移动后,十六个测试被跳过且不报告。 - C5:
check_workflow_assertions.py要求允许失败的作业在名字中指名一个 issue,并在检查 issue 状态时报告已不再打开的 issue。
| 版本 | 日期 | 变更 |
|---|---|---|
| v0.1 | 2026-10-02 | 初版 |