batch: #261 windows cmdline, #257 clang depfile, #258 conditional per-glob flags, #254 host/target axis — 0.0.102#264
Merged
Merged
Conversation
… one --strict policy
Every branch that does LESS work because a precondition was not met used to
be silent or debug-logged, which is the same thing from the user's side.
mcpp.diag makes that reporting explicit and mandatory:
- degraded() requires an 'impact' string, forcing the author to state what
the user will actually experience — the sentence missing from every
silent-degradation bug this channel exists to prevent.
- records dedupe on their whole payload and render as they are reported,
so warning/status interleaving and early-return behaviour are unchanged.
- flush(strict) settles the --strict policy in one place.
Migrates prepare.cppm's eight raw println(stderr, "warning: ...") sites
(schema, platforms, feature forwarding/request, scanner, validator) onto the
sink. Rendering still goes through ui::warning, so output is byte-identical
and existing e2e greps keep matching. The 11 ui::warning call sites in other
files are left alone — out of scope for this batch.
Design: .agents/docs/2026-07-22-v0.0.102-batch-254-261-design.md section 2
…per and its 8191 ceiling The clang scan rule produced its P1689 JSON through shell redirection. Ninja on Windows spawns via CreateProcess, which does not interpret `>`, so the rule was wrapped in `cmd /c` — and cmd.exe caps a command line at 8191 chars, a quarter of CreateProcess's 32767. That ceiling is reached in practice: Sunrisepeak/opencv-m consumed as a dependency measured 8370-8570 char scan commands (48 -I entries, ~124 chars of package-root prefix at a workspace-member path), and all six scan invocations failed with "The command line is too long". The same package builds green in its own CI, where the path prefix is 23 chars. clang-scan-deps has -o since LLVM 17 — verified on both bundled toolchains (20.1.7, 22.1.8) — so the redirect is unnecessary. Writing the output via a flag also makes the clang branch match what the GCC (-fdeps-file=) and MSVC (/scanDependencies) branches already do, and collapses the two platform branches into one rule text. `cmd /c` was the only shell mcpp put between ninja and a compiler; a regression test keeps it gone. Note this only raises the ceiling to 32767 — the include-dir list itself is still unbounded, which the following commit addresses.
…cal_includes Dropping the cmd /c wrapper raised the scan rule's ceiling from 8191 to 32767, but did not address the axis: local_include_flags() emits one -I per dependency include dir with no upper bound, and cxx_module / cxx_object / c_object / asm_object / cxx_scan all inline that payload. The scan command is strictly longer than the compile command for the same TU, since it wraps it, so it just happens to fail first. Generalises the #247 link-rule mitigation to every rule that carries an unbounded flag payload: on Windows the payload moves into `rspfile_content` and the command references `@$out.rsp`. Safe for all consumers — the gcc and clang drivers and cl.exe expand @file, and clang-scan-deps passes an @file in its post-`--` command straight through to the driver (verified against the bundled 20.1.7). nasm_object is deliberately excluded: NASM spells response files `-@ file`, not `@file`. Also switches escape_flag_path to generic_string(). This is the #247 trap one level down: response-file content is tokenized GNU-style, where backslash is an escape character, so a Windows -I path would lose its separators once it moved off the command line. Forward slashes are accepted by every Windows consumer and POSIX output is unchanged. useCompileRsp keys on `is_windows || msvcDeps` rather than is_windows alone. The msvc dialect only ever runs on Windows, so this is faithful, and it makes the response-file shape reachable from a POSIX test host — the same over-approximation the link rules already use. POSIX output is byte-identical, guarded by a test asserting the compile rules stay inline there. New e2e 148 builds a package with 64 include dirs on every platform: the failure was found from the consumer side, where a deep dependency path pushed an otherwise-green package over the limit.
… from "filter GCC module rules"
Editing a file pulled into a module interface via a purview #include did not
retrigger the interface rebuild under Clang: the next build reused the stale
BMI, so importers compiled against the old surface. It failed silently, which
is the worst shape a build system can take — the wrong answer arrives looking
like a correct incremental build, and suspicion falls on user code first.
The issue reporter lost a debugging round to it.
Root cause is a conflated predicate from 0.0.97. `posixDepfile` decided BOTH
whether to emit a depfile at all AND whether to run the awk filter that
strips the reversed make-rules GCC's `-fmodules -MMD` bolts on. Clang's
depfile shape was unverified at the time, so the filter was scoped to GCC —
and the depfile went with it. Measured now, on both bundled toolchains:
clang 20.1.7/22.1.8: x.o: x.cppm ops.inc
gcc 16.1.0: x.o gcm.cache/x.gcm: x.cppm ops.inc
x.c++-module: gcm.cache/x.gcm
.PHONY: x.c++-module
gcm.cache/x.gcm:| x.o
Clang emits a single plain rule — nothing the filter would remove. The gate
was protecting against a shape that does not exist, at the cost of the
correctness contract it was bundled with.
Split into `posixDepfile` (any POSIX non-MSVC toolchain) and
`needsGnuModuleFilter` (GCC only, which alone writes to the scratch
$out.d.raw). Clang writes -MF straight to $out.d.
Also fixes the other half of the same asymmetry: c_object and asm_object had
no depfile on any toolchain, so C and GAS units never tracked their headers.
They take the flag but not the filter — module reversed-rules cannot occur
there.
Windows non-MSVC (mingw gcc / hosted clang) remains without include
tracking, since the GCC filter needs awk. That is now reported through
diag::degraded with its user-visible impact instead of being silent. cl.exe
is unaffected: deps=msvc via /showIncludes is the equivalent mechanism, not
a degradation.
e2e 118 drops `# requires: gcc` and gains a Clang leg exercising both
assertions against the newest installed LLVM (verified locally on 22.1.8).
…l axis may contribute mcpp has two conditional axes: `[target.'cfg(...)']` (platform) and `[features.<name>]`. Each hand-picked which build fields it could carry, and they picked DIFFERENT subsets — cfg took cflags/cxxflags/ldflags/sources, features took sources/defines/flags. "Which build inputs may be contributed conditionally" was being decided in two places, differently, which is the architectural debt the batch ledger names: the same decision derived twice drifts, and the drift only surfaces when someone tries to use the capability one axis has and the other silently lacks (#258). Extracts the additive build inputs into a type. Membership is now answered BY the type rather than by a list someone has to remember to update, and two properties qualify a field: appending is its merge semantics, and it is consumed after the conditional merge point. That excludes the selection axis (`target` SELECTS the triple its own predicate is evaluated against — conditioning it is circular) and the resolved policy scalars, which would need last-wins override semantics: a different operation and a separate design. BuildConfig INHERITS BuildInputs rather than nesting it. `buildConfig.cflags` is read in ~150 places and a BuildConfig genuinely is a set of build inputs plus selection and policy, so nesting would be 150 mechanical edits buying nothing. ConditionalConfig, by contrast, nests it — that is where the guarantee has to bite, and it costs ~20 call sites. merge_conditional_sources_flags becomes merge_conditional_build_inputs and folds through a single append(BuildInputs&, const BuildInputs&), so "how does a contribution combine with the base" has one answer for every axis: append in declaration order, giving later entries GNU last-wins precedence. Pure refactor: no behaviour change, and every existing unit assertion passes unmodified apart from the mechanical cc.<field> -> cc.inputs.<field> path. Design: .agents/docs/2026-07-22-v0.0.102-batch-254-261-design.md section 5
….)'.build] (xpkg target_cfg parity) With BuildInputs in place this is mostly plumbing: a conditional section is a set of build inputs, so it reads through the same entry grammar [build].flags uses, and the merge already appends whatever BuildInputs carries. What it unblocks. `sources` was OS-conditional but `flags` was not, so one manifest covering three OSes had to present every OS with the other two OSes' flag entries, each matching zero sources by construction. The vendored-opencv port paid for that with 703 committed stub files whose only purpose was to give windows TUs OS-unique PATHS a global flag table could key on, 32 globs pointing at them, and ~23 structurally-guaranteed dead-glob warnings on every build of every OS. This is the engine's missing expressiveness leaking out as filesystem structure — the same shape as the pre-0.0.97 tu-stub machinery that #233 deleted. Ordering carries the semantics: conditional entries append AFTER the base table, so under GNU last-wins a conditional rule beats a broader unconditional one. That is what makes an off-OS REMOVAL expressible — the windows leg needs `-UHAVE_UNISTD_H` against a base `-DHAVE_UNISTD_H=1`, which no unconditional overlay can express (a `-U` counter-entry would itself need to be windows-only, recursing into the same gap). The dead-glob noise disappears structurally, not by suppression: an off-OS entry never enters buildConfig.globFlags, so globFlagHits has no counter for it and scanner.cppm cannot warn. Same shape as #253's fix on the feature axis. The `optional = true` / `?`-prefix escape hatch floated in the issue is deliberately NOT added — it would be a second suppression mechanism alongside #253's "the entry conditionally does not exist", i.e. the same decision derived twice again. xpkg's target_cfg gets the same key for grammar parity, and its unknown-key handling stays a HARD error. Sharing a parser must not be read as licence to relax a grammar that was already closed; making the unknown-key policy consistent across all sections is #263, deliberately not bundled here since it has a compatibility surface and #258 does not depend on it. e2e 149 asserts the three costs are paid off: matching-OS entries reach the TU, they override the base table, and off-OS entries produce no warning — with a control proving a genuinely dead glob in [build].flags still does.
…es; xpkg per-OS sections key on the target
mcpp resolves "which platform?" along two axes that coincide only for a
native build: the host (where mcpp runs — correct for toolchain payloads and
other host tools) and the target (where the produced binaries run — correct
for anything compiled INTO the user's build).
Both were std::string_view, so mixing them cost nothing and read identically,
and they had drifted apart: every xpkg per-OS splice and every xpm
version/asset table keyed on `mcpp::platform::xpkg_platform`, a COMPILE-TIME
HOST constant, while `[target.'cfg(...)']` was evaluated against the resolved
target. The same decision, derived two different ways. Cross-compiling
therefore spliced the host's section into a dependency being built for the
target: its per-OS sources, flags, deps and prebuilt asset all came from the
wrong leg. Native builds cannot observe this, which is why three-platform CI
never caught it.
Introduces mcpp.platform.axis: HostPlatform and TargetPlatform, with no
conversion between them and no constructor from a bare string. APIs take the
common PlatformKey base, so a function states which axis it accepts and a
call site must NAME the axis it supplies. `synthesize_from_xpkg_lua`'s
platform parameter loses its host default and becomes required — a silent
default is precisely how this bug survived. The triple-vs-xpkg vocabulary
translation ("macos" vs "macosx") moves into TargetPlatform::for_os so no
call site has to remember it.
The type change and the behaviour fix land together because they are the
same edit: once the parameter is required, every call site must decide, and
those decisions ARE the fix. Classified per site:
prepare.cppm dep manifest synthesis (3), pm/resolver version+asset
selection -> TARGET; these are code compiled into the user's build.
toolchain/lifecycle version listing -> HOST; a toolchain payload runs on
this machine. Its existing comment already said so.
cmd_xpkg --all-os -> for_lint_of(); neither axis, and named awkwardly on
purpose so that reads as deliberate at the call site.
scaffold `mcpp add` and the legacy resolver overloads -> HOST, stated
explicitly rather than inherited from a default.
Tests: static_asserts that the axes are mutually inconvertible and neither is
constructible from a string; per-OS splice selects each target's own section
and none of the others; and a native-build guard asserting host == target
produces an identical splice, which is the safety net for every existing
platform's behaviour.
…e hazard docs mcpp has no defect here: --precompile succeeds and the crash is inside the Clang frontend on the import side. But mcpp bundles LLVM, so the hazard ships with the toolchain, and it lands squarely on the module-package pattern this project recommends — wrapping an upstream header whose operators are static inline templates and mirroring their signatures with a trivially-true constraint is exactly how you hit it. Two things it needs, both of which mcpp owns: Documentation, so the next person does not repeat the bisect: every template parameter of an exported operator template should be pinned by the FIRST argument; otherwise deduce whole operand types and constrain them. The reporter's shipped workaround is included, along with the property that makes this so hard to attribute — the crash is name-keyed, so one poisoned operator* makes every `x * y` in every importer crash on unrelated types. A canary, because the alternative is finding out by accident. It is a STATE canary rather than a pass/fail contract: the expectation table records crash for LLVM 20-22 and ok for 18-19, and the test fails when reality stops matching — a Clang bump that FIXES this is just as much a signal as one that re-breaks it, and neither should be able to change what packages can express without anyone noticing. A control build without the unpinned overload guards the canary itself. Verified against both bundled toolchains (20.1.7 and 22.1.8 crash as recorded; system clang 18.1.3 and gcc 16.1.0 compile the same repro fine). An upstream LLVM report is filed separately; this does not wait on it.
…invariants Version bump plus the three documentation surfaces this batch owes: - CHANGELOG entry covering #261 / #257 / #258 / #254 and what was explicitly left out (#259, and the unknown-key policy split into #263). - docs/05-mcpp-toml.md: the new conditional keys, and — more usefully than a key list — WHY the set is what it is. A conditional section accepts exactly the additive build inputs; linkage/target/profile knobs are excluded because they are inputs to target selection or need override semantics. Includes the per-OS removal idiom and the "off-target entries do not exist, so they cannot warn" rule. - Batch ledger gains a section 6 for cross-batch invariants: the existing "one decision, one derivation" rule with #254/#258 recorded as instances, and the new "failures must be audible" rule with its rationale — three of this batch's four issues were the same silent-degradation shape, in a code base that already practiced the discipline elsewhere but had never made it an invariant new code inherits.
…d, .s depfile split, feature axis folds through append Three findings from the whole-branch review, two of them defects introduced by this batch. 1. diag::flush() was never called outside its own unit test. The one new degradation this branch introduces (#257's windows + non-MSVC "no depfile" case) would print its warning and then be ignored, and `mcpp build --strict` would still exit 0. The channel built to stop silent degradation was itself silently degrading. BuildContext now carries `strict` and run_build_plan calls flush() after the build — after, because backend emission is where degradations are discovered, so end-of-prepare_build would be too early. 2. `-MMD` was applied to `.s` as well as `.S`. The C driver preprocesses `.S` but not `.s`, so clang emits two `argument unused during compilation` warnings per file and writes no depfile (measured on 20.1.7; gcc writes none either, just quietly) — and ninja's `deps = gcc` treats an absent depfile as an error, so the cases cannot share a rule. Split into asm_object (.S, tracked) and asm_object_raw (.s, pre-#257 shape). 3. The feature axis was still folding its per-glob flags field-by-field rather than through append(BuildInputs&), so the "one funnel for both axes" claim held for only one axis. Now routed through append. The other two feature tables stay out on purpose and the comment says why: feature `sources` carry DROP-then-ADD semantics, and feature `defines` are interface contributions that propagate along Public edges — neither is a plain append, so neither belongs in BuildInputs. Also fixes an ordering bug found while re-reading the #254 change: the target platform was computed at the top of prepare_build, but overrides.target_triple is only filled in from `[build] target` / the config default and canonicalized some 200 lines later. Any project that sets its target in the manifest rather than on the command line would have silently fallen back to the host axis — the exact bug #254 is about, reintroduced one level up. Moved below the resolution block, with a comment recording why it cannot move back.
The planned mingw-cross e2e consuming an xpkg dep with per-OS sections did not land: it needs a real cross build inside a throwaway MCPP_HOME, and a fresh home re-installs the entire cross toolchain (measured >10 min). The e2e harness has no fixture for reusing the host registry while injecting a local index and a pre-placed payload. Rather than ship a test that times out, the semantics stay pinned by three unit tests and the gap is written down in both the design doc and the changelog, with what building the fixture would take.
…caught the #247 trap one level deeper The Windows leg failed with module file 'D:amcppmcpptargetx86_64-windows-msvcc1ffdc8b90b687f4pcm.cachestd.pcm' not found i.e. D:\a\mcpp\mcpp\target\...\pcm.cache\std.pcm with every separator eaten. Response-file content is tokenized GNU-style, where backslash is an ESCAPE character. Switching escape_flag_path to generic_string() covered the -I paths this file builds itself, but $cxxflags carries paths produced elsewhere — flags.cppm's -fprebuilt-module-path= and module-file mappings — which are still native-separated. Moving those off the command line broke every `import std;`. The fix is to scope the response file to what this file can guarantee the form of: $local_includes only. $cxxflags / $unit_cxxflags / $asmflags stay inline, where a backslash is just a character. That is also sufficient for #261: the unbounded axis is one -I per dependency include dir, and the rest of the payload is bounded. Making every flag producer emit forward slashes would be the more general fix, but it is a much wider change than this batch should carry, and it is not needed for the ceiling. Only the `mcpp test` step failed, not the build: the build step runs the bootstrap mcpp, so the freshly-built binary is first exercised when it compiles the test targets. Also makes e2e 118 use a portable in-place edit. Dropping its `# requires: gcc` gate in #257 meant it ran on macOS for the first time, where BSD sed reads `-i`'s next argument as a backup suffix and swallows the script. The other three new e2e tests pass on macOS as-is.
…a capability it lacks Windows plus a GNU-dialect toolchain — which is what the CI leg's clang is — genuinely cannot track textual includes: the depfile GCC emits for a module TU needs an awk filter to be loadable by ninja, and native Windows has no awk. #257 does not fix that platform; it makes the engine SAY so through diag::degraded, and the Windows CI log carries that message 13 times. Dropping the `# requires: gcc` gate pointed the test at that platform for the first time, where it asserted a rebuild that cannot happen. Asserting the capability there would be wrong, and re-gating the test to POSIX would put the gap back out of sight. So on Windows the test now asserts the degradation IS reported and stops. That is the stronger contract: silence is the actual defect, and this pins it. Everywhere else the rebuild assertions are unchanged.
This was referenced Jul 21, 2026
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.
Four issues in one batch, plus the
#256known-issue canary. Design:.agents/docs/2026-07-22-v0.0.102-batch-254-261-design.md; the triage thatdecided which of the six reported issues were real:
.agents/docs/2026-07-22-issue-triage-254-261.md.Two architectural threads run through all four:
#254and#258are both the sameshape: a decision derived in two places that had drifted apart.
were the engine quietly doing less when a precondition was not met.
#261— windows scan-deps hits the 8191-char limitThe clang scan rule produced its P1689 JSON via shell redirection. Ninja
spawns through
CreateProcesson Windows, which does not interpret>, sothe rule was wrapped in
cmd /c— andcmd.execaps a command line at 8191,a quarter of
CreateProcess's 32767. Real measurement from the reporter:8370–8570 chars, all six scan invocations failing, for a package that is
green in its own CI and only fails when consumed as a dependency at a deeper
path.
clang-scan-depshas-osince LLVM 17 (verified on both bundledtoolchains, 20.1.7 and 22.1.8), so the redirect is unnecessary. That also
makes the clang branch match what the GCC (
-fdeps-file=) and MSVC(
/scanDependencies) branches already do.But
-oonly raises the ceiling; it does not address the axis.local_include_flags()emits one-Iper dependency include dir with noupper bound, and five rules inline that payload. So the
#247link-ruleresponse-file mitigation is generalised to every rule carrying an unbounded
payload. Along the way
escape_flag_pathswitches togeneric_string()—the
#247trap one level down, since response-file content is tokenizedGNU-style where backslash is an escape character.
POSIX output is byte-identical, guarded by a test.
#257— clang reused stale BMIs after a purview#includeeditEditing a file pulled into a module interface via a purview
#includedidnot retrigger the rebuild under Clang. It failed silently: the wrong
answer arrived looking like a correct incremental build.
Root cause was a conflated predicate from 0.0.97.
posixDepfiledecided bothwhether to emit a depfile and whether to run the awk filter that strips
GCC's reversed module rules. Clang's depfile shape was unverified then, so
the filter was scoped to GCC — and the depfile went with it. Measured now:
Clang emits nothing the filter would remove. The gate was protecting against
a shape that does not exist, at the cost of the correctness contract bundled
with it. Split into
posixDepfileandneedsGnuModuleFilter.Also fixes the other half of the asymmetry:
c_objectandasm_objecthadno depfile on any toolchain. (Review caught that
.sis not preprocessedwhile
.Sis, so they need separate rules — see below.)e2e 118 drops
# requires: gccand gains a Clang leg.#258— per-glob flags in conditional sectionssourceswas OS-conditional butflagswas not, so one manifest coveringthree OSes had to present each OS with the other two OSes' flag entries, all
matching zero sources by construction. The vendored-opencv port paid for that
with 703 committed stub files whose only purpose was to give windows TUs
OS-unique paths a global flag table could key on, 32 globs pointing at
them, and ~23 guaranteed dead-glob warnings per build. That is missing engine
expressiveness leaking out as filesystem structure.
The fix is not the feature but the type behind it. mcpp has two conditional
axes — cfg and features — and each hand-picked which build fields it could
carry, picking different subsets. Extracting
BuildInputsmakesmembership answerable by the type instead of by a list someone must remember
to update.
BuildConfiginherits it (~150 read sites unchanged);ConditionalConfignests it, which is where the guarantee has to bite.With that in place
#258is mostly plumbing. Conditional entries appendafter the base table, so GNU last-wins makes a per-OS removal
expressible — the windows leg needs
-UHAVE_UNISTD_Hagainst a base-D,which no unconditional overlay can express.
The dead-glob noise disappears structurally: an off-OS entry never enters
globFlags, so there is no hit counter to warn about. Theoptional = trueescape hatch floated in the issue is deliberately not added — it would be a
second suppression mechanism alongside
#253's.Two exclusions are by category, not convenience:
generatedFilesis aside-effecting materialization action, not an input;
featureDefinesareinterface contributions that propagate along Public edges. Per-glob
definesare private and per-TU, so those do belong — which is exactly whatthe opencv use case needs.
#254— xpkg per-OS sections keyed on the host, not the targetEvery per-OS splice and every xpm version/asset table keyed on a
compile-time host constant, while
[target.'cfg(...)']was evaluatedagainst the resolved target. Cross builds therefore spliced the host's
section into a dependency built for the target. Native builds cannot observe
this, which is why three-platform CI never caught it.
Both axes were
std::string_view, so mixing them cost nothing. They are nowdistinct types with no conversion between them and no constructor from a bare
string, and the platform parameter loses its host default — a silent default
is precisely how this survived. Every call site now names its axis; the
classification is in the commit message.
#256— clang module operator-template canaryNo mcpp defect:
--precompilesucceeds and the crash is inside the Clangfrontend on the import side. But mcpp bundles LLVM, and the hazard lands
squarely on the module-package pattern this project recommends. Adds a
documented rule (every template parameter of an exported operator template
should be pinned by the first argument) with the reporter's workaround,
and a state canary: LLVM 20–22 recorded as crashing, 18–19 as fine, and
the test fails when reality stops matching — a bump that fixes this is as
much a signal as one that re-breaks it.
Reproduced locally across the full matrix: clang 20.1.7 crash, 22.1.8 crash,
system clang 18.1.3 ok, gcc 16.1.0 ok; control without the unpinned overload
ok on both.
Architecture review pass
The whole-branch review found three things, two of them defects this batch
introduced:
diag::flush()was never called in production. The--strictpromotion mechanism was dead code outside its own unit test, so the one
degradation this branch adds would print and then be ignored. The channel
built to stop silent degradation was itself silently degrading. Now
settled in exactly one place, after the build (degradations are discovered
during backend emission).
-MMDwas applied to.sas well as.S. The C driver preprocesses.Sbut not.s; measured, clang emits twoargument unusedwarningsper file and writes no depfile, and ninja's
deps = gcctreats an absentdepfile as an error — so they cannot share a rule. Split into
asm_objectandasm_object_raw.funnel for both axes" claim held for one axis. Now routed through
append(), with the two genuinely-different feature tables documented asto why they stay out.
Self-review separately caught an ordering bug in the
#254change: thetarget platform was computed before
overrides.target_tripleis filled from[build] targetand canonicalized, so a project setting its target in themanifest would have fallen back to the host — the same bug, one level up.
Verification
pre-existing environmental baseline (54/62 exit 127 on a missing
~/.xlingsgcc binary), unchanged by this branchquadrants), 150 (
#256canary); 118 extended with a Clang leg#257verified by hand under llvm 20.1.7 and 22.1.8#261's-oand@rsppass-through verified against the bundledclang-scan-deps
What CI caught that local testing could not
Two of the fixes in this PR exist because the first CI run failed. Both are
worth recording, because they are the class of bug this batch is about.
Windows: the
#247trap one level deeper. The response-file change brokeevery
import std;:D:\a\mcpp\...\pcm.cache\std.pcmwith every separator eaten. Switchingescape_flag_pathtogeneric_string()covered the-Ipaths this filebuilds itself — but
$cxxflagscarries paths produced inflags.cppm(
-fprebuilt-module-path=, module-file mappings) that are stillnative-separated. The response file is scoped to
$local_includesonly now:the part whose form this file can guarantee, and the only part that is
actually unbounded. Everything else stays inline, where a backslash is just a
character.
Note only the
mcpp teststep failed, not the build — the build step runsthe bootstrap mcpp, so a freshly built binary is first exercised when it
compiles the test targets.
macOS: removing a gate exposes a test to a new platform. Dropping e2e
118's
# requires: gccfor#257meant it ran on macOS for the first time,where BSD
sed -ireads its next argument as a backup suffix. Fixed with aportable in-place edit; the three genuinely new e2e tests pass on macOS
as-is.
Deliberately out of scope
#259— root cause traced to a silent per-dep skip on the xlings side;the investigation (which retracts the issue's own attribution and found
that mcpp's sysroot safety-net has never executed) is posted as a comment
there.
sections, split out as manifest: unknown-key policy is inconsistent across five sections — [build] and [target.*.build] drop unknown keys silently #263. It has a compatibility surface and
#258doesnot depend on it.
#254cross-build e2e did not land; the harness lacks a fixture forreusing the host registry, and a throwaway
MCPP_HOMEre-installs thewhole cross toolchain. Semantics are pinned by unit tests and the gap is
written down rather than papered over.