Skip to content

Latest commit

 

History

History
386 lines (237 loc) · 19.6 KB

File metadata and controls

386 lines (237 loc) · 19.6 KB

SPEC-009:工具链的支持与维护

项 值
规范编号 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:必须 / 禁止(强制)、应当(强烈建议)、可以(可选)。实现状态标记见 规范索引。


1. 术语

族与载荷沿用 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 所声明的工具链

2. 支持分级

2.1 三个等级 未实现

发布按下列三个等级分级。

等级 范围 义务
Default 线表为一行所指名的发布 该行 CI 宿主上的完整 e2e 套件;该宿主构建 mcpp 时,mcpp 自身的构建;发布产物以它构建
Supported 每一行上一个 Default,以及线表明确列出的发布 线表或其载荷变化时,CI 运行验收程序(SPEC-006 §6.2);回归是缺陷
Available 索引中的其余所有发布 无 CI,接受报告。已知缺陷必须以载荷的能力(.mcpp-toolchain.json)陈述,禁止写成引擎里的版本分支

当前:引擎不按发布分级。kKnownTargets 的 tier(verified、preview、planned)是目标行的验证级别,与本节的发布分级是两个概念。

2.2 下限 未实现

每个族的下限,即 mcpp 接受其 import std 的最老的线,必须写在线表中。

当前:下限是隐含的,没有一处陈述。


3. 一张线表

3.1 唯一的来源 未实现

每一个默认与每一个行的钉住版本,必须读自引擎中的同一张表。表为每一行写明族、发布、变体与等级; 行的发布不同于其族的 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)。

3.2 引擎内的读者 未实现

帮助文本、安装建议、错误信息与 mcpp self env --format json 必须由线表格式化。引擎内出现第二处默认值字面量是缺陷。

当前:mcpp self env --format json 的 data.defaultToolchain 取自 pins::host_default_toolchain(表 A)。安装建议使用 kSuggest* 字面量; 两张表之外,另有 21 处默认值字面量分布在九个源文件中。

3.3 引擎外的读者 部分实现

引擎之外凡写出默认值者(文档、工作流、测试、示例与 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 个文档文件中的其余陈述。

3.4 索引的 latest 不是默认值 已实现

索引的 latest 禁止作为默认值。mcpp 钉住精确的发布;移动 latest 不改变 mcpp 构建的任何结果。


4. 版本线整体移动

4.1 一个族在一台宿主上解析到一个发布 未实现

在一台宿主上,同一个族的各行应当解析到同一个发布。一行可以落后,但必须在线表中写明理由, 且理由必须在下一次移动时重新评估。

当前:不成立。macOS 与带 MSVC 的 Windows 默认 llvm@20.1.7,而 17 个目标行钉住 llvm@22.1.8。Linux 在 x86_64 之外的架构默认 gcc@15.1.0-musl,其余 gcc 行为 16.1.0。这些落后都没有记录理由。

4.2 载荷齐备 未实现

一个发布进入某一行的 Default,必须以该行所需的每一个载荷在该发布上都存在为前提,且两个镜像上都有(§9)。 同一条 GCC 线的载荷(glibc、原生 musl、交叉 musl、mingw)在此处视为同一条线。

当前:没有程序检查此条件。


5. 来源

5.1 上游发布 部分实现

载荷必须出自一个上游发布:官方标签,及其官方的源码或二进制归档;载荷的描述必须记录该归档的 sha256(SPEC-006 §4.5)。

当前:索引以 sha256 固定资产(SPEC-006 §4.6),但描述文件尚不记录上游源码的 sha256(SPEC-006 §4.5 未实现)。

5.2 配方 未实现

载荷必须由检入仓库的配方产出,由一个 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 的发布。

5.3 打补丁的载荷 部分实现

打补丁的载荷,即一个上游发布加上尚无发布携带的上游提交,可以发布,当且仅当以下条件全部成立:

  • 每一个补丁都是上游发布分支上的一个提交,或是主分支上带有未关闭的回移(backport)的提交,并以哈希引用;
  • 它作为该发布的修订发布(revision = N,资产名为 <发布>-r<N>),禁止使用官方资产名,其描述列出全部补丁;
  • 它只放在需要它的行上,不放在其他行上;
  • 它有退出条件:当某个上游发布携带了这些补丁,Default 移到该发布,修订只为已钉住它的使用者留在索引中。

当前:索引已有修订的先例(xim-pkgindex 的 glibc 2.44.3-r1,revision 字段与 -rN 资产名),机制存在;上述四项条件没有程序检查。

5.4 引擎不改动平台的输入 部分实现

引擎禁止以改变平台输入的方式绕开工具链缺陷,例如选择更旧的 SDK、改写 SDK 的文件、替换另一个链接器,除非那就是该行声明的设计。 工具链缺陷由工具链的发布修复,或由 §5.3 的打补丁载荷修复。

当前:mcpp#669 没有以改动平台输入的方式处理:引擎没有为它选择更旧的 SDK、改写 SDK 的文件或替换链接器。全引擎范围的符合性没有核对,也没有检查。


6. 支持一条版本线的含义

6.1 能力取自载荷 部分实现

引擎必须从载荷推导工具链能做什么,而不是从版本:标准库模块取自库的清单或布局,扫描器取自驱动,flag 取自在该行上测得的探针。 版本号的比较只在两种情形被允许:一项登记于 §7 的缺陷,或一张语言特性表,其每一行引用发布说明。

当前:标准库模块、扫描器与描述文件的字段按载荷取得(SPEC-006 §4.4、§4.5)。引擎中是否还有按版本号的分支,没有逐处核对;已知的一处是按 cl.exe 横幅的版本选择 std 模块的语言级别(src/toolchain/msvc.cppm)。

6.2 Default 行的验收 部分实现

在一个线是 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 构建;该行的第三项验收不成立。

6.3 随发布编码的输出 部分实现

每一个编码了发布的输出,必须在移动时被评审:包的 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)。评审本身没有清单,也没有检查。


7. 编译器缺陷登记

7.1 登记项 未实现

凡因一个编译器缺陷而存在的引擎行为,以及凡因此而采取的 mcpp 自身源码的形状,必须有一个登记项,写明:族、首次观察到的发布、最后验证过的发布、上游报告、 tests/ 下一个在缺陷存在时失败的最小复现,以及依赖它的位置。

当前:没有登记表。这些行为的原因散见于源码注释与早先的记录,不保证每一项都有最小复现。

7.2 移动时重跑 未实现

每一次移动一条线时,必须对新发布运行每一个复现,并在登记项中记录结果。

当前:没有登记表,也就没有可运行的集合。

7.3 移除条件 未实现

一个绕开缺陷的做法,必须在每一个 Supported 发布都越过修复之后才移除。

当前:没有此规则;各处绕行的移除时机由修改者判断。

7.4 已知条目

以下条目取自源码注释与早先的记录,待 §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 —

8. 宿主平台的新版本

8.1 新版本的 CI 腿 部分实现

一个行的使用者将会遇到的新操作系统或新 SDK 发布,必须在存在托管的 runner 镜像时尽早得到一条 CI 腿,早于该镜像成为 runner 的默认。

当前:macOS 27 在 xcode-27 镜像上已有 CI 腿,因 mcpp#669 为已知红色。没有一般性的清单规定哪些宿主版本须有腿。

8.2 已知红色的腿 已实现

因外部原因而红色的腿必须带 known_red: '#<issue>',工作流断言(.github/tools/check_workflow_assertions.py)必须保证该 issue 未关闭, issue 关闭时该腿必须离开已知红色的列表。

8.3 修复 部分实现

宿主平台新版本引出的工具链缺陷,其修复遵循 §5.3 与 §5.4。

当前:状态同 §5.3 与 §5.4。mcpp#669 尚未修复,两条 xcode-27 腿保持已知红色。


9. 镜像

9.1 先镜像,后入索引 未实现

一个版本行禁止在它所指名的每一个资产都出现在 GLOBAL 与 CN 两个镜像上、并在两者上经 GET 验证(状态码 200、字节数与 sha256)之前进入索引。

当前:没有程序检查镜像的存在与哈希,顺序由发布者保证。

9.2 CN 镜像的上传 未实现

向 CN 镜像上传大于约 8 MiB 的文件,必须从 CN 网络内的主机进行。

当前:没有检查;该条是一项操作约束。


10. 移动默认版本 未实现

移动一行的 Default 必须按以下顺序进行。当前:没有程序串联这些步骤;没有线表,步骤 10.6 须同时修改两张表及其全部副本(§3);步骤 10.5 见该节。

10.1 上游发布

该上游发布已经存在。

10.2 载荷

其载荷由各自的配方构建(§5),通过 SPEC-006 §6 的准入,并各自记录其输入。

10.3 镜像

资产已在两个镜像上并通过验证(§9)。

10.4 索引

索引增加该版本的行;latest 不变。

10.5 门:新发布不使模块体验变差

引擎在每一个将以该发布为 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 不在同一作业中比较两个发布。

10.6 引擎

一个 mcpp 拉取请求移动线表,以及线表的每一个读者与每一个被检查的副本;随后发布 mcpp。

10.7 latest

在该 mcpp 发布之后,索引的 latest 移动。

10.8 等级移位

此前的 Default 成为 Supported,再之前的线成为 Available。

10.9 撤销

移动通过回退 10.6 撤销。载荷与索引的行保留。

10.10 C 库绑定

本条承接 SPEC-006 §7 原有的规则:移动 C 库绑定,或某个载荷的 latest,之前,SPEC-006 §6.2 的矩阵必须在新版本上全部通过。 状态:未实现(§6.2 的矩阵部分实现,此顺序没有检查)。


11. 已有默认值的机器

11.1 声明的发布不移动 已实现

使用者或项目所声明的发布([toolchain]、--toolchain、MCPP_TOOLCHAIN、mcpp toolchain default)禁止移动。

当前:已记录的默认值只由首次运行与 mcpp toolchain default 写入,没有路径因线表的变化而改写它。

11.2 首次运行记录的默认值 未实现

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 不存在。

11.3 已安装的载荷 已实现

已安装的载荷保留;移除它们是使用者的决定。

当前:移除载荷的入口只有 mcpp toolchain remove。


12. mcpp 用自己给出的工具链构建

12.1 清单取线表 部分实现

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,但没有线表项记录理由与退出条件。


13. 一致性检查

程序能够检查的规则必须在 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 初版