Skip to content

2026.9.24.1 — the MSVC toolset is the sysroot, the deployment target follows the target, and frozen gcc headers are named (#685, #687) - #688

Merged
Sunrisepeak merged 10 commits into
mainfrom
feat/toolchain-selection
Sep 23, 2026
Merged

Sunrisepeak merged 10 commits into
mainfrom
feat/toolchain-selection

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Summary

Three toolchain defects and the specification that follows from them, in one release (2026.9.24.1).

MSVC ABI: one toolset, chosen once, specifiable, recorded

On *-windows-msvc, clang's driver chose the MSVC headers and libraries by its own search (VCToolsInstallDir, PATH, the newest instance's default, %INCLUDE% wholesale) while mcpp located std.ixx by a different order (VSINSTALLDIR, vswhere -latest, the lexically highest directory). With several toolsets installed the two could name different toolsets, a project could not choose, and the choice reached neither the cache key nor any record.

  • The MSVC toolset is the row's sysroot: [target.x86_64-windows-msvc] sysroot = "msvc@system" | "msvc@<toolset>" | "xim:msvc@<toolset>". The compiler stays the toolchain (llvm@… or msvc@…).
  • msvc::select_system_toolset() is the one selection both rows use. msvc@system: VCToolsInstallDir → the VSINSTALLDIR instance's default toolset → the first cl.exe on PATH → the newest instance with C++ tools (its Microsoft.VCToolsVersion.default.txt). A pinned version is matched across every instance (numeric, component-wise prefix), ignores the environment and reports what it ignored.
  • msvc@<toolset> takes an installed toolset of that version before it installs the payload; xim:msvc@<toolset> asks for the payload only. xim: is the one toolchain namespace: it was already stripped on every family (a synonym for gcc/llvm), and another namespace, which earlier releases also stripped silently, is now refused where it is read. xim:msvc@system is refused as a spelling.
  • prepare binds the choice once (bind_msvc_sysroot); the link model hands it to clang as -Xmicrosoft-visualc-tools-root, -Xmicrosoft-windows-sdk-root, -Xmicrosoft-windows-sdk-version on the compile, scan, std-module and link lines; std.ixx comes from the same toolset. resolution.json gains msvc_toolset and windows_sdk; the toolset directory and SDK version enter the dependency cache key; the SDK version becomes the row's ucrt@ runtime identity.
  • The cl.exe row refuses a sysroot naming another toolset and no longer borrows another toolset's std.ixx. mcpp toolchain list shows installed toolsets; mcpp toolchain default msvc@<toolset> accepts an installed one.
  • Paths with whitespace on the ninja compile line are quoted (the system toolset lives under C:\Program Files); std-module commands use platform quoting on Windows. Paths without whitespace, and every POSIX command, are byte-identical.

#685 — the macOS deployment target is keyed on the target

platform::macos::deployment_target() read #if defined(__APPLE__) and answered empty on every other host, so --target aarch64-macos from Linux or Windows ignored [build] macos_deployment_target and MACOSX_DEPLOYMENT_TARGET: the triple fell back to arm64-apple-macos14.0, the fingerprint did not fold the value, and the macos.deployment-target platform fact was empty. The mirror defect: a macOS host crossing to a non-Apple target still emitted -mmacosx-version-min.

The resolver now takes targetIsMacos and resolves env > manifest > 14.0 on any host; every reader passes its own target's answer (the effective triple, the fingerprint, the platform fact, -mmacosx-version-min, the std-module precompile). The build.mcpp host compile keeps answering for the host, which is its target. A macOS host building for macOS is unchanged.

Windows build: two exported std::optional<std::string> members become plain strings

