Date: 2026-07-13 Status: approved for implementation (target release: 0.0.88)
mcpp on Windows today builds exclusively with MSVC-ABI Clang (llvm@20.1.7),
which silently borrows the MSVC STL (std.ixx) and Windows SDK from an
installed Visual Studio via mcpp.toolchain.msvc discovery. But MSVC itself
is not a selectable toolchain: mcpp toolchain default msvc falls into the
xim-package path and fails with a confusing index error, and a machine
without VS fails deep inside the Clang std-module build with no actionable
message.
Goal of this change (detection-first): make MSVC a first-class system toolchain that a developer can switch to / configure, where mcpp
- checks whether the system has a usable MSVC installation,
- auto-locates it (vswhere → env vars → well-known paths) and identifies its
version (VS product, VC tools version,
cl.execompiler version), - and, when MSVC is absent, prints clear installation guidance — mcpp never installs MSVC itself.
Explicit non-goal (phase 2, separate PR): building with cl.exe
(/std:c++23, .ifc modules, link.exe/lib.exe, /scanDependencies).
Selecting MSVC and then running mcpp build must fail with a precise
"native cl.exe builds not yet supported" message that names the detected
version and points at llvm@20.1.7 as the working alternative — never with
an incidental downstream error.
| Layer | Symbol | Status |
|---|---|---|
| model | CompilerId::MSVC, is_msvc_target() (src/toolchain/model.cppm) |
exists |
| 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 |
| capabilities | capabilities_for() MSVC case: msvc-stl / lib.exe (src/toolchain/provider.cppm) |
stub exists |
| abi | msvcrt / msvc-stl / msvc mappings (src/toolchain/abi.cppm) |
exists |
| spec | frontend_candidates_for("msvc") → {"cl.exe"} (src/toolchain/registry.cppm) |
exists |
| manifest | [toolchain] windows = "msvc@system" documented in src/manifest/types.cppm comment |
schema ready, unimplemented |
| escape hatch | tcSpec == "system" skips xim resolution (src/build/prepare.cppm:685) |
precedent for system toolchains |
Missing: detect() cannot classify cl.exe; no cl version identification;
no lifecycle (default/list/install/remove) branches; no
prepare-time resolution of msvc@system; no doctor reporting; no
absent-MSVC guidance anywhere.
MSVC is a system toolchain: mcpp locates it, never installs/removes it.
Canonical spec string is msvc@system. Accepted user inputs (CLI and
manifest) and their normalization:
msvc,msvc@system→msvc@systemmsvc@<ver-prefix>(e.g.msvc@19.44) → detect, then require the detectedcl.exeversion to start with<ver-prefix>; mismatch is an error that prints the detected version. (Pin-verify, not select-among-many: we always use the newest VC tools of the newest VS, same policy as vswhere-latest.)
The persisted global default (~/.mcpp/config.toml [toolchain] default) and
the manifest value are always the stable form msvc@system — never a
concrete version — so config survives VS updates. Concrete versions are
displayed, not stored.
Registry additions (registry.cppm):
bool is_system_toolchain(const ToolchainSpec&)— true formsvctoday (the concept is deliberately general;system(PATH compiler) stays as-is).parse_toolchain_specunchanged; msvc branching happens in callers viais_system_toolchain(keeps parse pure).
New exported surface:
struct MsvcInstallation {
std::filesystem::path vsRoot; // C:\Program Files\Microsoft Visual Studio\2022\BuildTools
std::string vsProduct; // "2022 BuildTools" (derived from path segments)
std::string toolsVersion; // "14.44.35207" (VC\Tools\MSVC\<dir>)
std::filesystem::path clPath; // ...\bin\Hostx64\x64\cl.exe
std::string clVersion; // "19.44.35211" (banner; falls back to toolsVersion-derived)
std::string arch; // "x64" | "x86" | "arm64" (host-native target dir)
bool hasStdModules; // modules\std.ixx present
};
// Locate the best (newest) usable installation. nullopt = MSVC absent.
std::optional<MsvcInstallation> detect_installation();
// Pure, cross-platform, unit-testable:
// parse "Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35211 for x64"
// (also localized banners: match a 19.xx.xxxxx token + arch token anywhere).
std::optional<std::pair<std::string,std::string>> parse_cl_banner(std::string_view);
// Multi-line installation guidance used everywhere MSVC is required but absent.
std::string install_guidance();Implementation notes:
detect_installation()composes the existingfind_vs_install_path()+find_latest_msvc_tools(); adds cl banner capture.cl.exeis executed bare with stderr merged; the banner prints regardless of the "usage" exit status, so ignore the exit code and parse the output (platform::process::capture, not probe'srun_capturewhich errors on non-zero exit). If execution or parsing fails, fall back toclVersion = ""and reporttoolsVersion— never fail detection just because the banner didn't parse.parse_cl_bannermust not assume the English banner: scan for a\d+\.\d+\.\d+-shaped token (first match) and an arch token amongx64|x86|arm64|ARM64(last match). Lives outside#ifdef _WIN32so Linux unit tests cover it.install_guidance()(also outside#ifdef) says, in this order: what was searched (vswhere / VSINSTALLDIR / standard paths), that mcpp does not install MSVC, and how to get it:- Visual Studio Installer → workload Desktop development with C++
(component
Microsoft.VisualStudio.Component.VC.Tools.x86.x64) - or Build Tools only:
winget install Microsoft.VisualStudio.2022.BuildToolsthen add the C++ workload in the installer - then re-run
mcpp toolchain default msvc.
- Visual Studio Installer → workload Desktop development with C++
(component
arch: host-native pair only (Hostx64\x64on x64,Hostarm64\arm64on ARM64 hosts, falling back across the two). Cross target dirs are phase 2.
All four subcommands gain an msvc branch before the xim-package path,
gated on is_system_toolchain(spec):
mcpp toolchain default msvc[@…]- non-Windows → error:
the msvc toolchain is only available on Windows hosts. - Windows, detected → verify optional version pin, then
write_default_toolchain(cfg, "msvc@system")and print e.g.Detected msvc 19.44.35211 (VS 2022 BuildTools, VC tools 14.44.35207) cl: C:\...\bin\Hostx64\x64\cl.exe import std: available (std.ixx) Default set to msvc@system (was: llvm@20.1.7) note: `mcpp build` with native MSVC (cl.exe) is not yet supported — coming in a later release. - Windows, absent → error +
install_guidance(), exit 1.
- non-Windows → error:
mcpp toolchain install msvc— never installs. If detected: print the detection summary + hintmcpp toolchain default msvc, exit 0. If absent:install_guidance(), exit 1. Non-Windows: same error asdefault.mcpp toolchain list— on Windows, after the xim "Installed" rows, run detection and append aSystem:section:withSystem: msvc 19.44.35211 (VS 2022 BuildTools) C:\...\cl.exe*when the effective default ismsvc@system(matches_default_toolchaingains:configuredDefault == "msvc@system"matches compilermsvc, any version). Absent MSVC adds one hint line:(msvc: not detected — run 'mcpp toolchain default msvc' for setup guidance). Non-Windows: section omitted entirely.mcpp toolchain remove msvc— error: system toolchain, remove via the Visual Studio Installer.
In the toolchain-resolution chain (before the xim resolve_xpkg_path
branch): if tcSpec parses to an msvc system spec →
- non-Windows: error
toolchain 'msvc@system' is only available on Windows. - Windows:
msvc::detect_installation(); absent → error + guidance; found →explicit_compiler = clPath(no xim resolution, no post-install fixup),ui::info("Resolved", "msvc@system → <cl path>").
After detect() returns (ctx.tc populated), a single gate:
tc.compiler == CompilerId::MSVC → return error:
native MSVC (cl.exe) builds are not yet supported by mcpp.
detected: msvc 19.44.35211 at C:\...\cl.exe (selection & detection work today)
for building on Windows use the MSVC-ABI Clang toolchain instead:
mcpp toolchain default llvm@20.1.7
This keeps every command that only resolves/inspects the toolchain
(toolchain …, doctor, self env) fully working while build/run/
test fail early with one owned message.
detect() classifies by driver filename first for MSVC: stem cl
(case-insensitive) short-circuits before the --version probe (cl.exe has no
--version; running it with GNU flags produces garbage). The MSVC path:
- run bare
cl.exe(stderr merged),parse_cl_banner→tc.version, arch. tc.compiler = CompilerId::MSVC;tc.targetTriple=x86_64-pc-windows-msvc/i686-…/aarch64-…from the banner arch (fallback: path segmentHostx64\x64, then host arch).tc.driverIdent= normalized banner (fingerprint input — MSVC patch updates change the banner, invalidating BMI caches correctly).tc.stdModuleSource=msvc::find_std_module_source();tc.hasImportStdaccordingly. Skip-dumpmachine/-print-sysroot/ payload probing (meaningless for cl.exe).bmi_traits()gains the MSVC branch (ifc.cache/.ifc/needsExplicitModuleOutput=true) so phase 2 doesn't silently reuse GCC defaults; unreachable in builds this release because of the 3.4 gate.
Windows-only section "Checking msvc (system)": detected → print product /
tools version / cl version / std.ixx presence as ok-lines; absent → a
warning (not a failure) with the one-line hint to run
mcpp toolchain default msvc for guidance. When the effective default is
msvc@system, doctor's existing toolchain check must treat the missing
std-module BMI as expected (build support pending), not an error.
docs/03-toolchains.md: new "MSVC (system toolchain, Windows)" section —
what detection does, the not-installed guidance, the build-support status,
[toolchain] windows = "msvc@system" manifest example (finally matching the
types.cppm comment).
Unit (tests/unit/test_toolchain_msvc.cpp, cross-platform):
parse_cl_banner: English banner, localized banner (arch token + version token only), garbage → nullopt, arm64 variant.install_guidance()non-empty, mentionswingetand does not claim mcpp installs MSVC.- spec normalization:
msvc/msvc@system/msvc@19.44→is_system_toolchaintrue;gcc@16.1.0→ false. matches_default_toolchain("msvc@system", "msvc", <any>)true.bmi_traitsMSVC branch.
E2E:
95_msvc_system_toolchain.sh—# requires: msvc(new capability inrun_all.sh, set on Windows when vswhere+VC tools present):toolchain default msvcsucceeds & printsDetected+msvc@system;toolchain listshows the System section with*;mcpp buildon a scratch project fails withnot yet supported+ detected version;toolchain remove msvcfails with the system-toolchain message;toolchain install msvcexits 0 with the already-installed summary.96_msvc_unavailable_nonwindows.sh—# requires: unix-shell:toolchain default msvcfails withonly available on Windows;toolchain listshows no System section.
CI: ci-windows.yml gains one step "Toolchain: MSVC — detection & selection"
running the e2e positive flow against $MCPP_SELF (windows-latest ships VS
2022 Enterprise, so detection must succeed; treat failure as regression).
- Temp branch
tmp/msvc-windows-ci: implementation + all workflows exceptci-windows.ymldeleted (CI-time economy while iterating), draft PR, iterate to green. - Real branch
feat/msvc-system-toolchainfrom the green tree with workflows restored, version bumped to 0.0.88 (src/toolchain/fingerprint.cppmMCPP_VERSION+mcpp.tomlversion +CHANGELOG.md), real PR, full CI green, merge. - Tag
v0.0.88→release.yml→ mirror assets to xlings-res (gh + gtc both ends) → xim-pkgindex mcpp package update (gitee auto-mirrors) →xlings install mcppverification → bump bootstrap pin.
- cl.exe bare-run exit code/output shape varies by VS version — mitigated: ignore exit code, tolerate parse failure (fall back to tools-dir version).
- windows-latest runner image changes VS layout — mitigated: detection has three strategies; CI step asserts detection so a silent regression surfaces.
- Non-English banners — mitigated: token-based parsing, unit-tested.
- Users interpret
msvcselection as build support — mitigated: the explicit note at selection time + the owned build-time error.