Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
421 changes: 421 additions & 0 deletions .agents/docs/2026-08-16-msvc-as-a-managed-toolchain.md

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions .agents/docs/2026-08-16-msvc-ecosystem-cross-repo-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# MSVC 在 xlings 生态里打通 —— 跨仓库计划、依赖与验收(2026-08-16)

> 配套 `2026-08-16-msvc-as-a-managed-toolchain.md`(mcpp 侧设计)。
> 那份讲**为什么这么改**;这份讲**改动落在哪几个仓库、谁挡着谁、以及每一步凭什么算通过**。

---

## 0. 一句话

四个仓库、五条改动,只有**一条硬依赖**:包必须先发布,mcpp 才装得到,xrgui 才用得上。
其余都可以并行,而把它们排成一条线是这类工作最常见的浪费。

---

## 1. 从八个角度看这次改的到底是什么

用户点名的八个角度不是修辞,它们各自对应一条具体改动。逐条落到实处:

| 角度 | 改前的具体事实 | 改后 |
|---|---|---|
| **架构** | MSVC 是工具链体系里唯一「无法声明版本」的家族;`is_system_toolchain()` 对**所有** msvc spec 为真 | 版本轴决定来源。**获取**与 gcc 同路(xim 安装),**解析**与 `msvc@system` 同路(`installation_from_tools_dir`)——两条轴正交,受管 toolset 不是第二条代码路径 |
| **稳定性** | 同一份源码在两台机器上被不同编译器编译**且不报错**;xrgui#3 实测 mcpp 用 14.51、xmake 用 14.52,直到 ICE 才暴露 | 声明了版本就必须拿到那个版本,拿不到是 nullopt 而不是替代品 |
| **优雅简洁** | 两个旋钮(`linkage` / `cxx_runtime`)指向同一个物理开关,只有一个管用,注释互相矛盾 | 一个 `msvc_wants_static_crt()`,项目 TU 与 std 模块问同一个函数 |
| **用户体验** | 没装 VS 时告诉你「mcpp 不安装 MSVC,自己去装」——而现在这句话是假的 | 两条路都给出:装一个 pin 住的 toolset,或用机器自己的 VS。`install_guidance()` 里两条命令都能直接抄 |
| **兼容性** | —— | `msvc@system` 语义一字未改;唯一的破坏性变更(`msvc@19.44`)有精确的替代指引,而它原本**只在一个命令里生效、构建路径完全忽略** |
| **跨平台** | msvc 发现逻辑一行都无法在 Windows 之外测试(入口全在 `#if defined(_WIN32)` 里) | `installation_at()` 收目录、`find_windows_sdk()` 收 root 列表,6 个单测在 Linux CI 上跑真 fixture |
| **一致性** | `search` 说 `xim:msvc` 在,`info` 说 `not found` —— 同一台机器、同一个索引 | xlings#550:区分「不存在」与「这个平台没有构建」,并说出它在哪些平台有 |
| **无感升级** | —— | 现有工程零影响:`msvc@system` 不变、默认 CRT 仍是 `/MD`(判据取 manifest 字面值而非解析后的 contract,否则每个 Windows 构建都会翻成 `/MT`) |

---

## 2. 五条改动与它们的仓库

| # | 仓库 | 改动 | PR |
|---|---|---|---|
| **X** | xim-pkgindex | payload 多来源 `urls` + 27 个 payload 镜像;windows-sdk 导出 `WindowsSdkDir`/`WindowsSdkVersion` | #629 ✅ 已合 |
| **M** | mcpp | A 受管 toolset / B SDK 搜索顺序 / C CRT 双入口 / D 发现顺序 | #434 |
| **L1** | xlings | D4 的管道那一半量到了(文档) | #549 |
| **L2** | xlings | `info` 对别的平台的包说 "not found" | #550 |
| **V** | xrgui | 删掉 vswhere workaround,验证整条链 | 待 mcpp 发布 |