clang with the MSVC STL cannot instantiate the implicit copy of an exported std::optional<std::string> data member once an importer's module graph changes (the _SMF_control "no matching constructor" error, recorded before on TargetEntry::sysroot and Profile::dependencyLinkage). The first round reported it on CompileUnit::providesModule, now a std::string (a module name is never empty, so empty means "not a module interface"). The second and third rounds reported it on RuntimeBinding's copy in prepare.cppm, a struct and a copy that main compiles. Two compile-only branches measured the candidates in parallel: replacing RuntimeBinding::libc and hostLibc compiles; replacing the new optional<std::string> return in toml.cppm does not. Every reader of the two members already treated absent and empty alike. An earlier hypothesis, the JSON import in msvc.cppm, was refuted by the third round; the vswhere listing is read in its text format all the same (parse_vswhere_text, four keys, unit-tested), which keeps the module's imports as they were.

With mcpp compiling, the next round showed import std failing on the clang row: std.compat.pcm was never written. The rebinding had also set a std.compat source, which main never gave this row, and the clang builder for it passes std.compat.ixx without -x c++-module, so clang took it for linker input and --precompile exited 0 with no output. The rebinding now replaces only an MSVC std.ixx and leaves std.compat as detection found it.

Earlier engines and the new spellings (measured)

The released 2026.9.21.3 on a Windows runner, one mcpp new project, four manifests: sysroot = "msvc@system" and "msvc@<toolset>" on the MSVC row refuse the whole manifest ("is not an xpkg reference"); "xim:msvc@<toolset>" is accepted and does nothing: no install, clang builds against the machine's toolset, and the output names the value as the c-abi layer. The changelog and docs/20 state this; a project that depends on the toolset it names pins mcpp 2026.9.24.1 or later.

#687 — frozen fixincludes headers

The recipe fix landed in openxlings/xim-pkgindex#870 (the prune never ran: nested quotes made grep search the cwd). mcpp self doctor now reports installed gcc payloads whose include-fixed/ still holds fixincludes-frozen headers, with the reinstall command.

SPEC-006 — toolchain management

docs/specs/toolchain-management.md (draft v0.2): identity and spelling, origin and selection, the payload contract, build, acceptance, release order — each clause tagged with its implementation status. The payload-contract and acceptance clauses are to be implemented with the next LLVM toolchain batch.

Design and plan: .agents/docs/2026-09-24-toolchain-selection-and-payload-trust-design.md, …-implementation-plan.md; analysis: 2026-09-24-685-687-msvc-stl-and-toolchain-payloads.md.

Closes #685. Closes #687.

