@@ -533,6 +533,27 @@ CompileFlags compute_flags(const BuildPlan& plan) {
533533 ? std::string{}
534534 : " " + plan.toolchain .crossTargetFlag ;
535535 const bool isClangWithCfg = dm.hasCfg ;
536+
537+ // ⭐⭐ THE TARGET SIDE COMES FROM THE DEPENDENCY GRAPH, STATED ONCE.
538+ //
539+ // ⚠️ TWO CONDITIONS AND NOT ONE, and each excludes a case the other admits.
540+ //
541+ // `crossTargetFlag` alone is too wide: it is set for EVERY hosted target a
542+ // retargetable clang is pointed at, including the musl and glibc crosses
543+ // served by a payload — and those still need this host's `-B`, its runtime
544+ // directories and its C-runtime flags, because for them the payload IS the
545+ // target side.
546+ //
547+ // `targetCxxRuntime` alone is too wide the other way: it says a package
548+ // supplies a C++ runtime, which is also true of a NATIVE build of such a
549+ // package, where the payload's link model is right and dropping it would
550+ // remove a working link.
551+ //
552+ // Together they say the thing this replacement depends on: the C library,
553+ // the C++ runtime and the platform are packages, AND we are pointing the
554+ // compiler at a target that is not this machine.
555+ const bool graphTargetSide = plan.toolchain .targetCxxRuntime
556+ && !plan.toolchain .crossTargetFlag .empty ();
536557 // LLVM root of a clang-with-cfg toolchain — used by the macOS link
537558 // path below to locate libc++.a/libc++abi.a for staticStdlib.
538559 std::filesystem::path llvmRootForStdlib;
@@ -1201,42 +1222,25 @@ CompileFlags compute_flags(const BuildPlan& plan) {
12011222 // Windows MinGW build (host≠target). No rpath/loader/payload model. Static
12021223 // + libstdc++exp (std::print's __open_terminal/__write_to_terminal live in
12031224 // libstdc++exp.a, not plain libstdc++). Self-contained binutils → no -B.
1204- if (isMingwTc) {
1205- // ⭐⭐ AND THE SAME SENTENCE THAT QUALIFIES THE PARAGRAPH ABOVE: that
1206- // holds while every PE cross is served by a MinGW PAYLOAD, whose driver
1207- // IS the target and therefore needs nothing said to it. When the target
1208- // side comes from the dependency graph the compiler is an ordinary
1209- // retargetable clang, and this early return was dropping the one flag
1210- // that tells it which target to link for.
1211- //
1212- // ⚠️ Measured 2026-08-23, `--target x86_64-windows-gnu` over openkal —
1213- // every object compiled, and then:
1214- //
1215- // ld.lld: error: obj/…/types.m.o: unknown file type (× 30)
1216- //
1217- // COFF objects handed to lld's ELF driver, because the compile line
1218- // carried `--target=` and the link line did not. Thirty accurate
1219- // messages, none of which names the missing flag.
1220- //
1221- // ⇒ Third time this shape has appeared (payload compile tokens, then
1222- // the host link model, now the PE early return). The predicate is the
1223- // same one every time, so it is spelled the same way.
1224- const std::string graphTargetLd =
1225- plan.toolchain .crossTargetFlag .empty () ? std::string{}
1226- : link_toolchain_flags;
1225+ // ⚠️ AND ONLY WHILE THE PAYLOAD IS THE TARGET, which is what the paragraph
1226+ // above assumes without saying so: `x86_64-w64-mingw32-g++` needs no
1227+ // `--target` because it HAS no other. When the target side comes from the
1228+ // dependency graph the compiler is an ordinary retargetable clang, and this
1229+ // branch is one of three shaped by the HOST rather than by the target — see
1230+ // the replacement below, which covers all three at once.
1231+ if (isMingwTc && !graphTargetSide) {
12271232 // `-static` / `-static-libstdc++` now come from the contract table via
12281233 // unit_ldflags (dist::Format::Pe) — the whole-link `-static` is what
12291234 // "self-contained" means here, since the piecemeal recipe still leaves
12301235 // libwinpthread-1.dll behind.
12311236 std::string mingw_stdexp;
12321237 if (caps.stdlib_id == " libstdc++" )
12331238 mingw_stdexp = " -lstdc++exp" ;
1234- f.ld = std::format (" {}{}{}{}{} " , graphTargetLd, link_intent_ld ,
1235- user_ldflags, mingw_stdexp, link_extra);
1239+ f.ld = std::format (" {}{}{}{}" , link_intent_ld, user_ldflags ,
1240+ mingw_stdexp, link_extra);
12361241 // `-lstdc++exp` is named explicitly, so swapping g++ for gcc would not
12371242 // drop it — the C line has to leave it out.
1238- f.ldC = std::format (" {}{}{}{}" , graphTargetLd, link_intent_ld,
1239- user_ldflags, link_extra);
1243+ f.ldC = std::format (" {}{}{}" , link_intent_ld, user_ldflags, link_extra);
12401244 return f;
12411245 }
12421246
@@ -1364,6 +1368,52 @@ CompileFlags compute_flags(const BuildPlan& plan) {
13641368 user_ldflags, link_extra);
13651369 }
13661370
1371+ // ── The target side comes from the graph, so the HOST's link is wrong ──
1372+ //
1373+ // ⭐⭐ THE THREE BRANCHES ABOVE ARE SHAPED BY THIS MACHINE, NOT BY THE
1374+ // TARGET. `if constexpr (is_windows)` / `needs_explicit_libcxx` / else is a
1375+ // question about where mcpp itself was built, and each answer describes a
1376+ // link on that machine: an SDK path, a deployment target, a loader search
1377+ // path, this host's `libatomic`. Every one of them is right when the target
1378+ // is the host or is served by a payload, and wrong when the C library, the
1379+ // C++ runtime and the platform are packages in the dependency graph.
1380+ //
1381+ // ⚠️ ONLY THE THIRD BRANCH EVER CONSUMED `link_toolchain_flags`, WHICH IS
1382+ // WHERE `--target=` LIVES. So a cross build over openkal linked correctly
1383+ // from a Linux host and would have handed a Mach-O or an ELF to a linker
1384+ // told nothing about the target from a macOS or a Windows one. The measured
1385+ // shape of that failure is on record from the PE case, which reached it a
1386+ // different way:
1387+ //
1388+ // ld.lld: error: obj/…/types.m.o: unknown file type (× 30)
1389+ //
1390+ // ⇒ Applied LAST and by REPLACEMENT, exactly as the freestanding block
1391+ // below is and for the same reason it is: what came before is not merely
1392+ // unnecessary but wrong, and appending to it would leave the outcome
1393+ // depending on the driver's flag ordering rather than on a decision.
1394+ //
1395+ // ⚠️ AND IT REPLACES A SPECIAL CASE RATHER THAN ADDING ONE. The PE branch
1396+ // above carried its own copy of this for one format; this covers PE, Mach-O
1397+ // and ELF, on every host, with the predicate stated once.
1398+ //
1399+ // What survives, and why each one is not the host's:
1400+ // full_static the contract table's, keyed on the target's FORMAT
1401+ // link_toolchain_flags `--target=`, `--no-default-config`, `-fuse-ld=lld`
1402+ // link_intent_ld what the user asked to build (exe/shared/static)
1403+ // user_ldflags the manifest's own words
1404+ // link_extra `-flto` / `-s`, profile decisions
1405+ //
1406+ // What does not: `b_flag` (this host's binutils), `runtime_dirs` and the
1407+ // `-rpath` beside them (this host's payload directories — measured on a
1408+ // Mach-O link as `-Wl,-rpath,…/lib/x86_64-unknown-linux-gnu`, which ld64
1409+ // accepts and writes into the image), `payload_ld`, `atomic_ld`.
1410+ if (!isFreestandingTarget && graphTargetSide) {
1411+ f.ld = std::format (" {}{}{}{}{}" , full_static, link_toolchain_flags,
1412+ link_intent_ld, user_ldflags, link_extra);
1413+ f.ldC = std::format (" {}{}{}{}{}" , full_static, link_toolchain_flags_c,
1414+ link_intent_ld, user_ldflags, link_extra);
1415+ }
1416+
13671417 // ── Freestanding: the target has no OS, so most of the above is wrong ──
13681418 //
13691419 // Applied LAST and by REPLACEMENT rather than woven in above, for two
0 commit comments