---

## 3. 依赖图 —— 边是真实依赖,不是先后偏好

```
X (xim-pkgindex #629)
│ 包必须先发布:`mcpp toolchain install msvc 14.44.35207` 装的就是它
│ 在此之前 mcpp 的 e2e 239 走 SKIP 分支,而不是红
M (mcpp #434) ──→ 发布 2026.8.16.1 ──→ V (xrgui)
L1、L2 与上面这条链 无 依赖 ────────┘(可随时合)
```

**为什么 C、D 没有拆成独立 PR**:C 与 A/B 确实无依赖,但它回答的是同一个问题
——「MSVC 在 mcpp 里到底怎么被描述」;拆开会让 CHANGELOG 的读者以为是两件事。
D 与 A 改同一个函数,拆开的第二个 PR 必然要重写第一个 PR 刚写的注释。

**为什么 L2 不在 M 里**:它是 xlings 的缺陷,是**在验证 M 的过程中**被发现的
(`xlings info msvc` 在 Linux 上说 not found),但它与 MSVC 无关 —— 每一个
windows-only 包在 Linux 上都会这样,反之亦然。

---

## 4. 验收标准 —— 哪些能自证,哪些不能

这一节是这份计划的重点。**能被"调一下测试"满足的判据,不算证据。**

| 判据 | 能否自证 | 说明 |
|---|---|---|
| 27 个 payload 镜像正确 | **不能** | 判据不是「上传成功」,是**下载回来 sha256 与微软一致**。而这恰好抓到了真问题:上传工具对 16 个文件报了失败、release 列表却显示它们在,实际 404。**谁都不能信,只能信字节。** |
| 受管 toolset 真的被用了 | **不能** | e2e 239 的每一条断言都写成「系统编译器来应答就会失败」:cl.exe 必须在 mcpp 的 store 里、toolset 目录必须是 spec 声明的那个 |
| 两条来源互不污染 | **不能** | 同一台机器、同一个项目,spec 换回 `msvc@system` 必须解析到系统 cl。少了这条,「受管能用」与「受管把一切都换掉了」长得一样 |
| 声明的 toolset 优先于"最新" | **不能** | 单测:两个 toolset 都在,要**老的**那个。「取最新」的实现会在这里失败,而真机上它可能碰巧对 |
| **xrgui 删掉 workaround 后仍然绿** | **不能** | 全套里最强的一条。workaround 还在时,「`VSINSTALLDIR` 被采纳」与「vswhere 找不到东西」现象完全一样 —— **无法区分缺陷 A 是否真修好**。而且删掉之后 vswhere 与 14.51 **都还在**:错误答案没有被拿走,它只是必须输 |
| 单测 83/83、静态检查 1788 项 | **能** | 有用,但它们证明的是「没有回归」,不是「这件事做成了」 |

---

## 5. 已知不被覆盖的部分(不要当成已完成)

1. **`msvc@14.52.36629` 的安装路径没有在 CI 上跑过**。index 的 `windows-test`
装的是 `latest`(14.44)。两者只差 payload URL 与目录版本,后者已逐个从真实
payload 读出核对,但**没有实际装过一次**。xrgui 的 V3 会第一次覆盖它。
2. **镜像回退没有被真正触发过**。两个前提单独验过了 —— `curl -f` 遇 404 退 22
且不留文件(所以 `pcall` 会接住、`os.isfile` 为假),官方地址仍然服务同样的
字节 —— 但「镜像挂掉时自动走官方」这条完整路径没有被执行过。
现在至少它**不会静默**:走到第一个之后的地址会 `log.warn`。
3. **D4 的控制台那一半仍然开着**。管道那条路量到了(xlings#549),
而 CI 结不了控制台的案:runner 上 job 没有附着的控制台。
4. **gitcode 的 probe 资产删不掉**。API 没有删除端点(两个路径都 404),
已在镜像 README 里点名说明,而不是留一堆没人知道是什么的文件。
90 changes: 90 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,96 @@
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [2026.8.16.1] — 2026-08-16

### 工具链

- **MSVC 不再是「唯一版本无法声明」的工具链。**

gcc / llvm 由 mcpp 自己安装、按声明解析;MSVC 是体系里唯一的例外——**每一个**
msvc spec 都是系统 spec,manifest 写了版本也会被丢掉。后果不是不够优雅,是
**同一份源码在两台机器上会被不同的编译器编译,而且不报错**。

xrgui#3 实测:同一轮 CI 里 mcpp 用 14.51、xmake 用 14.52,直到 14.51 触发
ICE 才暴露。导出完整 vcvars 环境**无效**,最后只能在 CI 里把 `vswhere.exe`
挪开,逼 mcpp 落到 `VSINSTALLDIR` 那一步。

现在由 **spec 的版本轴**决定来源,两条来源并存:

| spec | 来源 | 用哪个编译器 |
|---|---|---|
| `msvc@system`(或裸 `msvc`) | 机器自己的 Visual Studio | 这台机器上装的那个 |
| `msvc@<toolset>`(如 `msvc@14.44.35207`) | mcpp 安装的 xlings payload | **声明的那个,每台机器都是** |

`msvc@<toolset>` 与 `gcc@16.1.0` 在每个方面都同构:多版本共存、
`toolchain remove msvc@<toolset>` 可卸载、manifest 里写了就自动安装。
payload 自带编译器、STL,并通过 `xim:windows-sdk` 依赖带上 ucrt/um 头与库,
机器上**什么都不必预装**。

实现上,「获取」与「解析」被拆成两条正交的轴:获取与 gcc 共用一条
(xim 安装),解析与 `msvc@system` 共用一条(`installation_from_tools_dir`)。
所以受管 toolset 不是第二条代码路径,也就不会长出自己的 bug。

- **⚠️ 破坏性变更:`msvc@19.44` 不再是 pin-verify。**

它过去表示「用系统 MSVC,并校验 banner 前缀」——而且只有
`mcpp toolchain default` 会校验,**构建路径完全忽略它**。版本轴现在到处都表示
toolset。写成 `19.x` 时,mcpp 会用这台机器自己的 cl 版本说清楚,并给出两个
替代写法(`msvc@system` 或该机器实际的 toolset 版本)。

- **`VSINSTALLDIR` 现在优先于 vswhere 探测。**

vswhere 在几乎每台开发机上都能返回点什么,于是 `VSINSTALLDIR` 事实上不可达:
一次已经导出了完整 vcvars 环境的构建,仍然用 vswhere 排第一的那个编译器。
**猜测不该压过答案。** 顺带给 vswhere 加了 `-prerelease`——没有它,只装了
Insiders 的机器会被报告成「没有 MSVC」,而磁盘上明明有一个可用的 cl.exe。

`VS*COMNTOOLS` 仍排在 vswhere **之后**:那是机器全局的残留(2017 的
`VS150COMNTOOLS` 不该压过当前安装),而 `VSINSTALLDIR` 是有人为这个 shell
设的。

- **Windows SDK 不再只认两个写死的绝对路径。**

顺序改为:`WindowsSdkDir`(+ `WindowsSdkVersion`,vcvars 本来就导出这两个)
→ 受管 toolset 在 mcpp 自己 store 里的 `xim:windows-sdk` payload
→ 原来的绝对路径(降为回退)。

第二条不需要任何配置:**编译器自己的路径就说明了它来自哪个 store**,
SDK 是它在那里的邻居。所以 mcpp 里没有任何地方写死 SDK 版本。

### 接口一致性

- **`cxx_runtime = "self-contained"` 在 MSVC 上真的生效了。**

过去两个旋钮只有一个管用:`linkage = "static"` **确实发** `/MT`,而
`cxx_runtime = "self-contained"` 报「未实现」——对着同一个物理开关。
两处注释也互相矛盾(`flags.cppm:605` 说发了 `/MT`,`distribution.cppm:202`
说「根本没有 /MT」)。

在 MSVC ABI 上这两条不是可以二选一的旋钮:`/MT` 把 C 运行时和 C++ 运行时
从同一个库里链进来,**它们本来就是一个开关**。现在两种写法都选中它,由
`msvc_wants_static_crt()` 统一推导——项目的 TU 与 std 模块问的是同一个函数,
不再各写各的表达式(#422 正是这样分叉的)。

默认仍是 `/MD`:判据取的是**manifest 里写下的字面值**,不是解析后的
contract——后者对多数 role 默认就是 self-contained,拿它做判据会把每一个
Windows 构建都翻成 `/MT`。

MSVC 的 CRT 模型是**整个项目**的属性(一个项目只编一份 std 模块,cl 把
`_MSVC_MT`/`_MSVC_MD` 烤进去),所以按 role 覆盖会被明确拒绝并说明原因,
而不是在 ucrt 头文件里炸出 C5050/C2375。

### 测试

- msvc 的发现逻辑第一次可以在 Windows 之外测试:`installation_at()` 接受目录
而不是去探测机器,`find_windows_sdk()` 接受 root 列表。6 个新单测在 Linux CI
上跑真实的 fixture 目录树,包括「两个 toolset 都在,要老的那个」这条——
「取最新」的实现会在这里失败。
- 新增 e2e `239_msvc_managed_toolset.sh`。它的每一条断言都写成
**系统编译器来应答就会失败**:cl.exe 必须在 mcpp 的 store 里、toolset 目录
必须是 spec 声明的那个、同一台机器上换回 `msvc@system` 必须仍然解析到系统
的 cl(两条来源互不污染)。

## [2026.8.13.1] — 2026-08-13

### 性能
Expand Down
116 changes: 89 additions & 27 deletions docs/03-toolchains.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,19 +213,40 @@ the current host can actually install, so if a target is missing from the
Targets block, that host genuinely cannot serve it (implemented by
`toolchain::host_can_serve`).

## MSVC (System Toolchain, Windows)
## MSVC (Windows)

MSVC is different from every other toolchain mcpp manages: it is a **system
toolchain**. mcpp locates and identifies an installed Visual Studio / Build
Tools — it never installs, updates, or removes MSVC itself.
An MSVC toolset reaches a build one of two ways, and the **version axis of the
spec** says which:

| Spec | Origin | Which compiler you get |
|---|---|---|
| `msvc@system` (or bare `msvc`) | the machine's own Visual Studio | whatever is installed here |
| `msvc@<toolset>` (e.g. `msvc@14.44.35207`) | an xlings payload mcpp installs | the one you named, on every machine |

They are not alternatives to pick between once — they answer different
questions. `msvc@system` asks *"use what this developer already has"*;
`msvc@14.44.35207` asks *"build this project with exactly this compiler"*.
Pinned toolsets coexist with each other and with a system Visual Studio.

### `msvc@system` — the machine's own Visual Studio

mcpp locates and identifies an installed Visual Studio / Build Tools; it never
installs, updates, or removes one.

```bash
mcpp toolchain default msvc
```

On a machine with MSVC installed, mcpp auto-locates it (via `vswhere.exe`,
then `VSINSTALLDIR`/`VS*COMNTOOLS`, then the standard install paths),
identifies the versions involved, and persists the stable spec `msvc@system`:
mcpp auto-locates it in this order:

1. **`VSINSTALLDIR`** — set by a developer command prompt or by a CI step that
ran `vcvarsall`. A declared answer, so it outranks the probes below.
2. `vswhere.exe` (including prerelease/Insiders instances)
3. `VS*COMNTOOLS`
4. the standard `Program Files\Microsoft Visual Studio\<year>\<edition>` paths

It then identifies the versions involved and persists the stable spec
`msvc@system`:

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

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

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

```toml
[toolchain]
windows = "msvc@system"
```

`msvc@<prefix>` (e.g. `msvc@19.44`) acts as a pin-verify: mcpp still uses the
newest installed VC tools, but errors if the detected version doesn't match
the prefix.

Since 0.0.90, **native cl.exe builds work**: mcpp synthesizes the
INCLUDE/LIB environment from the detected VC tools + Windows SDK (no
`vcvarsall` involved), stages `std.ixx`/`std.compat.ixx` as `.ifc` BMIs,
compiles `.cppm` module units via `/interface /TP /ifcOutput`, scans with
`/scanDependencies`, and links with `link.exe`/`lib.exe` through response
files. `[target.x86_64-windows-msvc] linkage = "static"` (or `mcpp build
--static`) selects the `/MT` CRT — not `[build] linkage`, which is not a key.
A missing Windows
SDK fails the build with installation guidance (`mcpp self doctor` reports
SDK status).
### `msvc@<toolset>` — a toolset mcpp installs and pins

```bash
mcpp toolchain list --available msvc # what can be pinned
mcpp toolchain install msvc 14.44.35207
```

This works like `gcc@16.1.0` in every respect: the payload is downloaded into
mcpp's own store, several toolsets coexist, `mcpp toolchain remove
msvc@<toolset>` uninstalls one, and a manifest that names one gets it
installed automatically on first build.

```toml
[toolchain]
windows = "msvc@14.44.35207"
```

**The version is the toolset directory name** (`14.44.35207` — what
`VC\Tools\MSVC\` is named and what `-vcvars_ver` takes), *not* the cl banner
version (`19.44.35211`) and not the product year. Nothing needs to be
installed on the machine: the payload brings the compiler, the STL, and — via
its `xim:windows-sdk` dependency — the ucrt/um headers and libraries.

> **Changed:** `msvc@19.44` used to mean "use the system MSVC and verify its
> banner starts with 19.44", which was checked by `mcpp toolchain default` and
> silently ignored by builds. The version axis now names a toolset everywhere.
> A `19.x` spelling gets an error naming both replacements — `msvc@system` or
> the toolset version that machine actually has.

### Native cl.exe builds

Since 0.0.90 these work on both origins: mcpp synthesizes the INCLUDE/LIB
environment from the VC tools + Windows SDK (no `vcvarsall` involved), stages
`std.ixx`/`std.compat.ixx` as `.ifc` BMIs, compiles `.cppm` module units via
`/interface /TP /ifcOutput`, scans with `/scanDependencies`, and links with
`link.exe`/`lib.exe` through response files.

The Windows SDK is located in this order: **`WindowsSdkDir`** (+
`WindowsSdkVersion`) if declared, then the `xim:windows-sdk` payload beside a
pinned toolset in mcpp's store, then `C:\Program Files (x86)\Windows Kits\10`.
A missing SDK fails the build with guidance (`mcpp self doctor` reports SDK
status).

**CRT model.** `/MD` (host-coupled) by default; `/MT` when either

```toml
[build]
linkage = "static" # the libc axis
cxx_runtime = "self-contained" # the C++ runtime axis
```

is written down. On the MSVC ABI these are one physical switch — `/MT` links
the C and C++ runtimes out of the same library — so both spellings select it
and mean the same thing. It is a **whole-project** property: one `std` module
is built per project and cl bakes `_MSVC_MT`/`_MSVC_MD` into it, so a
per-role override (`cxx_runtime = { tests = … }`) is refused with a message
saying so rather than producing a module mismatch inside the ucrt headers.

## Project-Level Version Pinning

Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mcpp"
version = "2026.8.15.3"
version = "2026.8.16.1"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
Loading
Loading