Test plan

  • mcpp test: 125 of 125 test binaries pass on Linux.
  • macos_deployment_target / MACOSX_DEPLOYMENT_TARGET are ignored when cross-compiling to aarch64-macos from a non-Apple host (always minos 14.0) #685: unit tests for the resolver's host independence, -mmacosx-version-min by synthetic target, and the fingerprint; e2e 746 on a Linux host (--configure-only, [target.aarch64-macos] toolchain = "llvm@…", no macOS SDK) asserts arm64-apple-macos11.0, a fingerprint change and env over manifest; all three fail with the fix reverted.
  • test_toolchain_msvc: parse_vswhere_text (CRLF, one instance per instanceId, an instance without a path dropped).
  • gcc@15.1.0 / gcc@13.3.0 on x86_64-linux-gnu: a stale fixincludes pthread.h in the payload shadows glibc 2.44, so <memory>/<mutex> do not compile #687: test_doctor_fixincludes (synthetic trees); mcpp self doctor on this machine reports gcc 13.3.0 and 15.1.0 and not 16.1.0.
  • Linux e2e run locally: 746, 96, 325, 26, 29, 14, 134, 136, 294, 22, 01, 645, 267.
  • Unit (run on Linux; the selection is a pure function over a synthetic machine): test_toolchain_msvc (toolset version comparison and matching, pinned and system selection, environment ignored and reported, incomplete candidates skipped, default file over highest directory, newest instance without C++ passed over, clang row without cl.exe, refusal listing; xim: spellings), test_manifest (MSVC-row sysroot spellings accepted and refused), test_linkmodel, test_hostflags, test_cache_key (verified to fail with the key change removed).
  • Linux zero-diff: the released 2026.9.21.3 and this branch generate byte-identical build.ninja for examples/01-hello and examples/04-workspace (except the mcpp path line) with identical fingerprints.
  • Windows e2e 760_msvc_toolset_is_chosen_once.sh (runner's Visual Studio, no downloads; this round's runner carries Visual Studio 2026 with MSVC 14.51.36231, and the record names 14.51.36231, origin system, SDK 10.0.26100.0): the default clang row records and passes the vswhere/default.txt toolset, ucrt@ identity present, -Xmicrosoft-* in build.ninja, program runs; a pinned sysroot ignores and reports VCToolsInstallDir; a C-library package is refused as an MSVC sysroot; the cl.exe row uses an installed pinned toolset; a mismatched cl.exe-row sysroot is refused.
  • Windows e2e 239 (ci-windows-msvc-xlings): xim:msvc@14.44.35207 on the cl.exe row and as the clang row's sysroot; the clang row compiled against .mcpp/registry/data/xpkgs/xim-x-msvc/14.44.35207/VC/Tools/MSVC/14.44.35207, recorded as managed with the payload's SDK.
  • CI: every check passes except the two macOS xcode-27 jobs, red on main for the same reason (mcpp#669: lld 22.1.8 cannot read the Xcode 27 SDK's .tbd files).
  • All other Windows clang-row e2e now run through the bound toolset.

speak-agent and others added 10 commits September 24, 2026 04:19
On *-windows-msvc the clang driver searched the machine for the MSVC headers
and libraries while mcpp searched it again for std.ixx, by a different order.
The toolset is now the row's sysroot ([target.<triple>].sysroot, default
msvc@system), selected once by msvc::select_system_toolset and handed to clang
as -Xmicrosoft-* on the compile, scan, std-module and link lines; recorded in
resolution.json and in the cache key.

msvc@<toolset> takes an installed toolset before the payload; xim: names the
payload and is accepted on every family. Adds SPEC-006 (toolchain management,
draft) and the design, plan and analysis records.
`platform::macos::deployment_target()` read `#if defined(__APPLE__)` and
answered empty on every non-Apple host regardless of what the build
targeted, so `mcpp build --target aarch64-macos` on Linux (or Windows)
silently ignored `[build] macos_deployment_target` and
`MACOSX_DEPLOYMENT_TARGET`: the effective triple, the BMI fingerprint, the
`macos.deployment-target` platform fact, and `-mmacosx-version-min` all
fell back to the built-in 14.0 default instead. The mirror defect also
existed in `hostflags.cppm`: a macOS host cross-compiling to a non-Apple
target kept emitting the macOS deployment flag, gated on the host rather
than the resolved toolchain's own target.

The resolver now takes an explicit `targetIsMacos` bool instead of reading
the host platform, so each reader (the effective triple, the fingerprint,
the platform facts, `-mmacosx-version-min` emission, the std module
precompile) answers from the toolchain's own resolved target rather than
from the machine running mcpp. The build.mcpp host compile keeps its
existing host-keyed resolution, because there "the target" legitimately is
the host toolchain building it. Behaviour for a macOS host building for
macOS is unchanged.

Adds unit coverage for the resolver's host-independence and precedence
(env > manifest > built-in default), for `host_compile_tokens`'s flag
emission by synthetic target triple, and for the fingerprint folding the
value only when the target is macOS. Adds an e2e test that exercises the
real toolchain-resolution -> triple -> compile_commands.json path on a
Linux host via an explicit `[target.aarch64-macos] toolchain = "llvm@…"`
override, without needing a macOS SDK.

Co-Authored-By: Claude <noreply@anthropic.com>
…687)

