Commit 5d71824
authored
fix(build): name artifacts from the target — cross builds relinked every time (B3) (#342)
* docs(b3): target-aware artifact naming — the real symptom is a relink every build
Writes up B3 properly and corrects what the original §6.5 claimed.
The original said it was 'symmetrically wrong' — Windows→Linux produces
mcpp.exe for an ELF, Linux→Windows produces mcpp for a PE. The second half is
false. Measured: a Linux host cross-compiling to x86_64-windows-gnu produces
b3probe.exe (PE32+), because mingw's GCC driver appends .exe itself when the
-o name has no extension. mcpp never participates in that decision.
The actual defect is elsewhere and matters more. ninja is told the output is
bin/foo while GCC writes bin/foo.exe, so the declared file never exists and
ninja reruns the link edge on every build. Verified by mtime across two
consecutive builds — the artifact is relinked every time. Incremental builds
are effectively off for PE targets, which is the path CI exercises daily.
That also explains why nothing caught it: 102_mingw_cross_wine.sh looks for the
real artifact (find -name '*.exe'), not for what ninja declared, so both of its
assertions hold while the inconsistency sits underneath them.
Two further findings the fix has to account for:
- naming is an (os, env) function, not an os one. windows-gnu uses the GNU
convention (libfoo.a); only windows-msvc is foo.lib. The current _WIN32
branch hardcodes the latter, so building a static library with mingw ON a
Windows host is already misnamed today — a pre-existing defect unrelated to
cross-compilation.
- the blast radius is much smaller than §6.5 feared. e2e and CI need
essentially no changes, precisely because they match the real artifact.
Also confirms the other 15 exe_suffix references are correct host semantics
(locating ninja / xlings / clang++ on the build machine) and must not be
touched; the change is confined to src/build/plan.cppm.
* docs(b3): resolve both open questions with a traced consumer chain
Q1 — windows-gnu static lib foo.lib -> libfoo.a: DO IT IN THE SAME PR.
The worry was that it changes a host build's output name. Tracing every
consumer shows the blast radius is empty:
- mcpp package deps link at OBJECT level (plan.cppm:836 splices dependency
.o files into lu.objects). A static library is never produced OR read as
part of an internal dependency edge.
- external prebuilt libs come through free-form ldflags; the name is written
in the package descriptor, mcpp never spells it.
- [runtime] library_dirs is a directory, not a name; the Windows side only
filters on the .dll extension for runtime deployment.
- fingerprint.cppm carries no artifact name but does carry MCPP_VERSION, so
any bump already rotates target/<triple>/<fp>/. No mixed state, no cache
migration, no "cache clean" advice needed.
So the only consumer is whoever takes the artifact outside mcpp — and the name
they get today is wrong: mingw's ar emits a GNU archive named foo.lib, claiming
an MSVC convention it does not satisfy. That is a pre-existing correctness bug,
not a nice-to-have, and shipping the (os, env) rule half-way would leave a state
harder to explain than the bug.
Q2 — PE import libs: DO NOT MODEL THEM YET. Draw the boundary instead.
All five shared-library e2e tests declare "# requires: elf", and that capability
is only added on the Linux branch of run_all.sh — Darwin gets "macos", Windows
gets "windows". Shared libraries have therefore never been verified end to end
on PE *or* Mach-O. This reframes the question: it is not a missing feature, it
is a path that was never walked while the code carries branches that look like
it was.
Those branches are speculation: mingw tolerates linking a .dll directly, MSVC's
link.exe cannot — and the branch keys on the host constant, so it points the
wrong way under cross-compilation anyway.
Recommends rejecting SharedLibrary on non-ELF targets with a clear error before
attempting to support it. An untested branch that also refuses to say no is the
hardest kind of debt — it can neither be trusted nor deleted, because nobody
knows who depends on it. Same shape as the offline-first code that a TTL gate
had quietly made unreachable.
Splits the work into three PRs accordingly; import lib support gets its own
design doc, gated on shared-library coverage existing for PE and Mach-O first.
* ci(cross): do not restore target/ in the windows->linux job
The job builds twice — once for the host, then once for x86_64-linux-musl — and
a cache-restored BMI tree makes the second build read std BMIs that no longer
match what the dependency BMIs were compiled against:
mcpplibs.cmdline: error: import 'std' has CRC mismatch
GCC bakes a CRC of each imported module's BMI into the importer, so the two have
to come from the same build round. A cache that restores one without the other
is not a partial speedup, it is an unbuildable tree.
Systematic, not flaky: it reproduced on rerun, and only in this job. The other
two cross jobs cache ~/.mcpp and ~/.xlings but deliberately never target/ —
this now follows the same convention.
It stayed hidden until a PR touched neither mcpp.toml nor .xlings.json, since
those two files key the sandbox cache; every earlier run had been a cold miss.
That also means it would have reddened every subsequent PR, so it is fixed
ahead of the B3 work rather than alongside it.
* test(cross): pin that a cross build declares the artifact it produces
Adds ArtifactNaming (a (os, env) function on the target triple) plus the
regression assertion for a defect that exists on HEAD today.
plan.cppm's target_output() spells the artifact suffix from
mcpp::platform::exe_suffix — a HOST constant. Cross-compiling Linux -> PE that
yields `bin/foo` while mingw's GCC driver writes `bin/foo.exe`, so the file
ninja was told to produce never appears. ninja finds the declared output
missing on every run and reruns the link edge forever.
The e2e asserts both the cause and its observable consequence: that the
declared ninja output exists, and that an up-to-date rebuild does not change
the artifact's mtime.
Verified RED before the fix:
FAIL: ninja declares output 'bin/relinkprobe' but that file does not exist
actually produced: relinkprobe.exe
=> the link edge can never be satisfied, so it reruns every build
The unit tests cover ArtifactNaming's own logic, including the part a single
_WIN32 branch cannot express: windows-gnu uses the GNU convention (libfoo.a)
while windows-msvc uses foo.lib. They pass a deliberately bogus host answer, so
any assertion leaking through to the host axis fails loudly.
None of the other cross tests could have caught this: they look for the REAL
artifact (find -name '*.exe'), not for what ninja declared, so both of their
assertions hold while the inconsistency sits underneath them.
Refs .agents/docs/2026-08-03-b3-target-aware-artifact-naming.md
* fix(build): name artifacts from the target, not from the build host
target_output() spelled the suffix and library affixes from
mcpp::platform::{exe_suffix,lib_prefix,static_lib_ext,shared_lib_ext} — host
constants selected by #if defined(_WIN32)/__APPLE__. On a host build the host
and target answers coincide, which is why it survived; they diverge the moment
host != target.
The consequence was not cosmetic. Cross-compiling Linux -> PE, ninja was told
to produce `bin/foo` while mingw's GCC driver writes `bin/foo.exe`, so the
declared output never existed and ninja reran the link edge on every single
build. Incremental builds were effectively off for PE targets — the path CI
exercises daily.
Naming now comes from ArtifactNaming, resolved once per plan from the target
triple. It is an (os, env) function, not an os one:
x86_64-windows-gnu -> libfoo.a (GNU/mingw)
x86_64-windows-msvc -> foo.lib (MSVC)
A single _WIN32 branch cannot express that, which is why building a static
library with mingw ON a Windows host produced `foo.lib` — a GNU archive wearing
an MSVC name. That is a behaviour change for that configuration, and it fixes a
name that was already wrong.
An empty triple means "build for this machine", and only there is the host
answer correct, so it is threaded in as the fallback rather than read directly.
Host builds are therefore bit-for-bit unchanged.
shared_library_link_flags gets the same treatment: whether a consumer links a
full path (PE), uses @loader_path (Mach-O) or $ORIGIN (ELF) is a property of
what we build FOR. Keying it on the host pointed it the wrong way under cross
builds.
Also refuses SharedLibrary on non-ELF targets. Every shared-library e2e
declares `# requires: elf` and run_all.sh grants that only on Linux, so those
paths have never been verified on PE or Mach-O — mingw's ld tolerates linking a
.dll directly, MSVC's link.exe cannot, and neither has an import library
because mcpp does not model one. A clear refusal beats emitting an artifact
nothing has ever checked.
Verified:
- e2e 183 red before, green after
- 53 unit tests pass
- 08_shared_library, 64_shared_soname_runtime_alias,
55/57_*_shared_artifact, 102_mingw_cross_wine all pass — the soname alias
edge is the one the design doc flagged as historically fragile
Refs .agents/docs/2026-08-03-b3-target-aware-artifact-naming.md
* release: 2026.8.3.3
Bumps the BUILDING pair only (mcpp.toml + fingerprint.cppm); the bootstrap pin
in .xlings.json stays at 2026.8.3.2 until this release exists and is reachable
through the index.
CHANGELOG calls out the windows-gnu static library rename explicitly: it is the
one change here that alters a HOST build's output name (foo.lib -> libfoo.a on
Windows + mingw), and it corrects a name that was already wrong.1 parent 9a696d2 commit 5d71824
10 files changed
Lines changed: 838 additions & 44 deletions
File tree
- .agents/docs
- .github/workflows
- src
- build
- toolchain
- tests
- e2e
- unit
Lines changed: 398 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 29 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
643 | 643 | | |
644 | 644 | | |
645 | 645 | | |
646 | | - | |
| 646 | + | |
647 | 647 | | |
648 | | - | |
649 | | - | |
650 | | - | |
651 | | - | |
652 | | - | |
653 | | - | |
654 | | - | |
655 | | - | |
656 | | - | |
657 | | - | |
658 | | - | |
659 | | - | |
660 | | - | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
661 | 676 | | |
662 | 677 | | |
663 | 678 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
307 | 307 | | |
308 | 308 | | |
309 | 309 | | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
310 | 329 | | |
311 | 330 | | |
312 | 331 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
6 | 26 | | |
7 | 27 | | |
8 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
197 | 198 | | |
198 | 199 | | |
199 | 200 | | |
200 | | - | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
201 | 226 | | |
202 | 227 | | |
203 | | - | |
204 | | - | |
| 228 | + | |
205 | 229 | | |
206 | 230 | | |
207 | 231 | | |
208 | | - | |
209 | | - | |
| 232 | + | |
210 | 233 | | |
211 | 234 | | |
212 | | - | |
| 235 | + | |
213 | 236 | | |
214 | 237 | | |
215 | 238 | | |
216 | | - | |
| 239 | + | |
| 240 | + | |
217 | 241 | | |
218 | 242 | | |
219 | 243 | | |
220 | 244 | | |
221 | 245 | | |
222 | | - | |
| 246 | + | |
223 | 247 | | |
224 | 248 | | |
225 | 249 | | |
| |||
232 | 256 | | |
233 | 257 | | |
234 | 258 | | |
235 | | - | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
236 | 273 | | |
237 | | - | |
238 | | - | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
239 | 279 | | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | | - | |
244 | | - | |
245 | | - | |
246 | | - | |
247 | | - | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
248 | 283 | | |
249 | 284 | | |
250 | 285 | | |
| |||
384 | 419 | | |
385 | 420 | | |
386 | 421 | | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
387 | 461 | | |
388 | 462 | | |
389 | 463 | | |
| |||
686 | 760 | | |
687 | 761 | | |
688 | 762 | | |
689 | | - | |
| 763 | + | |
690 | 764 | | |
691 | 765 | | |
692 | 766 | | |
| |||
767 | 841 | | |
768 | 842 | | |
769 | 843 | | |
770 | | - | |
| 844 | + | |
771 | 845 | | |
772 | | - | |
| 846 | + | |
773 | 847 | | |
774 | 848 | | |
775 | 849 | | |
| |||
812 | 886 | | |
813 | 887 | | |
814 | 888 | | |
815 | | - | |
| 889 | + | |
816 | 890 | | |
817 | 891 | | |
818 | 892 | | |
| |||
833 | 907 | | |
834 | 908 | | |
835 | 909 | | |
836 | | - | |
| 910 | + | |
837 | 911 | | |
838 | 912 | | |
839 | | - | |
| 913 | + | |
840 | 914 | | |
841 | | - | |
| 915 | + | |
842 | 916 | | |
843 | 917 | | |
844 | | - | |
| 918 | + | |
845 | 919 | | |
846 | 920 | | |
847 | 921 | | |
848 | | - | |
| 922 | + | |
849 | 923 | | |
850 | 924 | | |
851 | 925 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
0 commit comments