Skip to content

Commit 26b26ec

Browse files
feat(toolchain): MSVC gets a version axis — msvc@<toolset> alongside msvc@system (#434)
* feat(toolchain): MSVC gets a version axis -- `msvc@<toolset>` alongside `msvc@system` (2026.8.16.1) gcc and llvm are installed by mcpp and resolved from what the manifest declares. MSVC was the one exception: EVERY msvc spec was a system spec, so a manifest could name a toolset and have that name discarded. The consequence is not inelegance. It is that the same source compiles with different compilers on different machines, and nothing says so. Measured on xrgui#3: in ONE CI run mcpp used 14.51 and xmake used 14.52, and it stayed invisible until 14.51 hit an ICE. Exporting a complete vcvars environment did not help -- the only way out was to move `vswhere.exe` aside so mcpp would fall through to VSINSTALLDIR, and that workaround is still in xrgui's workflow. ## The version axis decides the origin msvc@system (or bare msvc) the machine's own Visual Studio -- UNCHANGED msvc@<toolset> an xlings payload mcpp installs and pins `msvc@14.44.35207` is isomorphic to `gcc@16.1.0` in every respect: coexisting versions, `toolchain remove msvc@<toolset>`, auto-install from a manifest. The payload brings the compiler, the STL, and -- through its `xim:windows-sdk` dependency -- the ucrt/um headers and libs, so nothing has to be preinstalled. Structurally, ACQUISITION and RESOLUTION are separated: acquisition is shared with gcc (the xim install), resolution is shared with `msvc@system` (`installation_from_tools_dir`). A pinned toolset is therefore not a second code path, and cannot grow its own bugs. What it does not share is the bin/-shaped frontend lookup (cl.exe is four levels deeper) and the ELF post-install fixup (there is nothing to patchelf on a PE toolchain). BREAKING: `msvc@19.44` was a pin-verify against the system install's cl banner -- checked by `toolchain default` and silently ignored by builds. The version axis now names a toolset everywhere. A `19.x` spelling errors out with this machine's actual cl version and both replacements. ## VSINSTALLDIR now outranks vswhere vswhere returns something on nearly every developer machine, which made VSINSTALLDIR effectively unreachable -- a build that had exported a complete vcvars environment still compiled with whatever vswhere ranked first. A guess must not silently override an answer. vswhere also gains `-prerelease`: without it a machine with only an Insiders VS is reported as "MSVC not found" while a perfectly good cl.exe sits on disk. VS*COMNTOOLS stays BELOW vswhere. Those are machine-wide leftovers -- a 2017 VS150COMNTOOLS must not outrank a current install -- whereas VSINSTALLDIR is someone setting it for this shell. The old `find_vs_via_env()` conflated the two, so promoting it would have promoted the leftovers too. ## The Windows SDK stops being two absolute paths Order: WindowsSdkDir (+ WindowsSdkVersion, both exported by vcvars) -> the `xim:windows-sdk` payload beside a pinned toolset in mcpp's own store -> the hardcoded roots, now a fallback. The second source needs no configuration and hardcodes no version: the COMPILER'S OWN PATH says which store it came from, and the SDK is its neighbour there. `sibling_sdk_roots()` returns empty for a system cl, so the two origins stay separate. ## cxx_runtime = "self-contained" now actually does something on MSVC Two knobs, one working: `linkage = "static"` really emitted /MT while `cxx_runtime = "self-contained"` reported "not implemented" -- for the same physical switch. Two comments contradicted each other about it (flags.cppm:605 vs distribution.cppm:202). On the MSVC ABI these are not alternatives: /MT links the C runtime and the C++ runtime out of the same library. Both spellings now select it through one `msvc_wants_static_crt()`, which the project's TUs and the std module both ask -- rather than each spelling out `linkage == "static"`, which is exactly how they diverged in #422. The default stays /MD: the predicate reads the WRITTEN manifest scalar, not the resolved contract, because most roles default to self-contained and keying off that would flip every Windows build to /MT. The CRT model is a whole-PROJECT property (one std module per project, and cl bakes _MSVC_MT/_MSVC_MD into it), so a per-role override is now refused with a message that says why, instead of failing later inside the ucrt headers. ## Tests MSVC discovery is testable off Windows for the first time: `installation_at()` takes a directory instead of probing, `find_windows_sdk()` takes a list of roots, and neither is behind a platform macro. Six new unit tests drive real fixture trees on Linux CI -- including "both toolsets present, ask for the OLDER one", which a latest-wins implementation fails and a real machine might pass by accident. New e2e `239_msvc_managed_toolset.sh`, written so that the SYSTEM compiler answering would FAIL rather than pass quietly: cl.exe must be inside mcpp's store, the toolset directory must be the one named, and switching the same project back to `msvc@system` must resolve to the system cl again. 83/83 unit targets pass; 95/96/103/183 e2e pass on Linux. Closes #432 * test(e2e): 239 asked for a flag that does not exist `toolchain list --available` is not a thing -- `toolchain list` already prints an "Available toolchains" section. Caught by ci-windows on the first run, which is the right place for it: this test only ever executes there. Also reordered: the install now runs BEFORE the discoverability check, so a runner that cannot reach the index skips cleanly instead of failing an assertion about a list the index would have filled in. And the check became stronger than the one it replaces -- it asserts the INSTALLED toolset shows up, not merely that some msvc row exists, because a toolset that installs and then never appears is indistinguishable from one that did not install. * docs: the cross-repo plan, its one hard dependency, and which claims can self-certify Four repos, five changes, ONE real dependency edge: the packages must be published before mcpp can install them and before xrgui can use them. Everything else is parallel, and stringing it into a line is the usual waste in work shaped like this. The part worth reading is §4: which acceptance criteria a person could satisfy by adjusting a test, and which they could not. The mirror is the example that earned the distinction -- the criterion is not "the upload succeeded" but "the bytes came back with Microsoft's sha256", and that caught a real failure where the tool reported 16 files as failed while the release listing showed them present and they were in fact absent. * fix(toolchain): a managed toolset without its SDK reported success Self-review catch. `installation_at()` succeeding means cl.exe is where the declared version says it should be -- it says nothing about the ucrt/um headers, which arrive as a separate package dependency and can therefore fail on their own. The install printed "Installed", and the build died inside the ucrt headers much later. That is the half-installed state `has_usable_msvc()` was written for; this applies the same judgement to the managed origin, and names the dependency that must have failed rather than leaving the reader to work it out. Also: `msvc_print_detected` now takes the label. "Detected" is a claim about probing the machine, and printing it after unpacking a payload the caller NAMED describes the wrong thing -- quietly, and in exactly the direction this whole change is about. --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent f4b64a1 commit 26b26ec

17 files changed

Lines changed: 1690 additions & 207 deletions

.agents/docs/2026-08-16-msvc-as-a-managed-toolchain.md

Lines changed: 421 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# MSVC 在 xlings 生态里打通 —— 跨仓库计划、依赖与验收(2026-08-16)
2+
3+
> 配套 `2026-08-16-msvc-as-a-managed-toolchain.md`(mcpp 侧设计)。
4+
> 那份讲**为什么这么改**;这份讲**改动落在哪几个仓库、谁挡着谁、以及每一步凭什么算通过**
5+
6+
---
7+
8+
## 0. 一句话
9+
10+
四个仓库、五条改动,只有**一条硬依赖**:包必须先发布,mcpp 才装得到,xrgui 才用得上。
11+
其余都可以并行,而把它们排成一条线是这类工作最常见的浪费。
12+
13+
---
14+
15+
## 1. 从八个角度看这次改的到底是什么
16+
17+
用户点名的八个角度不是修辞,它们各自对应一条具体改动。逐条落到实处:
18+
19+
| 角度 | 改前的具体事实 | 改后 |
20+
|---|---|---|
21+
| **架构** | MSVC 是工具链体系里唯一「无法声明版本」的家族;`is_system_toolchain()`**所有** msvc spec 为真 | 版本轴决定来源。**获取**与 gcc 同路(xim 安装),**解析**`msvc@system` 同路(`installation_from_tools_dir`)——两条轴正交,受管 toolset 不是第二条代码路径 |
22+
| **稳定性** | 同一份源码在两台机器上被不同编译器编译**且不报错**;xrgui#3 实测 mcpp 用 14.51、xmake 用 14.52,直到 ICE 才暴露 | 声明了版本就必须拿到那个版本,拿不到是 nullopt 而不是替代品 |
23+
| **优雅简洁** | 两个旋钮(`linkage` / `cxx_runtime`)指向同一个物理开关,只有一个管用,注释互相矛盾 | 一个 `msvc_wants_static_crt()`,项目 TU 与 std 模块问同一个函数 |
24+
| **用户体验** | 没装 VS 时告诉你「mcpp 不安装 MSVC,自己去装」——而现在这句话是假的 | 两条路都给出:装一个 pin 住的 toolset,或用机器自己的 VS。`install_guidance()` 里两条命令都能直接抄 |
25+
| **兼容性** | —— | `msvc@system` 语义一字未改;唯一的破坏性变更(`msvc@19.44`)有精确的替代指引,而它原本**只在一个命令里生效、构建路径完全忽略** |
26+
| **跨平台** | msvc 发现逻辑一行都无法在 Windows 之外测试(入口全在 `#if defined(_WIN32)` 里) | `installation_at()` 收目录、`find_windows_sdk()` 收 root 列表,6 个单测在 Linux CI 上跑真 fixture |
27+
| **一致性** | `search``xim:msvc` 在,`info``not found` —— 同一台机器、同一个索引 | xlings#550:区分「不存在」与「这个平台没有构建」,并说出它在哪些平台有 |
28+
| **无感升级** | —— | 现有工程零影响:`msvc@system` 不变、默认 CRT 仍是 `/MD`(判据取 manifest 字面值而非解析后的 contract,否则每个 Windows 构建都会翻成 `/MT`) |
29+
30+
---
31+
32+
## 2. 五条改动与它们的仓库
33+
34+
| # | 仓库 | 改动 | PR |
35+
|---|---|---|---|
36+
| **X** | xim-pkgindex | payload 多来源 `urls` + 27 个 payload 镜像;windows-sdk 导出 `WindowsSdkDir`/`WindowsSdkVersion` | #629 ✅ 已合 |
37+
| **M** | mcpp | A 受管 toolset / B SDK 搜索顺序 / C CRT 双入口 / D 发现顺序 | #434 |
38+
| **L1** | xlings | D4 的管道那一半量到了(文档) | #549 |
39+
| **L2** | xlings | `info` 对别的平台的包说 "not found" | #550 |
40+
| **V** | xrgui | 删掉 vswhere workaround,验证整条链 | 待 mcpp 发布 |
41+
42+
---
43+
44+
## 3. 依赖图 —— 边是真实依赖,不是先后偏好
45+
46+
```
47+
X (xim-pkgindex #629)
48+
│ 包必须先发布:`mcpp toolchain install msvc 14.44.35207` 装的就是它
49+
│ 在此之前 mcpp 的 e2e 239 走 SKIP 分支,而不是红
50+
51+
M (mcpp #434) ──→ 发布 2026.8.16.1 ──→ V (xrgui)
52+
53+
L1、L2 与上面这条链 无 依赖 ────────┘(可随时合)
54+
```
55+
56+
**为什么 C、D 没有拆成独立 PR**:C 与 A/B 确实无依赖,但它回答的是同一个问题
57+
——「MSVC 在 mcpp 里到底怎么被描述」;拆开会让 CHANGELOG 的读者以为是两件事。
58+
D 与 A 改同一个函数,拆开的第二个 PR 必然要重写第一个 PR 刚写的注释。
59+
60+
**为什么 L2 不在 M 里**:它是 xlings 的缺陷,是**在验证 M 的过程中**被发现的
61+
(`xlings info msvc` 在 Linux 上说 not found),但它与 MSVC 无关 —— 每一个
62+
windows-only 包在 Linux 上都会这样,反之亦然。
63+
64+
---
65+
66+
## 4. 验收标准 —— 哪些能自证,哪些不能
67+
68+
这一节是这份计划的重点。**能被"调一下测试"满足的判据,不算证据。**
69+
70+
| 判据 | 能否自证 | 说明 |
71+
|---|---|---|
72+
| 27 个 payload 镜像正确 | **不能** | 判据不是「上传成功」,是**下载回来 sha256 与微软一致**。而这恰好抓到了真问题:上传工具对 16 个文件报了失败、release 列表却显示它们在,实际 404。**谁都不能信,只能信字节。** |
73+
| 受管 toolset 真的被用了 | **不能** | e2e 239 的每一条断言都写成「系统编译器来应答就会失败」:cl.exe 必须在 mcpp 的 store 里、toolset 目录必须是 spec 声明的那个 |
74+
| 两条来源互不污染 | **不能** | 同一台机器、同一个项目,spec 换回 `msvc@system` 必须解析到系统 cl。少了这条,「受管能用」与「受管把一切都换掉了」长得一样 |
75+
| 声明的 toolset 优先于"最新" | **不能** | 单测:两个 toolset 都在,要**老的**那个。「取最新」的实现会在这里失败,而真机上它可能碰巧对 |
76+
| **xrgui 删掉 workaround 后仍然绿** | **不能** | 全套里最强的一条。workaround 还在时,「`VSINSTALLDIR` 被采纳」与「vswhere 找不到东西」现象完全一样 —— **无法区分缺陷 A 是否真修好**。而且删掉之后 vswhere 与 14.51 **都还在**:错误答案没有被拿走,它只是必须输 |
77+
| 单测 83/83、静态检查 1788 项 | **** | 有用,但它们证明的是「没有回归」,不是「这件事做成了」 |
78+
79+
---
80+
81+
## 5. 已知不被覆盖的部分(不要当成已完成)
82+
83+
1. **`msvc@14.52.36629` 的安装路径没有在 CI 上跑过**。index 的 `windows-test`
84+
装的是 `latest`(14.44)。两者只差 payload URL 与目录版本,后者已逐个从真实
85+
payload 读出核对,但**没有实际装过一次**。xrgui 的 V3 会第一次覆盖它。
86+
2. **镜像回退没有被真正触发过**。两个前提单独验过了 —— `curl -f` 遇 404 退 22
87+
且不留文件(所以 `pcall` 会接住、`os.isfile` 为假),官方地址仍然服务同样的
88+
字节 —— 但「镜像挂掉时自动走官方」这条完整路径没有被执行过。
89+
现在至少它**不会静默**:走到第一个之后的地址会 `log.warn`
90+
3. **D4 的控制台那一半仍然开着**。管道那条路量到了(xlings#549),
91+
而 CI 结不了控制台的案:runner 上 job 没有附着的控制台。
92+
4. **gitcode 的 probe 资产删不掉**。API 没有删除端点(两个路径都 404),
93+
已在镜像 README 里点名说明,而不是留一堆没人知道是什么的文件。

CHANGELOG.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,96 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.16.1] — 2026-08-16
7+
8+
### 工具链
9+
10+
- **MSVC 不再是「唯一版本无法声明」的工具链。**
11+
12+
gcc / llvm 由 mcpp 自己安装、按声明解析;MSVC 是体系里唯一的例外——**每一个**
13+
msvc spec 都是系统 spec,manifest 写了版本也会被丢掉。后果不是不够优雅,是
14+
**同一份源码在两台机器上会被不同的编译器编译,而且不报错**
15+
16+
xrgui#3 实测:同一轮 CI 里 mcpp 用 14.51、xmake 用 14.52,直到 14.51 触发
17+
ICE 才暴露。导出完整 vcvars 环境**无效**,最后只能在 CI 里把 `vswhere.exe`
18+
挪开,逼 mcpp 落到 `VSINSTALLDIR` 那一步。
19+
20+
现在由 **spec 的版本轴**决定来源,两条来源并存:
21+
22+
| spec | 来源 | 用哪个编译器 |
23+
|---|---|---|
24+
| `msvc@system`(或裸 `msvc`) | 机器自己的 Visual Studio | 这台机器上装的那个 |
25+
| `msvc@<toolset>`(如 `msvc@14.44.35207`) | mcpp 安装的 xlings payload | **声明的那个,每台机器都是** |
26+
27+
`msvc@<toolset>``gcc@16.1.0` 在每个方面都同构:多版本共存、
28+
`toolchain remove msvc@<toolset>` 可卸载、manifest 里写了就自动安装。
29+
payload 自带编译器、STL,并通过 `xim:windows-sdk` 依赖带上 ucrt/um 头与库,
30+
机器上**什么都不必预装**
31+
32+
实现上,「获取」与「解析」被拆成两条正交的轴:获取与 gcc 共用一条
33+
(xim 安装),解析与 `msvc@system` 共用一条(`installation_from_tools_dir`)。
34+
所以受管 toolset 不是第二条代码路径,也就不会长出自己的 bug。
35+
36+
- **⚠️ 破坏性变更:`msvc@19.44` 不再是 pin-verify。**
37+
38+
它过去表示「用系统 MSVC,并校验 banner 前缀」——而且只有
39+
`mcpp toolchain default` 会校验,**构建路径完全忽略它**。版本轴现在到处都表示
40+
toolset。写成 `19.x` 时,mcpp 会用这台机器自己的 cl 版本说清楚,并给出两个
41+
替代写法(`msvc@system` 或该机器实际的 toolset 版本)。
42+
43+
- **`VSINSTALLDIR` 现在优先于 vswhere 探测。**
44+
45+
vswhere 在几乎每台开发机上都能返回点什么,于是 `VSINSTALLDIR` 事实上不可达:
46+
一次已经导出了完整 vcvars 环境的构建,仍然用 vswhere 排第一的那个编译器。
47+
**猜测不该压过答案。** 顺带给 vswhere 加了 `-prerelease`——没有它,只装了
48+
Insiders 的机器会被报告成「没有 MSVC」,而磁盘上明明有一个可用的 cl.exe。
49+
50+
`VS*COMNTOOLS` 仍排在 vswhere **之后**:那是机器全局的残留(2017 的
51+
`VS150COMNTOOLS` 不该压过当前安装),而 `VSINSTALLDIR` 是有人为这个 shell
52+
设的。
53+
54+
- **Windows SDK 不再只认两个写死的绝对路径。**
55+
56+
顺序改为:`WindowsSdkDir`(+ `WindowsSdkVersion`,vcvars 本来就导出这两个)
57+
→ 受管 toolset 在 mcpp 自己 store 里的 `xim:windows-sdk` payload
58+
→ 原来的绝对路径(降为回退)。
59+
60+
第二条不需要任何配置:**编译器自己的路径就说明了它来自哪个 store**,
61+
SDK 是它在那里的邻居。所以 mcpp 里没有任何地方写死 SDK 版本。
62+
63+
### 接口一致性
64+
65+
- **`cxx_runtime = "self-contained"` 在 MSVC 上真的生效了。**
66+
67+
过去两个旋钮只有一个管用:`linkage = "static"` **确实发** `/MT`,而
68+
`cxx_runtime = "self-contained"` 报「未实现」——对着同一个物理开关。
69+
两处注释也互相矛盾(`flags.cppm:605` 说发了 `/MT`,`distribution.cppm:202`
70+
说「根本没有 /MT」)。
71+
72+
在 MSVC ABI 上这两条不是可以二选一的旋钮:`/MT` 把 C 运行时和 C++ 运行时
73+
从同一个库里链进来,**它们本来就是一个开关**。现在两种写法都选中它,由
74+
`msvc_wants_static_crt()` 统一推导——项目的 TU 与 std 模块问的是同一个函数,
75+
不再各写各的表达式(#422 正是这样分叉的)。
76+
77+
默认仍是 `/MD`:判据取的是**manifest 里写下的字面值**,不是解析后的
78+
contract——后者对多数 role 默认就是 self-contained,拿它做判据会把每一个
79+
Windows 构建都翻成 `/MT`
80+
81+
MSVC 的 CRT 模型是**整个项目**的属性(一个项目只编一份 std 模块,cl 把
82+
`_MSVC_MT`/`_MSVC_MD` 烤进去),所以按 role 覆盖会被明确拒绝并说明原因,
83+
而不是在 ucrt 头文件里炸出 C5050/C2375。
84+
85+
### 测试
86+
87+
- msvc 的发现逻辑第一次可以在 Windows 之外测试:`installation_at()` 接受目录
88+
而不是去探测机器,`find_windows_sdk()` 接受 root 列表。6 个新单测在 Linux CI
89+
上跑真实的 fixture 目录树,包括「两个 toolset 都在,要老的那个」这条——
90+
「取最新」的实现会在这里失败。
91+
- 新增 e2e `239_msvc_managed_toolset.sh`。它的每一条断言都写成
92+
**系统编译器来应答就会失败**:cl.exe 必须在 mcpp 的 store 里、toolset 目录
93+
必须是 spec 声明的那个、同一台机器上换回 `msvc@system` 必须仍然解析到系统
94+
的 cl(两条来源互不污染)。
95+
696
## [2026.8.13.1] — 2026-08-13
797

898
### 性能

docs/03-toolchains.md

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,40 @@ the current host can actually install, so if a target is missing from the
213213
Targets block, that host genuinely cannot serve it (implemented by
214214
`toolchain::host_can_serve`).
215215

216-
## MSVC (System Toolchain, Windows)
216+
## MSVC (Windows)
217217

218-
MSVC is different from every other toolchain mcpp manages: it is a **system
219-
toolchain**. mcpp locates and identifies an installed Visual Studio / Build
220-
Tools — it never installs, updates, or removes MSVC itself.
218+
An MSVC toolset reaches a build one of two ways, and the **version axis of the
219+
spec** says which:
220+
221+
| Spec | Origin | Which compiler you get |
222+
|---|---|---|
223+
| `msvc@system` (or bare `msvc`) | the machine's own Visual Studio | whatever is installed here |
224+
| `msvc@<toolset>` (e.g. `msvc@14.44.35207`) | an xlings payload mcpp installs | the one you named, on every machine |
225+
226+
They are not alternatives to pick between once — they answer different
227+
questions. `msvc@system` asks *"use what this developer already has"*;
228+
`msvc@14.44.35207` asks *"build this project with exactly this compiler"*.
229+
Pinned toolsets coexist with each other and with a system Visual Studio.
230+
231+
### `msvc@system` — the machine's own Visual Studio
232+
233+
mcpp locates and identifies an installed Visual Studio / Build Tools; it never
234+
installs, updates, or removes one.
221235

222236
```bash
223237
mcpp toolchain default msvc
224238
```
225239

226-
On a machine with MSVC installed, mcpp auto-locates it (via `vswhere.exe`,
227-
then `VSINSTALLDIR`/`VS*COMNTOOLS`, then the standard install paths),
228-
identifies the versions involved, and persists the stable spec `msvc@system`:
240+
mcpp auto-locates it in this order:
241+
242+
1. **`VSINSTALLDIR`** — set by a developer command prompt or by a CI step that
243+
ran `vcvarsall`. A declared answer, so it outranks the probes below.
244+
2. `vswhere.exe` (including prerelease/Insiders instances)
245+
3. `VS*COMNTOOLS`
246+
4. the standard `Program Files\Microsoft Visual Studio\<year>\<edition>` paths
247+
248+
It then identifies the versions involved and persists the stable spec
249+
`msvc@system`:
229250

230251
```
231252
Detected msvc 19.44.35211 (VS 2022 BuildTools) (VC tools 14.44.35207)
@@ -234,34 +255,75 @@ Detected msvc 19.44.35211 (VS 2022 BuildTools) (VC tools 14.44.35207)
234255
Default set to msvc@system (was: llvm@20.1.7)
235256
```
236257

237-
If MSVC is **not** installed, mcpp prints installation guidance instead
238-
(Visual Studio Installer with the *Desktop development with C++* workload, or
239-
`winget install Microsoft.VisualStudio.2022.BuildTools`) and exits non-zero —
240-
install it yourself, then re-run the command.
258+
If no Visual Studio is installed, mcpp says so and offers both routes — a
259+
pinned toolset it can install for you, or the Visual Studio Installer /
260+
`winget install Microsoft.VisualStudio.2022.BuildTools`.
241261

242262
`mcpp toolchain list` shows the detected MSVC in a separate `System:` section,
243-
and `mcpp self doctor` reports its status on Windows. In a manifest you can
244-
pin it per-platform:
263+
and `mcpp self doctor` reports its status on Windows. In a manifest:
245264

246265
```toml
247266
[toolchain]
248267
windows = "msvc@system"
249268
```
250269

251-
`msvc@<prefix>` (e.g. `msvc@19.44`) acts as a pin-verify: mcpp still uses the
252-
newest installed VC tools, but errors if the detected version doesn't match
253-
the prefix.
254-
255-
Since 0.0.90, **native cl.exe builds work**: mcpp synthesizes the
256-
INCLUDE/LIB environment from the detected VC tools + Windows SDK (no
257-
`vcvarsall` involved), stages `std.ixx`/`std.compat.ixx` as `.ifc` BMIs,
258-
compiles `.cppm` module units via `/interface /TP /ifcOutput`, scans with
259-
`/scanDependencies`, and links with `link.exe`/`lib.exe` through response
260-
files. `[target.x86_64-windows-msvc] linkage = "static"` (or `mcpp build
261-
--static`) selects the `/MT` CRT — not `[build] linkage`, which is not a key.
262-
A missing Windows
263-
SDK fails the build with installation guidance (`mcpp self doctor` reports
264-
SDK status).
270+
### `msvc@<toolset>` — a toolset mcpp installs and pins
271+
272+
```bash
273+
mcpp toolchain list --available msvc # what can be pinned
274+
mcpp toolchain install msvc 14.44.35207
275+
```
276+
277+
This works like `gcc@16.1.0` in every respect: the payload is downloaded into
278+
mcpp's own store, several toolsets coexist, `mcpp toolchain remove
279+
msvc@<toolset>` uninstalls one, and a manifest that names one gets it
280+
installed automatically on first build.
281+
282+
```toml
283+
[toolchain]
284+
windows = "msvc@14.44.35207"
285+
```
286+
287+
**The version is the toolset directory name** (`14.44.35207` — what
288+
`VC\Tools\MSVC\` is named and what `-vcvars_ver` takes), *not* the cl banner
289+
version (`19.44.35211`) and not the product year. Nothing needs to be
290+
installed on the machine: the payload brings the compiler, the STL, and — via
291+
its `xim:windows-sdk` dependency — the ucrt/um headers and libraries.
292+
293+
> **Changed:** `msvc@19.44` used to mean "use the system MSVC and verify its
294+
> banner starts with 19.44", which was checked by `mcpp toolchain default` and
295+
> silently ignored by builds. The version axis now names a toolset everywhere.
296+
> A `19.x` spelling gets an error naming both replacements — `msvc@system` or
297+
> the toolset version that machine actually has.
298+
299+
### Native cl.exe builds
300+
301+
Since 0.0.90 these work on both origins: mcpp synthesizes the INCLUDE/LIB
302+
environment from the VC tools + Windows SDK (no `vcvarsall` involved), stages
303+
`std.ixx`/`std.compat.ixx` as `.ifc` BMIs, compiles `.cppm` module units via
304+
`/interface /TP /ifcOutput`, scans with `/scanDependencies`, and links with
305+
`link.exe`/`lib.exe` through response files.
306+
307+
The Windows SDK is located in this order: **`WindowsSdkDir`** (+
308+
`WindowsSdkVersion`) if declared, then the `xim:windows-sdk` payload beside a
309+
pinned toolset in mcpp's store, then `C:\Program Files (x86)\Windows Kits\10`.
310+
A missing SDK fails the build with guidance (`mcpp self doctor` reports SDK
311+
status).
312+
313+
**CRT model.** `/MD` (host-coupled) by default; `/MT` when either
314+
315+
```toml
316+
[build]
317+
linkage = "static" # the libc axis
318+
cxx_runtime = "self-contained" # the C++ runtime axis
319+
```
320+
321+
is written down. On the MSVC ABI these are one physical switch — `/MT` links
322+
the C and C++ runtimes out of the same library — so both spellings select it
323+
and mean the same thing. It is a **whole-project** property: one `std` module
324+
is built per project and cl bakes `_MSVC_MT`/`_MSVC_MD` into it, so a
325+
per-role override (`cxx_runtime = { tests = … }`) is refused with a message
326+
saying so rather than producing a module mismatch inside the ucrt headers.
265327

266328
## Project-Level Version Pinning
267329

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "2026.8.15.3"
3+
version = "2026.8.16.1"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

0 commit comments

Comments
 (0)