GCC's fixincludes step runs at payload build time against the build
machine's own glibc and writes an edited copy into
lib/gcc/<triple>/<version>/include-fixed. mcpp's own C-library search order
puts that directory (-idirafter) ahead of the realised glibc a project
actually resolves, so a frozen header left over from the build machine can
disagree with it — measured on the xim-x-gcc 13.3.0 and 15.1.0 payloads,
whose frozen pthread.h breaks <mutex>/<memory>. The index recipe that stops
a fresh install from carrying this file is fixed separately (xim-pkgindex);
this is the diagnostic half for payloads already installed.

`mcpp self doctor` now scans every installed GCC-family payload (native,
musl-gcc, mingw-(cross-)gcc, and any cross gcc payload the existing
toolchain-runtime-deps check already enumerates) for a fixincludes banner
under include-fixed, and warns per payload naming the file(s), the
banner's own recorded source path, the consequence, and the remedy
(`mcpp index update`, then `mcpp toolchain remove`/`install`). The scan
itself is a pure function of a payload root, independent of host OS
(unlike the neighbouring RUNPATH check, which needs readelf), so it also
covers a cross gcc payload installed on a non-Linux mcpp host.

Verified against the real registry: reports xim-x-gcc 13.3.0 and 15.1.0,
silent on 16.1.0 (which ships only include-fixed/README, no frozen
header).

Co-Authored-By: Claude <noreply@anthropic.com>
std::optional<std::string> in the exported CompileUnit does not copy under
clang with the MSVC STL once the module interface around it changes: the
Windows build failed with no matching _SMF_control constructor, reported at
plan.cppm's CompileUnit. A module name is never empty, so an empty string
means "not a module interface"; every reader is rewritten accordingly.
CHANGELOG entry, SPEC-006 implementation status (v0.2), the design's
implementation record, and the sandbox verification script for the release.
…ibrary

The second Windows round failed with the same _SMF_control error on
RuntimeBinding's implicit copy, a struct and a copy that main compiles.
The one change this branch makes to the import graph is msvc.cppm
importing mcpp.libs.json; the vswhere listing is now read in its text
format by parse_vswhere_text, which a unit test covers.

Also: an xim:msvc@<v> sysroot whose package cannot be provided no longer
reports on the machine it never looked at; the verify script's section F
measures what changed (another namespace refused, xim:msvc@system refused
as a spelling) rather than xim:gcc, which earlier releases already
accepted; the changelog and toolchain docs say so.
The Windows build (clang with the MSVC STL) failed on RuntimeBinding's
implicit copy in prepare.cppm, a struct and a copy that main compiles.
Measured on two compile-only branches: replacing these two members
compiles; replacing the new optional<string> return in toml.cppm does
not. The earlier hypothesis, the JSON import in msvc.cppm, was refused
by the third round; its comment is corrected.

Every reader already treated absent and empty alike (value_or(""),
assigned only when non-empty, JSON decodes empty as absent).
2026.9.21.3 on a Windows runner refuses the whole manifest for
msvc@system and msvc@<toolset>, and accepts xim:msvc@<toolset> without
acting on it: nothing is installed, clang builds against the machine's
toolset, and the output names the value as the c-abi layer.
Windows CI, first round with a building mcpp: import std failed on the
clang row because std.compat.pcm was never written. Main never gave this
row a std.compat source; the rebinding did, and clang's compat builder
passes the file without -x c++-module, so clang took std.compat.ixx for
linker input and --precompile wrote nothing. std.compat is left as
detection found it, and only an MSVC std.ixx is replaced.
@Sunrisepeak
Sunrisepeak marked this pull request as ready for review September 23, 2026 21:45
@Sunrisepeak Sunrisepeak changed the title feat: the MSVC toolset is the sysroot of the MSVC ABI; #685 deployment target keyed on the target; #687 doctor; SPEC-006 2026.9.24.1 — the MSVC toolset is the sysroot, the deployment target follows the target, and frozen gcc headers are named (#685, #687) Sep 23, 2026
@Sunrisepeak
Sunrisepeak merged commit b482469 into main Sep 23, 2026
41 of 43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants