| subject | triage |
|---|---|
| status | landed |
Status: landed in mcpp 2026.9.12.2. The four questions in the first draft were answered in review (§9), and three statements were measured and corrected before implementation (§2, §5.2, §7). The corrections made during implementation are recorded in §11. The ecosystem changes E1 and E2 follow the release (§10).
The scope is issues #564 to #618 in mcpp-community/mcpp, the gaps the
2026-09-11 record left open, and one defect found while measuring them. Each
issue was checked against the code on main at c688fcab, not against the PR
that claims to fix it.
| item | before | finding on main |
action |
|---|---|---|---|
#564 default_jobs unread |
open | prepare.cppm reads defaultJobs into globalDefaultJobs; default_backend is removed |
closed, citing #607 |
| #597 WebAssembly target | open | ObjectFormat::Wasm; the wasm32-emscripten row is verified |
closed, citing #605, #610, #617 |
| #599 bench hub path | open | the hub is the pinned tree's own path; the uninitialised branch fails under CI | closed, citing #607 |
| #603 clang on Windows, level 23 | open | the clang path calls std_module_min_level_for_stl |
closed, citing #607 |
#604 MSVC /reference pair |
open | flags are appended verbatim; orphaned_reference refuses early |
closed, citing #607 |
| #606 scanner inside comments | open | one three-state pass; tests/e2e/639 |
closed, citing #607 |
#609 MSVC STL 14.51 _Find_vectorized |
open | upstream microsoft/STL#6294; nothing in mcpp is wrong | §6, then closed |
| #611 personal notes | open | a to-do list spanning three repositories | left open; its one engine item is #613 |
| #613 install hooks and the standard library | open | the refusal already exists; the hook environment does not | §2 |
#614 XLINGS_PROJECT_DIR asymmetry |
open | unfixed | §3 |
| #615 runtime files that are not DLLs | open | unfixed | §4 |
| #618 Windows GUI subsystem | open | unfixed | §1 |
| G1 no per-target tool declaration | recorded 2026-09-11 | misdiagnosed: the declaration exists | §5.1 |
G2 no whole-graph channel for -pthread |
recorded 2026-09-11 | misdiagnosed in part: the channel exists; scoping and a requirement do not | §5.2 |
T1 --toolchain replayed by the fast path |
found 2026-09-12 | a defect | §7 |
| openkal-musl never built for Darwin | recorded 2026-09-11 | package-side | not engine; belongs to openkal-musl |
no device runner for aarch64-ios |
recorded 2026-09-11 | needs a developer signature | not engine; out of scope |
No. Four independent reasons, any one of which would be enough.
- The spelling belongs to one linker dialect, and a PE target has two. The
flags in #365's report,
-Wl,-subsystem:windows -Wl,-entry:mainCRTStartup, are link.exe and lld-link syntax passed through a clang driver. The same program built forx86_64-windows-gnulinks with GNU ld or lld in MinGW mode, which take--subsystem windows; GCC's own spelling is-mwindows. The engine has already recorded a GNU linker rejecting a subsystem option (ld: unrecognized option '--subsystem',prepare.cppm:9412). One intent therefore needs onecfgblock per ABI, and a project that forgets one gets a console on that ABI with no diagnostic. - The pair is a pair on one CRT only. On the MSVC CRT,
/SUBSYSTEM:WINDOWSchanges the default entry toWinMainCRTStartup, so a portableint main()fails withLNK2019: unresolved external symbol WinMainunless/ENTRY:mainCRTStartupaccompanies it;/ENTRY:mainlinks and skips CRT initialisation. mingw-w64's startup code is different, and copying the MSVC entry override there is not correct by construction. The correct flags are a function of the subsystem and the CRT, which a project should not have to compute. - The scope is wrong, and no flag-carrying key has the right scope.
[build] ldflagsand[target.<selector>.build] ldflagsland in the global$ldflagsofbuild.ninja, so everymcpp testbinary becomes a GUI program whose output no terminal shows, and they propagate to consumers (docs/30: "[build] ldflagsalready propagates to consumers").mcpp:link-flagreaches consumers by design.[targets.<name>]has no link-side key. - The engine cannot read a flag's meaning. With a field, mcpp knows the
artefact is a GUI program:
mcpp runcan state that the program has no console,mcpp testcan refuse the key on a test target, and a packager can treat the artefact as an application.
A per-target ldflags key would fix reason 3 and none of the others. It is not
part of this change.
[targets.myapp]
kind = "bin"
main = "src/main.cpp"
windows_subsystem = "windows" # "console" (default) | "windows"
windows_entry = "main" # "main" (default) | "wmain" | "WinMain" | "wWinMain"Naming, as decided in review. windows_subsystem names the one platform it
affects, so a reader on another platform can tell it is inert there. The value is
"windows", the PE subsystem's own name and the value Rust's
#![windows_subsystem] and Meson's win_subsystem use, so a developer arriving
from either reads it without translation. One spelling, with no alias.
The entry point. windows_entry names the function the program defines, not
the CRT symbol that calls it. It is independent of the subsystem, because a
console program may define wmain.
windows_entry |
MSVC CRT startup | mingw-w64 |
|---|---|---|
main |
mainCRTStartup |
default |
wmain |
wmainCRTStartup |
-municode |
WinMain |
WinMainCRTStartup |
default |
wWinMain |
wWinMainCRTStartup |
-municode |
| target | windows_subsystem = "windows" renders |
decided by |
|---|---|---|
PE, MSVC style: cl, clang-cl, clang targeting *-windows-msvc |
/SUBSYSTEM:WINDOWS plus the entry's /ENTRY: symbol, spelled with -Wl, under a GNU-style driver |
pe_msvc_abi (§11.1) |
PE, GNU style: MinGW gcc, clang targeting *-windows-gnu |
-mwindows, plus -municode for a wide entry |
pe_msvc_abi (§11.1) |
| ELF, Mach-O, Wasm | nothing; no diagnostic; byte-identical artefact | ObjectFormat |
"console" with "main" renders nothing on every target, because both are the
linker's defaults. On the MSVC ABI any other combination renders both flags
(§11.2). The ABI is answered by pe_msvc_abi, the predicate the import library
flag already uses, and the emitter spells the flag for the linker it invokes
(§11.1).
- Appended to
LinkUnit::linkFlagsof that target'sBinaryunit only (plan.cppm:103, rendered per edge as$unit_ldflags). - Refused, naming the target and the key, on library targets and on test targets.
- It never reaches consumers or another target of the package.
kKnownTargetKeysgains both keys. The warning that lists per-target keys is generated from the same list, because the hand-written copy already omitsexports.- A build program selects the subsystem for a target it names, through a directive, so a framework's rule package can mark the application it knows about. The directive names a target of the package being built and therefore cannot leak into consumers.
windows_subsystem = "windows"withint main()links onx86_64-windows-msvcand onx86_64-windows-gnu. The PE optional header's Subsystem field reads 2 (IMAGE_SUBSYSTEM_WINDOWS_GUI), read from the bytes.- In the same package, the
mcpp testbinaries and a secondbintarget read 3 (IMAGE_SUBSYSTEM_WINDOWS_CUI). - A static constructor in the GUI target runs before
mainon both ABIs. - The same manifest on Linux produces no diagnostic and an artefact byte-identical to one built without the keys.
- The keys are refused on a library target and on a test target, each refusal naming the target and the key; an unknown value is refused naming the accepted values.
- Rendering is unit-tested for every row of the tables in §1.2 and §1.3.
It does not produce an application bundle, embed an application manifest, or
choose DPI awareness. Those belong to packaging formats and to [resources].
Build programs receive the resolved toolchain as environment variables:
MCPP_COMPILER, MCPP_CXX_STDLIB, MCPP_TARGET, the MCPP_TARGET_* splits and
the MCPP_TOOLCHAIN_* paths (build_program.cppm, around line 540). The value
of MCPP_CXX_STDLIB is the toolchain's stdlibId: libstdc++ for gcc,
libc++ for clang, msvc-stl for clang targeting MSVC. Install hooks receive
none of these. install_packages runs as
cd <home> && env -u XLINGS_PROJECT_DIR XLINGS_HOME=<home> xlings interface install_packages … (xlings.cppm:1435).
The first draft proposed a new abi = { cxx_stdlib = … } declaration. It is not
needed. The layer grammar already states the requirement, and the engine already
refuses it at resolution. Measured 2026-09-12 with a path dependency declaring
requires = ["mcpp:c++-abi=libstdc++"] and a project on llvm@22.1.8:
error: `stdreq@0.1.0` requires the c++-abi to be `libstdc++`.
c++-abi libc++ (payload)
required libstdc++ (required by stdreq@0.1.0)
With the default gcc toolchain the same project resolves
c++-abi libstdc++ (payload) and builds.
- Order. The requirement must be refused before an index package's install hook runs, not after a source build has already spent several minutes. This is measured first; if the check follows provisioning, it moves ahead of it.
- The hook's environment. The install command carries the subset build
programs already receive, under the same names and the same rule ("always
emitted, empty when not applicable"):
MCPP_COMPILER,MCPP_CXX_STDLIB,MCPP_TARGET,MCPP_TARGET_OS,MCPP_TARGET_ARCH,MCPP_TARGET_ENV. They are empty while a toolchain payload itself installs. - The rule for a hook. A hook may use these values to refuse or to diagnose. It must not build a variant into a store directory that does not name the variant, because the store is keyed by package and version, and the first consumer would otherwise decide the flavour for every later one. A store keyed by variant is an xlings change and is out of scope.
- Documentation. docs/22 and docs/06 state the recipe for a source-built
static package: declare
requires = ["mcpp:c++-abi=<stdlib>"].
- A requirement mismatch on an index package with an install hook is refused before the hook runs; the refusal names the layer, both implementations and the package.
- A hook that prints
MCPP_CXX_STDLIBprints the resolvedstdlibId, and an empty value while a toolchain payload installs. The environment composition is unit-tested on both platforms.
build_command_prefix (xlings.cppm, from line 1136) and the self init call
(around line 1590) express global mode as env -u XLINGS_PROJECT_DIR on POSIX
and as env::set("XLINGS_PROJECT_DIR", "") on Windows. Absent and empty are
different answers to "which scope is this", and xlings resolves its subos scope
from that variable. The Windows branch also mutates mcpp's own process
environment, so the value outlives the invocation that needed it.
- One function decides the environment of an xlings invocation: home, project directory or its absence, PATH prefix, and the hook variables of §2.3. Each platform renders that decision. The three copies of the decision become its callers.
- On Windows the decision is applied through the scoped guard
modules/platform/src/env.cppmalready provides, so global mode is unset and the prior value is restored when the command returns.
mcpp prints xlings reported: <childError> from the NDJSON error event
(package_fetcher.cppm:396). xlings' own [xim] error lines never reach the
user. When install_packages exits non-zero, mcpp appends xlings' error-level
lines to the diagnostic, bounded to the last 20, each prefixed so it reads as
xlings' words.
- A unit test of the environment function: global mode produces "unset" on both platforms, project mode produces the path, and the process environment is unchanged afterwards.
- A failing install's
[xim]error line appears in mcpp's error output.
runtime.deploy_files is an explicit, platform-neutral list of strings, readable
from [runtime] in a manifest (toml.cppm:2030) and from a package's exports
(xpkg.cppm:2080). Each entry becomes DeployFile{source, dest} with
dest = bin/<filename> (plan.cppm:1182-1197). Two readers consume it: the
collision check (flags.cppm:1267) and the copy edges
(ninja_backend.cppm:653).
An older mcpp reading deploy_files with a table entry does not report an error.
Its reader calls read_string(), which returns an empty string without
advancing when the next token is {, and the loop around it never terminates.
A published descriptor that extended deploy_files would hang every older
client that resolved it. The runtime table, by contrast, skips sub-keys it
does not know. The table form therefore takes a new sub-key, deploy.
runtime = {
deploy_files = { "bin/vulkan-1.dll" }, -- unchanged
deploy = {
{ from = "lib/libMoltenVK.dylib", to = "." },
{ from = "share/vulkan/icd.d/MoltenVK_icd.json", to = "vulkan/icd.d" },
},
},fromis relative to the package root;tois a directory relative to the executable's directory. Both obey the string rules the payload descriptor applies tofrontend:/-separated, not absolute, no drive, no.or..component, except thatto = "."names the executable's directory itself. Anything else is refused by name.destbecomesbin/<to>/<filename>. Both readers key on the full relative destination, so two files with the same name in different directories do not collide, while two sources for one destination still do.- Honoured on every object format. DLL discovery through
runtime_search_dirsstays DLL-only, a PE loader rule. - The manifest's
[runtime] deployaccepts the same table. mcpp packcarries the files at the same relative paths.
- A dependency deploying a file to
.and another to a nested directory: aftermcpp build, both exist atbin/andbin/<to>/; aftermcpp test, the test binaries see the same layout. - A plan built from
deploy_filesstrings only is byte-identical to today's. to = "../x",to = "/x", a backslash, and a missingfromare each refused, naming the package and the entry.- An older mcpp resolves a descriptor carrying
runtime.deploywithout error, which is the reason for the new key.
The 2026-09-11 record and examples/13-platform-targets/mcpp.toml state that a
tool cannot be declared per target, citing
error: [target.aarch64-ios-sim.xlings] does not accept 'deps'. The refusal is
real; the conclusion drawn from it is not. [target.<selector>.xlings.workspace]
is accepted, and its entries are folded into the same install list deps feeds
(toml.cppm, around line 2890). It is covered by
tests/unit/test_target_xlings_axis.cpp and tests/e2e/625.
Measured 2026-09-12 on linux-x86_64 with mcpp 2026.9.12.1: with
"xim:apple-simulator-tools" = "" under [target.aarch64-ios-sim.xlings.workspace],
a host mcpp build exits 0 and never mentions the macOS-only package, and
mcpp build --target aarch64-ios-sim is refused at the SDK gate.
What is missing is three statements that point to it:
- The refusal gains a second sentence naming
[target.<selector>.xlings.workspace]. examples/13-platform-targetsdeclaressimctl-run's package beside the row that uses it, and its README drops the manualxlings installstep. The iOS CI fixture does the same.- This record states the correction; the 2026-09-11 record is not edited.
What was recorded. openkal-emscripten's README states that mcpp has no channel for a flag that applies to a whole dependency graph, citing:
error: POSIX thread support was disabled in precompiled file
'.../pcm.cache/openkal.types.pcm' but is currently enabled
What was measured, 2026-09-12. The channel is [build] dialect_cxxflags,
which docs/04 describes as applied "to the std BMI prebuild, the module scan and
every translation unit in the graph, including dependencies", and which enters
each dependency's cache key (cache_key.cppm, dialect_flags). A program that
references kal_task_start, with the threads feature on:
| root manifest | result |
|---|---|
dialect_cxxflags = ["-pthread"], ldflags = ["-pthread"] |
links; 16 -pthread in build.ninja, including the global cxxflags line; the generated JavaScript mentions SharedArrayBuffer and PThread |
cxxflags = ["-pthread"], ldflags = ["-pthread"] |
the recorded error, verbatim, on openkal.task.pcm |
The recorded failure put the flag in the per-package channel.
What remains.
- Scoping.
dialect_cxxflagsis not a conditional key:[target.<selector>.build]accepts only build inputs, so a portable manifest cannot limit-pthreadto the Web target. - Portability.
-pthreadis a GNU-driver spelling. A manifest that builds the same program with cl has no correct value to write. - A requirement. openkal-emscripten's
threadsfeature cannot state that it needs the switch, so a consumer who forgets it gets a precompiled-module mismatch instead of a sentence.
Decision: a typed abi table, first member threads. Reason 2 is the same
reason §1 prefers a field to ldflags, and reason 3 is only possible with a
typed value. The table is built now so that a later graph-wide ABI switch has a
place that is not another free-form flag list.
[target.'cfg(os = "emscripten")'.abi]
threads = true- A sub-table of
[target.<selector>], so it takes a triple or acfgpredicate and is evaluated against the resolved target, like.build. - Rendered through the existing graph-global dialect channel:
-pthreadjoinsplan.dialectFlags, and therefore the std module prebuild, the scan, every translation unit and every dependency's cache key, and-pthreadjoins the link. That is for GNU-style drivers (gcc, clang, em++). MSVC-style drivers render nothing, since the MSVC runtime is always multithreaded. - Only
threadsis accepted. An unknown member is refused naming the accepted set, so the table cannot become a flag list. - A feature or a package states the requirement with
requires_abi = { threads = true }. When the resolved target'sabidoes not satisfy it, resolution refuses, naming the package, the feature, the member and the manifest line that would satisfy it. An older mcpp reports the unknown key and skips it, so the key can be published. dialect_cxxflagsstays the raw channel for flags that have no typed member. It does not become conditional in this change: the typed member covers the measured case, and a conditional raw flag would bring back reason 2.
Criteria.
- With
threads = trueunder the Web target'sabitable, thethreadsfeature of openkal-emscripten links, and a program that starts a task runs under node. - Without it, a feature declaring
requires_abi = { threads = true }is refused at resolution, naming the key. - A dependency's cache key differs between the two builds, and a host build of the same manifest is byte-identical to one without the table.
- An unknown member, and a non-boolean
threads, are refused by name.
Nothing in mcpp is wrong: microsoft/STL#6294 is open upstream. mcpp is still the
tool that assembles clang with the MSVC STL, so docs/20-toolchains.md and its
Chinese copy gain a second "Known Toolchain Hazard" section beside the existing
one. It states the error text, the affected STL release (14.51), the upstream
issue, and the two workarounds measured downstream: an explicit operator== on
the element type, or an older runner image. There is no engine change and no
detection heuristic. #609 is closed as documented upstream tracking.
Measured 2026-09-12. In a project with no dependencies:
| step | command | result |
|---|---|---|
| 1 | mcpp build |
Resolved gcc@16.1.0, builds into target/x86_64-linux-gnu/9dde3d1f4b99cc99 |
| 2 | mcpp build --toolchain llvm@22.1.8 |
Finished dev in 0.00s; nothing resolved; the same directory; the gcc artefact is left in place |
| 3 | the same command in a fresh copy | Resolved llvm@22.1.8, builds into 1e0091d91feafd6c, and the artefact's .comment names clang 22.1.8 |
Step 2 is a build that reports success with the wrong compiler. It also skips every resolution-time check, including the §2.2 refusal. That is how this defect was found: the §2.2 measurement first read "exit 0" under llvm, because the llvm build never happened.
Fix. The fast path is taken only when the inputs that choose the toolchain
are the ones the recorded build used. The command-line override is such an
input, beside the manifest's [toolchain] and the global default, so a
different override declines the fast path and resolution runs. Every other
command-line input that changes resolution is checked in the same pass, and the
rule is stated once, as a named set.
Criteria. An end-to-end test of A-B-A: build with the default, build with
--toolchain llvm@22.1.8, build with the default again. After each step the
artefact's own .comment section names the expected compiler, and step 2
resolves rather than replays. The test is run once with the fix removed, and
must fail there.
Each angle states what the design does, and what would be wrong with the alternative.
- Architecture. Every change lands on a mechanism that already exists:
LinkUnitand the PE link-flag predicate (§1), the layer requirement check (§2), theruntimetable's skipping of unknown sub-keys (§4), the xlings workspace selector (§5.1), the graph-global dialect channel and the dependency cache key (§5.2), and the fast path's recorded inputs (§7). No new channel carries raw flags. - Stability. T1 removes a silent wrong-compiler build. Every new key refuses malformed input by name rather than ignoring it.
- Simplicity. Two gaps recorded as missing engine features are closed with statements and examples (§5.1) or with an existing requirement grammar (§2.2). New keys exist only where a raw flag cannot express the intent: the subsystem and entry (§1), and the thread ABI (§5.2).
- User experience. A failure that surfaced as a link error, a precompiled-module mismatch or a silently wrong artefact becomes a sentence naming the key that fixes it. Names follow the convention of the ecosystems users arrive from (§1.2).
- Compatibility. No existing key changes meaning. New descriptor keys are
placed where older parsers skip rather than hang:
runtime.deploy(§4.2),requires_abias an unknown feature key (§5.2).console/mainrender nothing, so no existing Windows command line changes. - Cross-platform. Rendering is decided by the object format and the PE link-flag predicate; a key that means nothing for a format is inert and byte-identical there. The Windows criteria run on Windows CI, not on Wine.
- Consistency. One vocabulary per concept:
mcpp:c++-abiis the standard library requirement,windows_*keys are Windows-only,abiholds graph-wide ABI switches, and every path rule is the descriptor'sfrontendrule. - Seamless upgrade. A manifest that builds today builds identically after the
change. The one behavioural change a user can observe is T1: a
--toolchainbuild that used to replay now builds with the toolchain it names. - Test coverage. Each section lists criteria, including the inert case on other platforms and a refusal for each malformed shape. Each new unit test is run once with its fix removed. The sandbox verification gains checks for §4, §5.1, §5.2 and §7 on published artefacts.
Rejected in review. A per-target ldflags key (§1.1); a new
abi.cxx_stdlib declaration, superseded by the existing layer requirement
(§2.2); extending deploy_files with tables, which would hang older clients
(§4.2); a general whole-graph flag list (§5.2); making dialect_cxxflags
conditional (§5.2).
- The subsystem value is
"windows", as in Rust and Meson. windows_entryships in the same change.- For #613, a resolve-time refusal is sufficient; a variant-keyed store is left to xlings. The refusal turned out to exist already (§2.2).
- The graph-wide switch is an
abitable rather than a single key, built now.
repo id task depends on
------------------ --- ------------------------------------------------------- ------------
mcpp M1 §5.1 refusal text, examples/13, iOS CI fixture -
mcpp M2 §6 docs/20 + zh hazard section -
mcpp M3 §7 fast-path inputs; A-B-A e2e -
mcpp M4 §3 xlings environment function; error surfacing -
mcpp M5 §2.3 check order; hook environment; docs M4
mcpp M6 §4 runtime.deploy: parsers, plan, readers, pack, tests -
mcpp M7 §1 windows_subsystem/windows_entry: parse, render, -
scope, directive, unit tests, Windows e2e, docs/04
mcpp M8 §5.2 abi table: parse, render, cache key, requires_abi, -
unit tests, wasm e2e, docs/20, docs/06
mcpp M9 CHANGELOG (the unreleased 2026.9.12.1 entry folds into M1-M8
the new version), version, record status, index
mcpp M10 CI green, self-review, release, mirrors, index bump, M9
sandbox verification, bootstrap pin
openkal-emscripten E1 README correction (§5.2); `threads` feature declares M10
requires_abi; CI engine pin; a task program runs
mcpp-index E2 compat.mysql-connector-cpp declares M10 (and the
`requires = ["mcpp:c++-abi=libstdc++"]` index floor)
M1 to M4, M6, M7 and M8 are independent and are implemented in parallel. The three ecosystem changes follow the release, because each adopts a key only the new engine reads, and E2's descriptor must be checked against the index's minimum engine version before it is published.
Each item states what the sections above said, what was measured or read in the code, and what was built instead.
- §1.3, the discriminator. The ABI is not read from
plan.rcStyle.LinkUnitcarries the declared words, and the emitter renders them, because only the emitter knows whether the link is a separate linker invocation, which decides between/SUBSYSTEM:and-Wl,/SUBSYSTEM:. The ABI is answered bype_msvc_abi, extracted frompe_link_flag, so the import library and the subsystem cannot address two different linkers. - §1.3, the MSVC row. Both
/SUBSYSTEM:and/ENTRY:<entry>CRTStartupare written whenever either key differs from its default. Without/SUBSYSTEM:, link.exe infers the subsystem from the entry function the objects define, soWinMainwith the console subsystem would link as a GUI program; without/ENTRY:, the GUI subsystem selectsWinMainCRTStartup, which a portableint main()does not satisfy. - §2.3 item 1, the order. The layer requirement check runs in target-side resolution, after the dependency graph is installed, and it is not moved ahead of provisioning. A package's manifest may live inside its payload, so the complete set of requirements is known only after installation. The hook environment is what lets an install hook refuse before it compiles.
- §2.3 item 2, the values.
MCPP_TARGETin a hook follows the build-program rule: the requested triple, or the host triple for a native build. The six values are computed byinstall_hook_env, from which the build-program environment also takes them, in their existing order, so no build program's re-run key changes. The toolchain values are empty while a dependency installs, which §2.3 assumed otherwise:tcis resolved after the dependency graph, because a package in the graph may supply a target-side layer, so no compiler or standard library has been decided when a dependency's hook runs. Measured with tests/e2e/648, whose hook compiled in an emptycompiler=andstdlib=beside the host'sos=linux. Resolving the toolchain before installation is the reorder the engine deliberately does not make, and a guessed value would let a hook build the wrong variant, so the variables are emitted empty, which also keeps a value inherited from a parent process out of the hook. Neither §2.4 criterion holds as written: the refusal follows the hook (item 3), and a hook cannot print a resolvedstdlibId. What holds is that thec++-abirefusal names both implementations before compilation, and that the hook sees the build's target and never an inherited toolchain value. - §3.1, absent and empty on Windows. The CRT defines
_putenv_s(key, "")as removal, so the Windows branch already produced an absentXLINGS_PROJECT_DIR, and the two platforms did not disagree about global mode. What was wrong on Windows was the lifetime: the value stayed in mcpp's environment after the invocation. The asymmetry that did exist was on POSIX, where theinstall_packagesfallback spelled global mode by hand whatever the project directory was. - §3.2, the scope of the guard. Only
XLINGS_PROJECT_DIRis scoped.XLINGS_HOMEand the PATH prefix are left process-wide on Windows, as before; scoping them has not been measured on Windows and is not part of this change. The hook variables of §2.3 are applied by the dependency installer's own scope rather than by the xlings environment function. - §4.3, the TOML form. mcpp's TOML layer refuses an array of tables in any
section not on an allowlist, and
runtime.deployhad to join it. The unit test written for the key reported this before an end-to-end test or a user could. - §5.2, rendering. Rendering is decided by the object format rather than by
the driver:
-pthreadon every target that is neither PE nor freestanding, nothing on PE and nothing on a freestanding target. The switch reaches the root'sdialect_cxxflags,cflagsandldflagsand every dependency'scflags. The MinGW driver would accept-pthread; it is not rendered there, and threads on that ABI are outside this change's criteria. - §7, the named set. A recorded build is replayed only for the same target
triple, profile, cache mode, requested features and toolchain request. The
toolchain request is the command-line override (
--toolchain,MCPP_TOOLCHAIN) together with the machine default ([toolchain] default).--offline,--lockedand--jobschange how a resolution is fetched, checked or executed, not what it chooses, and are not compared. An entry written before thetoolchain=line declines once. - §7, the criterion. A CI runner has one toolchain family installed, so the
A-B-A test requests the platform's own toolchain through
--toolchainand asserts that resolution runs, which the fast path skips. The machine-default leg switches to a second installed version of the same family and asserts the version string in the artefact; it reports itself as not measured where no second version is installed. - Outside this change: the fast path is taken only for ELF artefacts.
tests/e2e/645's control, two unchanged plain builds, found the second one
resolving the toolchain on macOS and on Windows CI.
try_fast_buildrequiresvalidated_artifact_snapshot, which requires aPassruntime-validation verdict for every artefact, and only an ELF artefact records one; a Mach-O or PE build therefore always takes the full path. The decline predates this change (#400). 645 distinguishes the two causes by thetoolchain=lines the two builds record: identical lines report the host as not measured, and differing lines fail.