|
| 1 | +# MSVC System-Toolchain Detection — Design |
| 2 | + |
| 3 | +Date: 2026-07-13 |
| 4 | +Status: approved for implementation (target release: 0.0.88) |
| 5 | + |
| 6 | +## 1. Problem & scope |
| 7 | + |
| 8 | +mcpp on Windows today builds exclusively with MSVC-ABI Clang (`llvm@20.1.7`), |
| 9 | +which *silently borrows* the MSVC STL (`std.ixx`) and Windows SDK from an |
| 10 | +installed Visual Studio via `mcpp.toolchain.msvc` discovery. But MSVC itself |
| 11 | +is not a selectable toolchain: `mcpp toolchain default msvc` falls into the |
| 12 | +xim-package path and fails with a confusing index error, and a machine |
| 13 | +*without* VS fails deep inside the Clang std-module build with no actionable |
| 14 | +message. |
| 15 | + |
| 16 | +**Goal of this change (detection-first):** make MSVC a first-class *system* |
| 17 | +toolchain that a developer can switch to / configure, where mcpp |
| 18 | + |
| 19 | +1. checks whether the system has a usable MSVC installation, |
| 20 | +2. auto-locates it (vswhere → env vars → well-known paths) and identifies its |
| 21 | + version (VS product, VC tools version, `cl.exe` compiler version), |
| 22 | +3. and, when MSVC is absent, prints clear installation guidance — |
| 23 | + **mcpp never installs MSVC itself.** |
| 24 | + |
| 25 | +**Explicit non-goal (phase 2, separate PR):** building with `cl.exe` |
| 26 | +(`/std:c++23`, `.ifc` modules, `link.exe`/`lib.exe`, `/scanDependencies`). |
| 27 | +Selecting MSVC and then running `mcpp build` must fail with a precise |
| 28 | +"native cl.exe builds not yet supported" message that names the detected |
| 29 | +version and points at `llvm@20.1.7` as the working alternative — never with |
| 30 | +an incidental downstream error. |
| 31 | + |
| 32 | +## 2. Existing scaffold (what we build on) |
| 33 | + |
| 34 | +| Layer | Symbol | Status | |
| 35 | +|---|---|---| |
| 36 | +| model | `CompilerId::MSVC`, `is_msvc_target()` (`src/toolchain/model.cppm`) | exists | |
| 37 | +| discovery | `mcpp.toolchain.msvc`: `find_vs_install_path()` (vswhere/env/paths), `find_msvc_tools_dir()`, `find_cl()`, `find_std_module_source()` (`src/toolchain/msvc.cppm`) | exists, used only by clang.cppm for std.ixx | |
| 38 | +| capabilities | `capabilities_for()` MSVC case: `msvc-stl` / `lib.exe` (`src/toolchain/provider.cppm`) | stub exists | |
| 39 | +| abi | msvcrt / msvc-stl / msvc mappings (`src/toolchain/abi.cppm`) | exists | |
| 40 | +| spec | `frontend_candidates_for("msvc") → {"cl.exe"}` (`src/toolchain/registry.cppm`) | exists | |
| 41 | +| manifest | `[toolchain] windows = "msvc@system"` documented in `src/manifest/types.cppm` comment | schema ready, unimplemented | |
| 42 | +| escape hatch | `tcSpec == "system"` skips xim resolution (`src/build/prepare.cppm:685`) | precedent for system toolchains | |
| 43 | + |
| 44 | +Missing: `detect()` cannot classify `cl.exe`; no cl version identification; |
| 45 | +no lifecycle (`default`/`list`/`install`/`remove`) branches; no |
| 46 | +prepare-time resolution of `msvc@system`; no doctor reporting; no |
| 47 | +absent-MSVC guidance anywhere. |
| 48 | + |
| 49 | +## 3. Design |
| 50 | + |
| 51 | +### 3.1 The `msvc@system` spec |
| 52 | + |
| 53 | +MSVC is a **system toolchain**: mcpp locates it, never installs/removes it. |
| 54 | +Canonical spec string is `msvc@system`. Accepted user inputs (CLI and |
| 55 | +manifest) and their normalization: |
| 56 | + |
| 57 | +- `msvc`, `msvc@system` → `msvc@system` |
| 58 | +- `msvc@<ver-prefix>` (e.g. `msvc@19.44`) → detect, then require the detected |
| 59 | + `cl.exe` version to start with `<ver-prefix>`; mismatch is an error that |
| 60 | + prints the detected version. (Pin-verify, not select-among-many: we always |
| 61 | + use the newest VC tools of the newest VS, same policy as vswhere `-latest`.) |
| 62 | + |
| 63 | +The persisted global default (`~/.mcpp/config.toml [toolchain] default`) and |
| 64 | +the manifest value are always the *stable* form `msvc@system` — never a |
| 65 | +concrete version — so config survives VS updates. Concrete versions are |
| 66 | +displayed, not stored. |
| 67 | + |
| 68 | +Registry additions (`registry.cppm`): |
| 69 | + |
| 70 | +- `bool is_system_toolchain(const ToolchainSpec&)` — true for `msvc` today |
| 71 | + (the concept is deliberately general; `system` (PATH compiler) stays as-is). |
| 72 | +- `parse_toolchain_spec` unchanged; msvc branching happens in callers via |
| 73 | + `is_system_toolchain` (keeps parse pure). |
| 74 | + |
| 75 | +### 3.2 Discovery + identification (`msvc.cppm` extension) |
| 76 | + |
| 77 | +New exported surface: |
| 78 | + |
| 79 | +```cpp |
| 80 | +struct MsvcInstallation { |
| 81 | + std::filesystem::path vsRoot; // C:\Program Files\Microsoft Visual Studio\2022\BuildTools |
| 82 | + std::string vsProduct; // "2022 BuildTools" (derived from path segments) |
| 83 | + std::string toolsVersion; // "14.44.35207" (VC\Tools\MSVC\<dir>) |
| 84 | + std::filesystem::path clPath; // ...\bin\Hostx64\x64\cl.exe |
| 85 | + std::string clVersion; // "19.44.35211" (banner; falls back to toolsVersion-derived) |
| 86 | + std::string arch; // "x64" | "x86" | "arm64" (host-native target dir) |
| 87 | + bool hasStdModules; // modules\std.ixx present |
| 88 | +}; |
| 89 | + |
| 90 | +// Locate the best (newest) usable installation. nullopt = MSVC absent. |
| 91 | +std::optional<MsvcInstallation> detect_installation(); |
| 92 | + |
| 93 | +// Pure, cross-platform, unit-testable: |
| 94 | +// parse "Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35211 for x64" |
| 95 | +// (also localized banners: match a 19.xx.xxxxx token + arch token anywhere). |
| 96 | +std::optional<std::pair<std::string,std::string>> parse_cl_banner(std::string_view); |
| 97 | + |
| 98 | +// Multi-line installation guidance used everywhere MSVC is required but absent. |
| 99 | +std::string install_guidance(); |
| 100 | +``` |
| 101 | +
|
| 102 | +Implementation notes: |
| 103 | +
|
| 104 | +- `detect_installation()` composes the existing `find_vs_install_path()` + |
| 105 | + `find_latest_msvc_tools()`; adds cl banner capture. `cl.exe` is executed |
| 106 | + bare with stderr merged; the banner prints regardless of the "usage" exit |
| 107 | + status, so **ignore the exit code** and parse the output |
| 108 | + (`platform::process::capture`, not probe's `run_capture` which errors on |
| 109 | + non-zero exit). If execution or parsing fails, fall back to |
| 110 | + `clVersion = ""` and report `toolsVersion` — never fail detection just |
| 111 | + because the banner didn't parse. |
| 112 | +- `parse_cl_banner` must not assume the English banner: scan for a |
| 113 | + `\d+\.\d+\.\d+`-shaped token (first match) and an arch token among |
| 114 | + `x64|x86|arm64|ARM64` (last match). Lives outside `#ifdef _WIN32` so Linux |
| 115 | + unit tests cover it. |
| 116 | +- `install_guidance()` (also outside `#ifdef`) says, in this order: what was |
| 117 | + searched (vswhere / VSINSTALLDIR / standard paths), that mcpp does not |
| 118 | + install MSVC, and how to get it: |
| 119 | + - Visual Studio Installer → workload **Desktop development with C++** |
| 120 | + (component `Microsoft.VisualStudio.Component.VC.Tools.x86.x64`) |
| 121 | + - or Build Tools only: `winget install Microsoft.VisualStudio.2022.BuildTools` |
| 122 | + then add the C++ workload in the installer |
| 123 | + - then re-run `mcpp toolchain default msvc`. |
| 124 | +- `arch`: host-native pair only (`Hostx64\x64` on x64, `Hostarm64\arm64` on |
| 125 | + ARM64 hosts, falling back across the two). Cross target dirs are phase 2. |
| 126 | +
|
| 127 | +### 3.3 Lifecycle commands (`lifecycle.cppm`) |
| 128 | +
|
| 129 | +All four subcommands gain an msvc branch **before** the xim-package path, |
| 130 | +gated on `is_system_toolchain(spec)`: |
| 131 | +
|
| 132 | +- `mcpp toolchain default msvc[@…]` |
| 133 | + - non-Windows → error: `the msvc toolchain is only available on Windows hosts`. |
| 134 | + - Windows, detected → verify optional version pin, then |
| 135 | + `write_default_toolchain(cfg, "msvc@system")` and print e.g. |
| 136 | + ``` |
| 137 | + Detected msvc 19.44.35211 (VS 2022 BuildTools, VC tools 14.44.35207) |
| 138 | + cl: C:\...\bin\Hostx64\x64\cl.exe |
| 139 | + import std: available (std.ixx) |
| 140 | + Default set to msvc@system (was: llvm@20.1.7) |
| 141 | + note: `mcpp build` with native MSVC (cl.exe) is not yet supported — coming in a later release. |
| 142 | + ``` |
| 143 | + - Windows, absent → error + `install_guidance()`, exit 1. |
| 144 | +- `mcpp toolchain install msvc` — never installs. If detected: print the |
| 145 | + detection summary + hint `mcpp toolchain default msvc`, exit 0. If absent: |
| 146 | + `install_guidance()`, exit 1. Non-Windows: same error as `default`. |
| 147 | +- `mcpp toolchain list` — on Windows, after the xim "Installed" rows, run |
| 148 | + detection and append a `System:` section: |
| 149 | + ``` |
| 150 | + System: |
| 151 | + msvc 19.44.35211 (VS 2022 BuildTools) C:\...\cl.exe |
| 152 | + ``` |
| 153 | + with `*` when the effective default is `msvc@system` |
| 154 | + (`matches_default_toolchain` gains: `configuredDefault == "msvc@system"` |
| 155 | + matches compiler `msvc`, any version). Absent MSVC adds one hint line: |
| 156 | + `(msvc: not detected — run 'mcpp toolchain default msvc' for setup guidance)`. |
| 157 | + Non-Windows: section omitted entirely. |
| 158 | +- `mcpp toolchain remove msvc` — error: system toolchain, remove via the |
| 159 | + Visual Studio Installer. |
| 160 | +
|
| 161 | +### 3.4 Build-time resolution (`prepare.cppm`) |
| 162 | +
|
| 163 | +In the toolchain-resolution chain (before the xim `resolve_xpkg_path` |
| 164 | +branch): if `tcSpec` parses to an msvc system spec → |
| 165 | +
|
| 166 | +- non-Windows: error `toolchain 'msvc@system' is only available on Windows`. |
| 167 | +- Windows: `msvc::detect_installation()`; absent → error + guidance; |
| 168 | + found → `explicit_compiler = clPath` (no xim resolution, no post-install |
| 169 | + fixup), `ui::info("Resolved", "msvc@system → <cl path>")`. |
| 170 | +
|
| 171 | +After `detect()` returns (ctx.tc populated), a single gate: |
| 172 | +`tc.compiler == CompilerId::MSVC` → return error: |
| 173 | +
|
| 174 | +``` |
| 175 | +native MSVC (cl.exe) builds are not yet supported by mcpp. |
| 176 | + detected: msvc 19.44.35211 at C:\...\cl.exe (selection & detection work today) |
| 177 | + for building on Windows use the MSVC-ABI Clang toolchain instead: |
| 178 | + mcpp toolchain default llvm@20.1.7 |
| 179 | +``` |
| 180 | +
|
| 181 | +This keeps every command that only *resolves/inspects* the toolchain |
| 182 | +(`toolchain …`, `doctor`, `self env`) fully working while `build`/`run`/ |
| 183 | +`test` fail early with one owned message. |
| 184 | +
|
| 185 | +### 3.5 Detection classification (`detect.cppm`) |
| 186 | +
|
| 187 | +`detect()` classifies by driver **filename first** for MSVC: stem `cl` |
| 188 | +(case-insensitive) short-circuits before the `--version` probe (cl.exe has no |
| 189 | +`--version`; running it with GNU flags produces garbage). The MSVC path: |
| 190 | +
|
| 191 | +- run bare `cl.exe` (stderr merged), `parse_cl_banner` → `tc.version`, arch. |
| 192 | +- `tc.compiler = CompilerId::MSVC`; `tc.targetTriple` = `x86_64-pc-windows-msvc` |
| 193 | + / `i686-…` / `aarch64-…` from the banner arch (fallback: path segment |
| 194 | + `Hostx64\x64`, then host arch). |
| 195 | +- `tc.driverIdent` = normalized banner (fingerprint input — MSVC patch |
| 196 | + updates change the banner, invalidating BMI caches correctly). |
| 197 | +- `tc.stdModuleSource` = `msvc::find_std_module_source()`; |
| 198 | + `tc.hasImportStd` accordingly. Skip `-dumpmachine` / `-print-sysroot` / |
| 199 | + payload probing (meaningless for cl.exe). |
| 200 | +- `bmi_traits()` gains the MSVC branch (`ifc.cache` / `.ifc` / |
| 201 | + `needsExplicitModuleOutput=true`) so phase 2 doesn't silently reuse GCC |
| 202 | + defaults; unreachable in builds this release because of the 3.4 gate. |
| 203 | +
|
| 204 | +### 3.6 Doctor (`doctor.cppm`) |
| 205 | +
|
| 206 | +Windows-only section "Checking msvc (system)": detected → print product / |
| 207 | +tools version / cl version / std.ixx presence as ok-lines; absent → a |
| 208 | +warning (not a failure) with the one-line hint to run |
| 209 | +`mcpp toolchain default msvc` for guidance. When the *effective default* is |
| 210 | +`msvc@system`, doctor's existing toolchain check must treat the missing |
| 211 | +std-module BMI as expected (build support pending), not an error. |
| 212 | +
|
| 213 | +### 3.7 User docs |
| 214 | +
|
| 215 | +`docs/03-toolchains.md`: new "MSVC (system toolchain, Windows)" section — |
| 216 | +what detection does, the not-installed guidance, the build-support status, |
| 217 | +`[toolchain] windows = "msvc@system"` manifest example (finally matching the |
| 218 | +types.cppm comment). |
| 219 | +
|
| 220 | +## 4. Testing |
| 221 | +
|
| 222 | +Unit (`tests/unit/test_toolchain_msvc.cpp`, cross-platform): |
| 223 | +
|
| 224 | +- `parse_cl_banner`: English banner, localized banner (arch token + version |
| 225 | + token only), garbage → nullopt, arm64 variant. |
| 226 | +- `install_guidance()` non-empty, mentions `winget` and does not claim mcpp |
| 227 | + installs MSVC. |
| 228 | +- spec normalization: `msvc` / `msvc@system` / `msvc@19.44` → |
| 229 | + `is_system_toolchain` true; `gcc@16.1.0` → false. |
| 230 | +- `matches_default_toolchain("msvc@system", "msvc", <any>)` true. |
| 231 | +- `bmi_traits` MSVC branch. |
| 232 | +
|
| 233 | +E2E: |
| 234 | +
|
| 235 | +- `95_msvc_system_toolchain.sh` — `# requires: msvc` (new capability in |
| 236 | + `run_all.sh`, set on Windows when vswhere+VC tools present): |
| 237 | + `toolchain default msvc` succeeds & prints `Detected` + `msvc@system`; |
| 238 | + `toolchain list` shows the System section with `*`; `mcpp build` on a |
| 239 | + scratch project fails with `not yet supported` + detected version; |
| 240 | + `toolchain remove msvc` fails with the system-toolchain message; |
| 241 | + `toolchain install msvc` exits 0 with the already-installed summary. |
| 242 | +- `96_msvc_unavailable_nonwindows.sh` — `# requires: unix-shell`: |
| 243 | + `toolchain default msvc` fails with `only available on Windows`; |
| 244 | + `toolchain list` shows no System section. |
| 245 | +
|
| 246 | +CI: `ci-windows.yml` gains one step "Toolchain: MSVC — detection & selection" |
| 247 | +running the e2e positive flow against `$MCPP_SELF` (windows-latest ships VS |
| 248 | +2022 Enterprise, so detection must succeed; treat failure as regression). |
| 249 | +
|
| 250 | +## 5. Rollout plan |
| 251 | +
|
| 252 | +1. Temp branch `tmp/msvc-windows-ci`: implementation + **all workflows except |
| 253 | + `ci-windows.yml` deleted** (CI-time economy while iterating), draft PR, |
| 254 | + iterate to green. |
| 255 | +2. Real branch `feat/msvc-system-toolchain` from the green tree with |
| 256 | + workflows restored, version bumped to **0.0.88** |
| 257 | + (`src/toolchain/fingerprint.cppm` `MCPP_VERSION` + `mcpp.toml` version + |
| 258 | + `CHANGELOG.md`), real PR, full CI green, merge. |
| 259 | +3. Tag `v0.0.88` → `release.yml` → mirror assets to xlings-res (gh + gtc |
| 260 | + both ends) → xim-pkgindex mcpp package update (gitee auto-mirrors) → |
| 261 | + `xlings install mcpp` verification → bump bootstrap pin. |
| 262 | +
|
| 263 | +## 6. Risks |
| 264 | +
|
| 265 | +- **cl.exe bare-run exit code/output shape varies by VS version** — mitigated: |
| 266 | + ignore exit code, tolerate parse failure (fall back to tools-dir version). |
| 267 | +- **windows-latest runner image changes VS layout** — mitigated: detection has |
| 268 | + three strategies; CI step asserts detection so a silent regression surfaces. |
| 269 | +- **Non-English banners** — mitigated: token-based parsing, unit-tested. |
| 270 | +- **Users interpret `msvc` selection as build support** — mitigated: the |
| 271 | + explicit note at selection time + the owned build-time error. |
0 commit comments