Skip to content

Commit dd4344e

Browse files
committed
T8 (#718 W9): the CRT model is a property of the MSVC ABI, not of cl.exe
Every MSVC-ABI row now receives the same CRT model: cl spells it /MT or /MD, clang++ targeting *-windows-msvc spells it -fms-runtime-lib=static or =dll. One helper (msvc_abi_crt_word, dialect.cppm) decides the word for the translation units, the std/std.compat BMIs and the link command alike, closing #649 E10 (a compile-only flag never reached the clang driver's own link-time choice of -defaultlib:). toolchain-coupled (the dynamic CRT, with the toolset's own vcruntime140.dll/msvcp140.dll staged beside the artifact) is now the default for every role on this ABI (dist::msvc_abi_default_contract, ContractStatement::msvcAbiDefault). A toolset with no VC\Redist\MSVC directory defaults to host-coupled silently and refuses an explicit toolchain-coupled, naming the missing directory (prepare/plan.cpp). A free-form CRT word in cxxflags/dialect_cxxflags is checked against the resolved model: agreeing is a warning, contradicting is a refusal (dialect::check_crt_word, wired in prepare/scan.cpp). The toolset's redistributable directory is carried as its own Toolchain field (msvcRedistDir), populated for cl from vc_redist_dir and for the LLVM row from its sysroot's tools directory (vc_redist_dir_for_tools_dir) rather than from linkRuntimeDirs, which holds LLVM's own runtime directories on that row. The staging gate and the mcpp run/test search path both read this field, gated on the MSVC ABI rather than on which compiler is in use. mcpp pack carries the staged DLLs by default; an explicit --mode system now resolves a defaulted (never-declared) toolchain-coupled contract to host-coupled instead of refusing. e2e 703 is inverted to the new default; e2e 814 covers the LLVM row's import table, staged DLL, clean-PATH run, self-contained round trip, BMI switching (A, B, A) and pack modes (Windows-only, unverified here). Unit tests cover the CRT-word derivation, the free-form-word check, the MSVC-ABI default/redistributable resolution and a compute_flags-level property test across both rows; the link-line half of that test is gated on mcpp.platform.is_windows, since link_shape resolves LinkShape::PeLld only when current_link_host() is Windows and a Linux-built mcpp cannot reach that branch regardless of the plan's target triple. docs/20, docs/zh/20 and SPEC-006 record the new default and the upgrade; docs/04 needed no change (it only points at docs/20). 131/131 unit tests pass on Linux (gcc 16.1.0 and llvm 22.1.8 rows); docs structure/style checks pass.
1 parent 6e2cefe commit dd4344e

22 files changed

Lines changed: 1135 additions & 268 deletions

‎docs/20-toolchains.md‎

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,10 @@ It is a **compatibility floor declaration**, not a payload binding like
499499
`glibc@2.39` on Linux — `ucrtbase.dll` is a Windows component and mcpp neither
500500
ships nor substitutes it.
501501

502-
**CRT model.** `/MD` (host-coupled) by default; `/MT` when either
502+
**CRT model.** `/MD` by default, with the toolset's own redistributable staged
503+
beside the artifact (`toolchain-coupled` — see [On the MSVC
504+
runtime](#on-the-msvc-runtime) below for the full model, which applies to `cl`
505+
and to clang++ on this ABI alike); `/MT` when either
503506

504507
```toml
505508
[target.x86_64-windows-msvc]
@@ -1131,7 +1134,7 @@ loaded *into* a process that already has a C++ runtime.
11311134
|---|---|---|
11321135
| ELF (Linux, …) | `toolchain-coupled` | ELF has one global symbol namespace and the first definition loaded wins. A `.so` that statically embedded libstdc++ **exports** it, and the executable linking that library binds *its* `std::` references there — its own `self-contained` contract silently becomes a no-op, and its C++ runtime is whichever build of that library happens to load. |
11331136
| Mach-O | `self-contained` | the mechanism there is already `-load_hidden`, i.e. hidden visibility, so dyld never unifies those symbols; and toolchain-coupled is not available on macOS at all (see the note below). |
1134-
| PE (Windows) | `self-contained` | PE has no global symbol namespace — imports resolve per-DLL by name, so a DLL's private runtime cannot be picked up by anything else. |
1137+
| PE, GNU ABI (MinGW) | `self-contained` | PE has no global symbol namespace — imports resolve per-DLL by name, so a DLL's private runtime cannot be picked up by anything else. The MSVC ABI's own default is a separate rule — see [On the MSVC runtime](#on-the-msvc-runtime) below. |
11351138

11361139
Setting `shared = "self-contained"` on ELF is supported and does exactly what it
11371140
says: the library embeds the runtime. mcpp additionally passes
@@ -1214,28 +1217,75 @@ artifact than the manifest asked for.
12141217

12151218
### On the MSVC runtime
12161219

1217-
The CRT model is the mechanism here, and it is a **whole-project** switch: cl
1218-
bakes `_MSVC_MT`/`_MSVC_MD` into the one `std` module a project builds, so a
1219-
per-role contract that disagrees with the project's cannot be honoured and is
1220-
reported rather than ignored.
1221-
1222-
| value | meaning on MSVC |
1223-
|---|---|
1224-
| `self-contained` | `/MT` — the static CRT. `linkage = "static"` selects the same thing from the libc axis. |
1225-
| `host-coupled` (default under `/MD`) | the target provides `vcruntime140.dll` / `msvcp140.dll` — i.e. Visual Studio or the redistributable is installed there. |
1226-
| `toolchain-coupled` | the toolset's **own** copy of those DLLs travels with the artifact. |
1227-
1228-
`toolchain-coupled` is worth spelling out, because the obvious reading is
1229-
wrong. `ucrtbase.dll` *is* a Windows component (since Windows 10) and mcpp
1230-
never ships it. `vcruntime140.dll` and `msvcp140.dll` are **not**: every MSVC
1231-
toolset carries them under `VC\Redist\MSVC\<version>\<arch>\`, exactly the
1232-
way a gcc payload carries `libstdc++.so`. Under this contract mcpp stages them
1233-
beside the artifact — which is what makes a default `/MD` build runnable on a
1234-
machine that has only the pinned toolset and no Visual Studio at all.
1220+
The CRT model is a property of the **target ABI**, not of the compiler: `cl`
1221+
and clang++ targeting `*-windows-msvc` (the `llvm` row) receive the *same*
1222+
model, each spelling it for its own driver. It is also a **whole-project**
1223+
switch: `cl` bakes `_MSVC_MT`/`_MSVC_MD` into the one `std` module a project
1224+
builds, so a per-role contract that disagrees with the project's cannot be
1225+
honoured and is reported rather than ignored.
1226+
1227+
| value | meaning on the MSVC ABI | `cl` spelling | clang++ spelling |
1228+
|---|---|---|---|
1229+
| `self-contained` (or `linkage = "static"`) | the static CRT | `/MT` | `-fms-runtime-lib=static` |
1230+
| `toolchain-coupled` (**default**) | the dynamic CRT, with the toolset's own copy of `vcruntime140.dll`/`msvcp140.dll` staged beside the artifact | `/MD` | `-fms-runtime-lib=dll` |
1231+
| `host-coupled` | the dynamic CRT, with nothing staged — the target provides those DLLs itself (Visual Studio, or the redistributable installer) | `/MD` | `-fms-runtime-lib=dll` |
1232+
1233+
**`toolchain-coupled` is the default**, whatever `cxx_runtime` says, for every
1234+
role. This is worth spelling out, because the obvious reading of "portable by
1235+
default" is wrong here: `ucrtbase.dll` *is* a Windows component (since Windows
1236+
10) and mcpp never ships it, but `vcruntime140.dll` and `msvcp140.dll` are
1237+
**not** — every MSVC toolset carries them under
1238+
`VC\Redist\MSVC\<version>\<arch>\`, exactly the way a gcc payload carries
1239+
`libstdc++.so`. Under this contract mcpp stages them beside the artifact
1240+
(on the `mcpp build` output directory) and puts the same directory on the
1241+
`mcpp run`/`mcpp test` search path — which is what makes the default build
1242+
runnable on a machine that has only the pinned toolset and no Visual Studio
1243+
at all.
1244+
1245+
A resolved toolset that carries no `VC\Redist\MSVC` directory (measured on
1246+
some `msvc@system` installs) cannot deliver `toolchain-coupled`. The
1247+
undeclared default then resolves to `host-coupled` instead, silently — this is
1248+
a property of the row, stated once here, not a warning on every build of it.
1249+
An **explicit** `cxx_runtime = "toolchain-coupled"` on such a row is refused,
1250+
naming the missing directory: an explicit statement a toolset cannot meet is
1251+
an error, never a silent downgrade.
12351252

12361253
The debug CRT (`vcruntime140d.dll` and friends, under `debug_nonredist\`) is
1237-
never staged: it may not be redistributed.
1238-
1254+
never staged: it may not be redistributed, and mcpp's `dev` profile does not
1255+
select it — it states debug information, not a different CRT. That axis stays
1256+
deferred until a consumer needs it.
1257+
1258+
Combining `toolchain-coupled` or `host-coupled` with `/MT` (`linkage =
1259+
"static"`, or `self-contained`) is a contradiction rather than a missing
1260+
feature — a static CRT leaves no DLL to couple to — so it is reported and
1261+
resolved to `self-contained`. `mcpp pack` enforces the other half: a mode
1262+
that bundles nothing (`--mode static`) together with an *explicit*
1263+
`toolchain-coupled` cannot deliver it and refuses; `--mode system` on a
1264+
project that never stated a contract resolves the default to `host-coupled`
1265+
instead, since an explicit mode outranks a default.
1266+
1267+
**A free-form CRT word is always a second statement.** Every MSVC-ABI build
1268+
now states its own CRT, so a literal `/MT`, `/MD`, `/MTd`, `/MDd` or
1269+
`-fms-runtime-lib=*` (either dash) in `[build] cxxflags` or `dialect_cxxflags`
1270+
can never be the only voice. One that **agrees** with the resolved model is
1271+
warned as redundant, naming the key (`cxx_runtime` or `linkage`) to write
1272+
instead; one that **contradicts** it is refused, naming the word, the key it
1273+
was found in, and the value it corresponds to. The engine never lets the
1274+
last word on the command line decide silently.
1275+
1276+
> **Upgrading to 2026.9.28.1?** `cl`-row projects are unchanged apart from
1277+
> gaining the staged DLLs beside their programs. **LLVM-row programs move
1278+
> from the static to the dynamic CRT**: before this release clang++ on the
1279+
> MSVC ABI received no model at all and linked `libcmt` regardless of
1280+
> `cxx_runtime`; now it receives the same model `cl` does, defaulting to
1281+
> `toolchain-coupled`. A project that links a prebuilt `/MT` library on this
1282+
> row now fails to link (`LNK2038`, a CRT mismatch) and should state
1283+
> `cxx_runtime = "self-contained"` to restore the static CRT it had before.
1284+
> Two manifests that built before this release are refused after it: a
1285+
> free-form CRT word that contradicts the resolved model, and an explicit
1286+
> `toolchain-coupled` on a row whose toolset ships no redistributable — see
1287+
> above for both.
1288+
>
12391289
> **Upgrading from 2026.8.15 or earlier?** This key used to be **inert** on the
12401290
> MSVC ABI — it reported `not implemented for the MSVC runtime yet` and every
12411291
> value fell back to `/MD`. Since 2026.8.16 it is honoured, so a manifest that
@@ -1244,23 +1294,6 @@ never staged: it may not be redistributed.
12441294
> model, and the switch is silent because the value was always valid. A project
12451295
> that set it while the key did nothing should re-confirm the intended value.
12461296
1247-
Combining it with `/MT` is a contradiction rather than a missing feature — a
1248-
static CRT leaves no DLL to couple to — so it is reported and resolved to
1249-
`self-contained`. `mcpp pack` enforces the other half: a mode that bundles
1250-
nothing (`--mode system`, `--mode static`) cannot deliver `toolchain-coupled`
1251-
and refuses.
1252-
1253-
**Clang on the MSVC ABI** (the `llvm` row of `x86_64-windows-msvc`, mcpp
1254-
2026.9.16.1+ for the record). The table above describes `cl.exe`, the one
1255-
compiler mcpp passes a CRT model to. Clang on the MSVC ABI speaks the GNU dialect
1256-
and receives no model, and its driver links the static CRT (`-defaultlib:libcmt`):
1257-
a program built on this row imports no `vcruntime140.dll`, `msvcp140.dll` or
1258-
`api-ms-win-crt-*`, and each DLL carries its own CRT. The row is therefore
1259-
`self-contained` whatever `cxx_runtime` says, `resolution.json` records it so, and
1260-
an explicit `host-coupled` or `toolchain-coupled` prints that the row does not
1261-
deliver it. A project that needs the dynamic CRT on the MSVC ABI builds with
1262-
`msvc@system`.
1263-
12641297
**Scope.** The contract governs the C++ runtime only. Static **libc** is a separate
12651298
axis (`linkage = "static"` / `--static`, e.g. a musl target), and the deployment
12661299
floor is a third — `macos_deployment_target` in `[package]` for Apple targets,

‎docs/specs/toolchain-management.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,23 @@ MSVC ABI 目标上:SDK 以 `ucrt@<版本>` 进入运行时身份;clang 行的 to
119119
描述产物的属性(最低系统版本、三元组中的版本段)**必须**按目标判定,与宿主无关;
120120
只有在宿主上执行的编译(build.mcpp)按宿主判定。macOS 的 deployment target 在任何宿主上都按目标解析与施加。
121121

122+
### 3.7 MSVC ABI 目标的 CRT 模型 已实现
123+
124+
在 `*-windows-msvc` 目标上,CRT 模型(静态或动态)是目标 ABI 的属性,而非某一个编译器的属性:
125+
`cl.exe` 与以该 ABI 为目标的 clang 行**必须**接收同一个模型,分别以各自驱动的拼写(`/MT`/`/MD`,
126+
`-fms-runtime-lib=static`/`=dll`)发给编译单元、`std`/`std.compat` BMI 与链接命令。
127+
128+
- 未声明的契约在该 ABI 上,对每个角色都**必须**解析为 `toolchain-coupled`:动态 CRT,并将所选
129+
toolset 自带的 `vcruntime140.dll`/`msvcp140.dll` 等文件置于产物旁。
130+
- `self-contained`,或 `linkage = "static"`,**必须**解析为静态 CRT。
131+
- `host-coupled` **必须**解析为动态 CRT,且不放置文件。
132+
- 所选 toolset 不带 `VC\Redist\MSVC\<版本>\<架构>\Microsoft.VC*.CRT` 目录时,未声明的契约**必须**
133+
静默解析为 `host-coupled`;显式声明的 `toolchain-coupled` **必须**被拒绝,并指出缺失的目录——
134+
这是行的一个属性,不因某一次构建而降级。
135+
- `[build] cxxflags` 或 `dialect_cxxflags` 中出现的自由拼写 CRT 词(`/MT`、`/MD`、`-fms-runtime-lib=*`
136+
等)与已解析的模型一致时**应当**被警告为冗余;不一致时**必须**被拒绝,消息**必须**指出该词、
137+
所在的键与该词对应的值。
138+
122139
---
123140

124141
## 4. 载荷契约

‎docs/zh/20-toolchains.md‎

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,7 @@ libc++.a/libc++abi.a/libunwind.a。更低的 macOS 下限(11–13)需要一
10581058
|---|---|---|
10591059
| ELF(Linux 等) | `toolchain-coupled` | ELF 只有一个全局符号命名空间,先加载的定义胜出。一个静态内嵌了 libstdc++ 的 `.so` 会把它**导出**,链接该库的可执行文件于是把自己的 `std::` 引用绑定到那里——它自己的 `self-contained` 契约会静默变成空操作,它的 C++ 运行时变成碰巧加载到的那一份该库。 |
10601060
| Mach-O | `self-contained` | 那里的机制本来就是 `-load_hidden`,即隐藏可见性,dyld 因此从不归一这些符号;而且 macOS 上根本没有 toolchain-coupled 这一档(见下文注记)。 |
1061-
| PE(Windows) | `self-contained` | PE 没有全局符号命名空间——导入按 DLL 逐个按名解析,一个 DLL 的私有运行时不可能被别的东西捡走。 |
1061+
| PE,GNU ABI(MinGW) | `self-contained` | PE 没有全局符号命名空间——导入按 DLL 逐个按名解析,一个 DLL 的私有运行时不可能被别的东西捡走。MSVC ABI 自己的默认值是另一条规则——见下文[在 MSVC 运行时上](#在-msvc-运行时上)。 |
10621062

10631063
在 ELF 上显式写 `shared = "self-contained"` 是支持的,而且就是字面意思:
10641064
库会内嵌运行时。此时 mcpp 会额外为标准库归档传递
@@ -1134,27 +1134,66 @@ cxx_runtime = { shared = "self-contained" }
11341134

11351135
### 在 MSVC 运行时上
11361136

1137-
这里的机制就是 CRT 模型,而它是一个**整个工程**级的开关:cl 会把
1137+
CRT 模型是**目标 ABI** 的属性,不是编译器的属性:`cl` 与以
1138+
`*-windows-msvc` 为目标的 clang++(`llvm` 行)接收**同一个**模型,各自以
1139+
自己驱动的拼写发出。它同时是一个**整个工程**级的开关:`cl` 会把
11381140
`_MSVC_MT`/`_MSVC_MD` 烘进一个工程唯一构建的那份 `std` 模块,所以一个与
11391141
工程不一致的按角色契约无法被兑现,会被报出来,而不是被忽略。
11401142

1141-
| 取值 | 在 MSVC 上的含义 |
1142-
|---|---|
1143-
| `self-contained` | `/MT`——静态 CRT。`linkage = "static"` 从 libc 那根轴选中的是同一件事。 |
1144-
| `host-coupled`(`/MD` 下的默认值) | 由目标机器提供 `vcruntime140.dll` / `msvcp140.dll`——即那台机器装了 Visual Studio 或对应的 redistributable。 |
1145-
| `toolchain-coupled` | toolset **自带**的那份 DLL 跟着产物一起走。 |
1143+
| 取值 | 在 MSVC ABI 上的含义 | `cl` 的拼写 | clang++ 的拼写 |
1144+
|---|---|---|---|
1145+
| `self-contained`(或 `linkage = "static"`) | 静态 CRT | `/MT` | `-fms-runtime-lib=static` |
1146+
| `toolchain-coupled`(**默认值**) | 动态 CRT,toolset 自带的 `vcruntime140.dll`/`msvcp140.dll` 会被放到产物旁边 | `/MD` | `-fms-runtime-lib=dll` |
1147+
| `host-coupled` | 动态 CRT,不放置任何文件——由目标机器自己提供这些 DLL(Visual Studio,或 redistributable 安装程序) | `/MD` | `-fms-runtime-lib=dll` |
11461148

1147-
`toolchain-coupled` 值得说清楚,因为直觉上的理解是错的。`ucrtbase.dll`
1148-
**是**一个 Windows 组件(Windows 10 起),mcpp 从不分发它;而
1149-
`vcruntime140.dll` 与 `msvcp140.dll` **不是**:每个 MSVC toolset 都在
1149+
**`toolchain-coupled` 是默认值**,不论 `cxx_runtime` 写了什么,对每个角色
1150+
皆然。这一点值得说清楚,因为「默认即可移植」这个直觉在这里是错的:
1151+
`ucrtbase.dll` **是**一个 Windows 组件(Windows 10 起),mcpp 从不分发它;
1152+
而 `vcruntime140.dll` 与 `msvcp140.dll` **不是**:每个 MSVC toolset 都在
11501153
`VC\Redist\MSVC\<version>\<arch>\` 下带着它们,和一个 gcc 载荷带着
1151-
`libstdc++.so` 是同一件事。在这份契约下,mcpp 会把它们放到产物旁边——这
1152-
正是让一次默认的 `/MD` 构建,能在一台只装了被钉住的 toolset、完全没有
1154+
`libstdc++.so` 是同一件事。在这份契约下,mcpp 会把它们放到产物旁边(在
1155+
`mcpp build` 的产出目录里),并把同一个目录放上 `mcpp run`/`mcpp test`
1156+
的搜索路径——这正是让默认构建,能在一台只装了被钉住的 toolset、完全没有
11531157
Visual Studio 的机器上运行起来的原因。
11541158

1155-
调试版 CRT(`debug_nonredist\` 下的 `vcruntime140d.dll` 等)永远不会被
1156-
放进去:它不可再分发。
1157-
1159+
一个不带 `VC\Redist\MSVC` 目录的 toolset(在某些 `msvc@system` 安装上实测
1160+
存在)无法兑现 `toolchain-coupled`。此时未声明的默认值会静默解析为
1161+
`host-coupled`——这是这一行的一个属性,在此说明一次,不是每次构建都打印
1162+
的警告。在这样的行上**显式**写 `cxx_runtime = "toolchain-coupled"` 会被
1163+
拒绝,并指出缺失的目录:一个 toolset 兑现不了的显式声明是一个错误,绝不
1164+
是一次静默降级。
1165+
1166+
调试版 CRT(`debug_nonredist\` 下的 `vcruntime140d.dll` 等)永远不会被
1167+
放进去:它不可再分发,而且 mcpp 的 `dev` profile 不会选中它——那个
1168+
profile 表达的是调试信息,不是另一个 CRT。这根轴留待有消费者需要时再设计。
1169+
1170+
把 `toolchain-coupled` 或 `host-coupled` 和 `/MT`(`linkage = "static"`,
1171+
或 `self-contained`)一起写是一处**矛盾**,而不是缺功能——一份静态 CRT
1172+
根本没有 DLL 可以耦合——所以它会被报出来,并落回 `self-contained`。
1173+
`mcpp pack` 兜底另一半:一个什么都不打包的模式(`--mode static`)配上一个
1174+
**显式**的 `toolchain-coupled` 兑现不了,会直接拒绝;而 `--mode system`
1175+
用在一个从未声明契约的工程上,会把默认值解析为 `host-coupled`——一个
1176+
显式的 mode 胜过一个默认值。
1177+
1178+
**自由拼写的 CRT 词永远是第二次声明。** 每个 MSVC ABI 构建现在都会声明
1179+
自己的 CRT,所以 `[build] cxxflags` 或 `dialect_cxxflags` 里出现的字面
1180+
`/MT`、`/MD`、`/MTd`、`/MDd` 或 `-fms-runtime-lib=*`(两种短横线拼写皆
1181+
可)永远不能是唯一的声音。与已解析的模型**一致**的会被警告为冗余,并指
1182+
出应当改写的键(`cxx_runtime` 或 `linkage`);**不一致**的会被拒绝,消息
1183+
指出该词、它所在的键,以及它对应的取值。引擎绝不让命令行上最后一个词
1184+
静默胜出。
1185+
1186+
> **升级到 2026.9.28.1?** `cl` 行的工程不受影响,只是程序旁多了被放置
1187+
> 的 DLL。**LLVM 行的程序会从静态 CRT 换到动态 CRT**:这次发布之前,
1188+
> MSVC ABI 上的 clang++ 收不到任何模型,总是链接 `libcmt`,与
1189+
> `cxx_runtime` 无关;现在它收到与 `cl` 相同的模型,默认解析为
1190+
> `toolchain-coupled`。一个在这一行链接 `/MT` 预构建库的工程,现在会链接
1191+
> 失败(`LNK2038`,一处 CRT 不一致),应当写
1192+
> `cxx_runtime = "self-contained"` 以恢复它此前的静态 CRT。有两类
1193+
> manifest 在这次发布前能构建、之后会被拒绝:一个与已解析模型矛盾的自由
1194+
> 拼写 CRT 词,以及在一个 toolset 不带 redistributable 的行上显式写
1195+
> `toolchain-coupled`——两者见上文。
1196+
>
11581197
> **从 2026.8.15 或更早版本升级时的变化。** 这个键在 MSVC ABI 上曾经是
11591198
> **空操作**——它会报 `not implemented for the MSVC runtime yet`,写任何
11601199
> 值都会退回 `/MD`。自 2026.8.16 起它真的会生效,于是一份从那个年代带着
@@ -1163,21 +1202,6 @@ Visual Studio 的机器上运行起来的原因。
11631202
> 这个值一直是合法的,这次切换是**静默**的。如果一个工程是在这个键尚未
11641203
> 生效时写下它的,应当重新确认所需的取值。
11651204
1166-
把它和 `/MT` 一起写是一处**矛盾**,而不是缺功能——一份静态 CRT 根本没有
1167-
DLL 可以耦合——所以它会被报出来,并落回 `self-contained`。另一半由
1168-
`mcpp pack` 兜底:一个什么都不打包的模式(`--mode system`、
1169-
`--mode static`)兑现不了 `toolchain-coupled`,会直接拒绝。
1170-
1171-
**MSVC ABI 上的 clang**(`x86_64-windows-msvc` 的 `llvm` 行,记录自 mcpp
1172-
2026.9.16.1 起)。上表描述的是 `cl.exe`,mcpp 只向它传递 CRT 模型。MSVC
1173-
ABI 上的 clang 使用 GNU 方言,收不到任何模型,它的驱动链接静态 CRT
1174-
(`-defaultlib:libcmt`):这一行构建出的程序不会导入
1175-
`vcruntime140.dll`、`msvcp140.dll` 或 `api-ms-win-crt-*`,每个 DLL 各自
1176-
带着自己的 CRT。因此这一行无论 `cxx_runtime` 写什么都是
1177-
`self-contained`,`resolution.json` 如实记录这一点,显式写
1178-
`host-coupled` 或 `toolchain-coupled` 会打印这一行兑现不了它。需要在
1179-
MSVC ABI 上使用动态 CRT 的工程,应当用 `msvc@system` 构建。
1180-
11811205
**边界。** 该契约只管辖 C++ 运行时。静态 **libc** 是另一根轴
11821206
(`linkage = "static"` / `--static`,例如一个 musl target),部署下限是
11831207
第三根轴——Apple target 用 `[package]` 里的

0 commit comments

Comments
 (0)