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
Conversation
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
marked this pull request as ready for review
September 23, 2026 21:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 locatedstd.ixxby 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.[target.x86_64-windows-msvc] sysroot = "msvc@system" | "msvc@<toolset>" | "xim:msvc@<toolset>". The compiler stays the toolchain (llvm@…ormsvc@…).msvc::select_system_toolset()is the one selection both rows use.msvc@system:VCToolsInstallDir→ theVSINSTALLDIRinstance's default toolset → the firstcl.exeonPATH→ the newest instance with C++ tools (itsMicrosoft.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@systemis refused as a spelling.bind_msvc_sysroot); the link model hands it to clang as-Xmicrosoft-visualc-tools-root,-Xmicrosoft-windows-sdk-root,-Xmicrosoft-windows-sdk-versionon the compile, scan, std-module and link lines;std.ixxcomes from the same toolset.resolution.jsongainsmsvc_toolsetandwindows_sdk; the toolset directory and SDK version enter the dependency cache key; the SDK version becomes the row'sucrt@runtime identity.sysrootnaming another toolset and no longer borrows another toolset'sstd.ixx.mcpp toolchain listshows installed toolsets;mcpp toolchain default msvc@<toolset>accepts an installed one.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-macosfrom Linux or Windows ignored[build] macos_deployment_targetandMACOSX_DEPLOYMENT_TARGET: the triple fell back toarm64-apple-macos14.0, the fingerprint did not fold the value, and themacos.deployment-targetplatform fact was empty. The mirror defect: a macOS host crossing to a non-Apple target still emitted-mmacosx-version-min.The resolver now takes
targetIsMacosand 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 stringsclang 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 onTargetEntry::sysrootandProfile::dependencyLinkage). The first round reported it onCompileUnit::providesModule, now astd::string(a module name is never empty, so empty means "not a module interface"). The second and third rounds reported it onRuntimeBinding's copy inprepare.cppm, a struct and a copy that main compiles. Two compile-only branches measured the candidates in parallel: replacingRuntimeBinding::libcandhostLibccompiles; replacing the newoptional<std::string>return intoml.cppmdoes not. Every reader of the two members already treated absent and empty alike. An earlier hypothesis, the JSON import inmsvc.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 stdfailing on the clang row:std.compat.pcmwas never written. The rebinding had also set astd.compatsource, which main never gave this row, and the clang builder for it passesstd.compat.ixxwithout-x c++-module, so clang took it for linker input and--precompileexited 0 with no output. The rebinding now replaces only an MSVCstd.ixxand leavesstd.compatas detection found it.Earlier engines and the new spellings (measured)
The released 2026.9.21.3 on a Windows runner, one
mcpp newproject, 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 anddocs/20state 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 doctornow reports installed gcc payloads whoseinclude-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.-mmacosx-version-minby synthetic target, and the fingerprint; e2e746on a Linux host (--configure-only,[target.aarch64-macos] toolchain = "llvm@…", no macOS SDK) assertsarm64-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 perinstanceId, an instance without a path dropped).test_doctor_fixincludes(synthetic trees);mcpp self doctoron this machine reports gcc 13.3.0 and 15.1.0 and not 16.1.0.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 withoutcl.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).build.ninjaforexamples/01-helloandexamples/04-workspace(except the mcpp path line) with identical fingerprints.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, originsystem, SDK 10.0.26100.0): the default clang row records and passes the vswhere/default.txt toolset,ucrt@identity present,-Xmicrosoft-*inbuild.ninja, program runs; a pinned sysroot ignores and reportsVCToolsInstallDir; 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.239(ci-windows-msvc-xlings):xim:msvc@14.44.35207on 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 asmanagedwith the payload's SDK..tbdfiles).