| 项 | 值 |
|---|---|
| 规范编号 | SPEC-006 |
| 标题 | 工具链管理:身份、来源、选择与载荷契约 |
| 状态 | 草案 v0.4 |
| 最后修改 | 2026-09-28 |
| 对应实现 | 逐条标注;标为「已实现」的条款对应 mcpp >= 2026.9.24.1。标为「未实现」的条款计划与下一批 LLVM 工具链一同落地,届时按实测修订本规范 |
| 相关设计文档 | .agents/docs/2026-09-24-toolchain-selection-and-payload-trust-design.md、.agents/docs/2026-09-24-685-687-msvc-stl-and-toolchain-payloads.md、.agents/docs/2026-09-28-ecosystem-design-and-optimisation-plan.md |
| 相关 issue | mcpp#685、mcpp#687、mcpp#718 |
| 使用文档 | docs/20 - 工具链、docs/32 - 编写载荷、docs/91 - 工具链内部 |
本规范定义 mcpp 对工具链的命名、选择和使用方式,以及一个工具链载荷在发布前必须满足的条件。 目标侧的层模型见 SPEC-002;清单的平面划分与解析轴见 SPEC-004。
用语按 RFC 2119:必须 / 禁止(强制)、应当(强烈建议)、可以(可选)。
| 术语 | 含义 |
|---|---|
| 工具链 | 执行编译的程序及其随附部分:编译器驱动、编译器运行时(builtins 与展开器)、随编译器发布的 C++ 标准库、汇编器、链接器与归档工具 |
| 载荷 | 由 xlings 从索引安装的预构建目录树,身份为命名空间、名字与版本 |
| 来源 | 工具链或 sysroot 的出处:managed(生态包)或 system(在本机定位) |
| sysroot | 编译时所针对的目标环境。Linux 目标上是 C 库载荷;MSVC ABI 目标上是 MSVC toolset(STL、vcruntime、CRT)及其 Windows SDK |
| 构建环境 | 产出一个载荷的机器或容器,以及其中的 C 库 |
工具链必须写作 <族>@<版本>,族为 gcc、llvm、msvc、emsdk、android-ndk。
部分版本必须解析为匹配的最高版本。
只有 msvc 有系统来源,写作 msvc@system。其他族写 @system,必须在读取处被拒绝。
不带族的 system(PATH 上的编译器)必须被拒绝,拒绝信息给出可用的写法。
xim:<族>@<版本> 表示只取生态包。对没有系统来源的族,它与不带前缀的写法等价,规范写法去掉前缀;
对 msvc,前缀改变含义,规范写法保留前缀。xim:msvc@system 与 xim: 之外的命名空间必须被拒绝。
msvc@<版本> 必须先在本机已安装的 toolset 中匹配,匹配不到再取生态包;规则见 §3.4。
mcpp toolchain default msvc@<版本> 在本机已有该 toolset 时必须直接接受,不要求先安装生态包。
前缀必须按版本分量匹配:14.4 匹配 14.4.x,不匹配 14.44。「最高」必须按数字元组比较。
当前:本机 MSVC toolset 的匹配与比较按本条实现;其他族的前缀匹配方式未逐一核对。
一次构建中,工具链及其 sysroot 必须只解析一次;编译、依赖扫描、标准库模块、链接与缓存键必须读取同一个结果。
clang 以 MSVC ABI 为目标时,所选 toolset 与 SDK 以 -Xmicrosoft-visualc-tools-root、-Xmicrosoft-windows-sdk-root、
-Xmicrosoft-windows-sdk-version 显式交给编译器驱动,std.ixx 取自同一个 toolset。
项目写明的版本必须优先于环境变量与本机扫描;被忽略的环境变量必须以一行说明报告。
| 条目 | 状态 |
|---|---|
受管 MSVC 忽略 WindowsSdkDir 并报告 |
已实现 |
VSINSTALLDIR 优先于 vswhere |
已实现 |
写明版本时忽略 VCToolsInstallDir 与 VSINSTALLDIR,并报告 |
已实现 |
凡由探测得到的选择,其来源、版本与 SDK 必须打印在构建输出中,写入 resolution.json,并进入缓存键。
MSVC ABI 目标上:SDK 以 ucrt@<版本> 进入运行时身份;clang 行的 toolset 与 SDK 写入 resolution.json 的
msvc_toolset 与 windows_sdk,toolset 目录与 SDK 版本进入缓存键,stdlibVersion 记为 toolset 的版本。
本机候选必须覆盖全部 VS 实例(含预发布版)及每个实例下的全部 toolset。
msvc@system 必须按以下顺序取第一个完整的候选:
VCToolsInstallDir所指的 toolset;VSINSTALLDIR或VCINSTALLDIR所指实例的默认 toolset;PATH上cl.exe所在的 toolset;- 带 C++ 组件、且安装版本最高的实例的默认 toolset;
- 没有 vswhere 时,扫描固定路径得到的候选。
带版本的写法必须依次尝试本机候选、已安装的生态包、索引;三者都没有时,必须报错并列出三类候选。
「完整」指本次构建所需的文件都在:库目录;需要 import std 时的 modules/std.ixx;cl.exe 行的 cl.exe。
不完整的候选必须被跳过,并报告。
cl.exe 行与 clang 行使用同一个选择。
在 *-windows-msvc 目标上,编译器是工具链,MSVC toolset 是 sysroot。
[target.<三元组>].sysroot在这些行上可以写作msvc@system、msvc@<版本>或xim:msvc@<版本>,含义同 §2。- 未写时必须等同于
msvc@system。 - 编译器为
cl.exe时,sysroot 必须是该编译器所属的 toolset;另写一个不同的 sysroot,必须被拒绝。 - SDK 必须跟随 toolset 的来源:生态包 toolset 用随其安装的
windows-sdk载荷,本机 toolset 用本机扫描的结果。 - 目标侧报告(SPEC-002)中,c-abi 与 c++ 两层记为预制来源;具体的 toolset 与 SDK 记入
resolution.json(§3.3)。 - 该值禁止作为 C 库包进入项目环境的安装集合。
描述产物的属性(最低系统版本、三元组中的版本段)必须按目标判定,与宿主无关; 只有在宿主上执行的编译(build.mcpp)按宿主判定。macOS 的 deployment target 在任何宿主上都按目标解析与施加。
在 *-windows-msvc 目标上,CRT 模型(静态或动态)是目标 ABI 的属性,而非某一个编译器的属性:
cl.exe 与以该 ABI 为目标的 clang 行必须接收同一个模型,分别以各自驱动的拼写(/MT//MD,
-fms-runtime-lib=static/=dll)发给编译单元、std/std.compat BMI 与链接命令。
- 未声明的契约在该 ABI 上,对每个角色都必须解析为
toolchain-coupled:动态 CRT,并将所选 toolset 自带的vcruntime140.dll/msvcp140.dll等文件置于产物旁。 self-contained,或linkage = "static",必须解析为静态 CRT。host-coupled必须解析为动态 CRT,且不放置文件。- 所选 toolset 不带
VC\Redist\MSVC\<版本>\<架构>\Microsoft.VC*.CRT目录时,未声明的契约必须 静默解析为host-coupled;显式声明的toolchain-coupled必须被拒绝,并指出缺失的目录—— 这是行的一个属性,不因某一次构建而降级。 [build] cxxflags或dialect_cxxflags中出现的自由拼写 CRT 词(/MT、/MD、-fms-runtime-lib=*等)与已解析的模型一致时应当被警告为冗余;不一致时必须被拒绝,消息必须指出该词、 所在的键与该词对应的值。依赖包的[build] cxxflags同样检查,因为它们作用于该包自己的单元: 不一致时必须被拒绝并指出该包;一致时不警告,因为替代它的键cxx_runtime只属于根。 调试 CRT 词(/MTd、/MDd、-fms-runtime-lib=*_dbg)必须被拒绝:模型不表达调试 CRT, 标准库模块与链接使用发布版 CRT。
PE 程序旁的每个名字,其字节来自哪里,必须由一个解析器回答(mcpp.build.runtime_placement)。
构建的放置边、链接后的 place-dlls 边、mcpp run/mcpp test 携带的文件与 mcpp pack 都读取
这一个答案,禁止各自再决定。
- 候选分三类。 声明:
[runtime] deploy_files与插件的deploy(SPEC-007 R4.2)。工具链: 所选 toolset 的Microsoft.VC*.CRT目录中的文件。推导:在运行时搜索目录中找到的 DLL。 声明优先于工具链,工具链优先于推导。 - MSVC C++ 运行时是一个带版本的集合。 同一集合的文件禁止跨版本混用,集合整体选择:
默认取 toolset 的集合;某个推导目录中的集合完整(含 toolset 集合的每一个名字)且其
VERSIONINFO文件版本严格更新时,取该集合,并说明一次。集合的版本取其成员中最旧的一个。 - 契约决定种类,而不只是次序。 host-coupled 下,禁止从任何来源放置运行时文件:声明的
运行时文件必须在编译前被拒绝(
crt-declared-under-host-coupled),推导目录中的运行时文件 被丢弃并说明一次。self-contained 下程序不导入运行时;依赖带来运行时的名字时,按上一条的集合 规则放置。 - 声明的运行时文件优先于集合选择,并与 toolset 的运行时版本比较。 更旧时必须给出警告,
点名两个版本。在这一下限的读数被证实可靠之前,它是警告而不是拒绝:可执行映像的
MajorLinkerVersion.MinorLinkerVersion由 lld-link 写为 14.0,不能回答「哪个 toolset 构建了 这个映像」。 - 读不出的版本不作决定。 某个候选的
VERSIONINFO读不出时,按种类次序决定,并说明一次; 禁止把缺失的版本当作更旧或更新比较。 - 依赖目录携带运行时文件是打包缺陷。 它必须被说明一次,禁止成为静默的来源 (xim-pkgindex 的配方规则见其文档)。
- 规划之后才出现的名字用同一规则。
prepare在构建中填充的目录里的运行时名字,由链接后 的放置以同一个解析器决定;place-dlls以--crt <规则>与--toolset-crt <目录>接收规划 采用的规则。mcpp pack在不携带运行时的模式下把运行时的名字当作宿主提供,不论哪个目录提供 了副本。 - 构建期的工具与程序使用同一个运行时。 为 MSVC ABI 目标运行的每个 action,
PATH的首位 必须是 toolset 的运行时目录。系统目录在加载器的搜索中先于PATH,所以装有 VC++ redistributable 的机器使用系统的运行时;PATH在系统没有时提供它。 - 决定被记录。
resolution.json的runtime.placement(每个目的地、来源与种类)、runtime.crt_set(规则、来源种类与版本)与runtime.placement_notes。
MinGW 的运行时(libstdc++-6.dll、libgcc_s_seh-1.dll、libwinpthread-1.dll)按种类次序决定,
没有版本规则:这些 DLL 不携带可靠的 VERSIONINFO,也没有工具链候选为它们放置。
载荷在使用时禁止依赖构建环境中的路径。以下文件含构建环境的路径,但不参与编译与链接,可以豁免:
libtool 的 .la;gcc 的 plugin/include/configargs.h 与 install-tools/mkheaders.conf。
gcc 载荷的 lib/gcc/<三元组>/<版本>/include-fixed/ 禁止含带 fixincludes 横幅(auto-edited by fixincludes)的头文件。
这类头文件是构建环境 C 库的冻结副本,在搜索顺序上排在构建所用的 C 库之前。gcc 自己生成、不带横幅的头文件不受此限。
当前:gcc 13.3.0、15.1.0 与 11.5.0 的 x86_64-linux-gnu 发布归档含这类文件(mcpp#687)。索引中的 gcc 配方在安装时删除它们(openxlings/xim-pkgindex#870 之后生效);
在此之前安装的载荷由 mcpp self doctor 报告(§6.4)。故标为部分实现:安装后的载荷满足本条,发布归档不满足。
| 要求 | 状态 |
|---|---|
安装后的改写必须限于一张列出的清单:ELF 的 PT_INTERP 与 RUNPATH、clang 的 .cfg |
部分实现:改写集中在单一入口,清单未写成规范 |
改写必须由标记文件 .mcpp-fixup.json 记录 |
已实现 |
| 改写必须经副本与原子重命名完成 | 部分实现:mcpp 的安装后修正管线如此;xlings 安装时所做的改写未核对 |
| 清单之外的文件必须与发布归档一致 | 未实现:没有检查 |
gcc 的 specs 禁止在安装时改写 |
已实现 |
载荷必须包含其声明的能力所需的全部文件,例如 llvm 载荷的 share/libc++/v1/std.cppm,以及 libc++ 运行期依赖的 libatomic.so.1。
当前:准入脚本检查 llvm 载荷的这几项,但它不在 CI 中运行。
载荷根目录的 .mcpp-toolchain.json 已支持 schema、frontend、platform_floor、std_module_defines、runner(已实现)。
它应当另外记录来源(未实现):配方所在仓库与提交、编译器的配置行、上游源码的 sha256、构建所用的 C 库及其版本。
一个版本在索引中的内容由 sha256 固定。内容有任何变化,必须使用新的资产名,因为 GitCode 上的发布资产既不能替换,也不能删除。
每个发布的载荷必须能由一份检入仓库的配方重新构建。
当前:llvm 子包、musl、glibc 等由 xim-pkgindex 中的构建脚本产出;gcc 由 fromsource 配方产出。已发布的三个 gcc 载荷出自两个不同的构建环境,载荷本身不记录构建环境。配方与构建 CI 所在的仓库待定。
构建必须在固定的容器中进行;构建 sysroot 必须取自索引发布的 C 库载荷,禁止取自某台机器的 subos。
| 等级 | 含义 | 要求 |
|---|---|---|
| 可重建 | 由配方与固定输入重新得到功能等价的载荷 | 必须 |
| 可验收 | 由第 6 节的程序判定合格与否 | 必须 |
| 逐字节可复现 | 固定时间戳、路径前缀映射与归档顺序 | 可以 |
必须有一个程序对一个载荷目录执行 §4.1 至 §4.4 的全部检查,并在发布前运行。 每一项检查必须有反向测试:把对应的缺陷放回载荷,该项检查必须失败。
载荷发布或 C 库绑定变化时,必须编译一个矩阵:一维是该目标行在索引中可解析的全部编译器版本,另一维是全部 C 库版本。
格子必须由索引枚举得到,禁止使用手写清单。
每格至少编译一个使用 <memory>、<mutex>、<thread> 的程序,以及一个 import std 的程序。
在 MSVC ABI 目标上,矩阵的另一维是 toolset 版本。
当前:描述符变化时,CI 只安装不带版本号时解析出的那一个版本(即 latest),并编译一个 import std 程序。gcc 描述符的改动因此只在 16.1.0 上验证过,13.3.0 与 15.1.0 未被覆盖。
xim-pkgindex 的准入脚本 verify-toolchain.sh 对一个载荷归档做一次真实的编译、链接与运行,并对 llvm 载荷检查完整性与 CRT 的解析位置(已实现)。
它必须在 CI 中运行(未实现),并必须覆盖 §6.2 的程序(未实现)。当前它默认使用 glibc 2.39,测试程序不含线程头文件。
mcpp self doctor 必须对已安装的载荷执行 §6.1 中不依赖归档的检查;发现问题时,必须给出重装命令。
当前:doctor 执行 §4.2 的检查(gcc 载荷 include-fixed/ 中带 fixincludes 横幅的文件);§4.1、§4.3、§4.4 的检查未实现。
移动 C 库绑定或某个载荷的 latest 之前,§6.2 的矩阵必须在新版本上全部通过。
| 版本 | 日期 | 变更 |
|---|---|---|
| v0.1 | 2026-09-24 | 初版草案:身份与写法、来源与选择(含 MSVC ABI 目标的 sysroot)、载荷契约、构建、验收、发布顺序 |
| v0.2 | 2026-09-24 | 随 mcpp 2026.9.24.1 更新实现状态:§2.3、§2.4、§3.1 至 §3.6 已实现;§4.2、§6.4 部分实现;§2.2 更正:不带族的 system 被拒绝 |
| v0.3 | 2026-09-28 | 随 mcpp 2026.9.28.1:新增 §3.7,MSVC ABI 的 CRT 模型是目标 ABI 的性质,cl 与 clang++ 同样收到,默认 toolchain-coupled(mcpp#718)。 |
| v0.4 | 2026-09-28 | 随 mcpp 2026.9.28.2:新增 §3.7.1,程序旁的文件由一个解析器决定;MSVC C++ 运行时是一个带版本的集合;契约决定种类;声明的运行时文件与 toolset 的版本比较;读不出的版本不作决定;action 的 PATH 首位是 toolset 的运行时目录(2026-09-28 设计 WS1)。 |