docs(b3): target-aware artifact naming — the real symptom is a relink every build - #341
Closed
Sunrisepeak wants to merge 3 commits into
Closed
docs(b3): target-aware artifact naming — the real symptom is a relink every build#341Sunrisepeak wants to merge 3 commits into
Sunrisepeak wants to merge 3 commits into
Conversation
Safe to move now: v2026.8.3.2 is published on all four platforms, mirrored to xlings-res, and xim-pkgindex tracks it as latest (openxlings/xim-pkgindex#480). Verified end to end — `xlings install mcpp@2026.8.3.2` then `xlings use` resolves to 2026.8.3.2. The pin is the self-hosting starting point, so it only ever moves AFTER the release it names actually exists and is reachable through the index; bumping it alongside the version bump would have pointed every CI job at a release that had not been built yet.
… 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.
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.
Sunrisepeak
force-pushed
the
docs/b3-artifact-naming
branch
from
August 3, 2026 06:02
d1bdf61 to
f90f8f4
Compare
Member
Author
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.
Docs only. Writes up the B3 follow-up filed in #339, and corrects what that PR's
§6.5 claimed — the original description of the symptom was wrong.
What §6.5 got wrong
It said B3 was "symmetrically wrong":
mcpp.exefor an ELFmcppfor a PEMeasured on a Linux host:
The name is right, because mingw's GCC driver appends
.exeitself when the-oname has no extension. mcpp never participates in that decision.What is actually wrong, and why it matters more
ninja is told one thing and GCC does another:
The declared output never exists, so ninja reruns the link edge on every
build. Verified by mtime across two consecutive builds:
Incremental builds are effectively off for PE targets — the path CI exercises
daily. So the asymmetry is real but inverted from what §6.5 said: Linux→Windows
is a functional defect, Windows→Linux is only cosmetic.
Nothing caught it because
102_mingw_cross_wine.shlooks for the realartifact (
find -name '*.exe'), not for what ninja declared. Both itsassertions hold while the inconsistency sits underneath them — same shape as the
explicit-ninja-goals regressions.
Two further findings
Naming is an (os, env) function, not an os one.
windows-gnuuses the GNUconvention (
libfoo.a); onlywindows-msvcisfoo.lib. The current_WIN32branch 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 rather
than the declaration. §6.5 expected to have to touch the mingw e2e and the
release packaging paths; neither turns out to be true.
Scope of the eventual fix
Confined to
src/build/plan.cppm. The other 15exe_suffixreferences locateninja/xlings/clang++on the build machine — correct host semantics,must not be touched. Verified one by one.
Doc includes the commit layering (stub-first so the assertions go red),
the verification criteria — the load-bearing one being a no-relink e2e assertion,
since that is the only thing that can falsify "fixed" — and two open questions
for you: whether the
foo.lib→libfoo.acorrection ships in the same PR(it changes a host build's output name), and that PE import libraries are
entirely unmodelled today.
Both open questions resolved (深度调研 update)
Q1 —
foo.lib→libfoo.a: do it in the same PR. Blast radius traced to empty.The worry was that it changes a host build's output name. Tracing every consumer:
plan.cppm:836puts dependency.ofiles straight intolu.objects. A static library is never produced or read on an internal dependency edgeldflags; the name lives in the package descriptor[runtime] library_dirs.dllfor runtime deployment onlyfingerprint.cppmcarries no artifact name but does carryMCPP_VERSION— any bump already rotatestarget/<triple>/<fp>/, so no mixed state and no cache migrationThe structural fact: mcpp static libraries are not internal link units. That turns
this from "load-bearing rename" into "one outward-facing filename".
And the name they get today is wrong: mingw's
aremits a GNU archive namedfoo.lib, claiming an MSVC convention it does not satisfy — a pre-existingcorrectness bug, unrelated to cross-compilation. Shipping the (os, env) rule
half-way would leave a state harder to explain than the bug itself.
Q2 — PE import libs: do not model them yet — draw the boundary instead
A fact that reframes the question. All five shared-library e2e tests declare
# requires: elf:and
elfis only added on the Linux branch ofrun_all.sh:46— Darwin getsmacos, Windows getswindows.Shared libraries have never been verified end to end on PE or Mach-O. This is not
a missing feature; it is a path nobody has walked, while the code carries branches that
look like it was. Those branches are speculation — mingw tolerates linking a
.dlldirectly, MSVC's
link.execannot — andshared_library_link_flagskeys onis_windows, a host constant, so it points the wrong way under cross-compilationregardless.
Recommendation: reject
SharedLibraryon non-ELF targets with a clear error beforeattempting to support it. Silently emitting something unusable is worse than saying no.
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.
Resulting split — three PRs, not one
SharedLibrary→ explicit error + e2e. Independent of PR-1; can land firstexisting for PE and Mach-O first. Adding artifact edges with no coverage would just
create the next batch of unverified branches