Skip to content

Commit 16c5749

Browse files
committed
feat(toolchain): Linux→Windows MinGW cross toolchain (mingw-cross) — host≠target (0.0.92)
Cross-compile Windows x86_64 PE (incl. import std) from a Linux host — the deferred host≠target axis registered in the 0.0.89 msvc-mingw design (§4.4/§7). - registry: mingw-cross → xim mingw-cross-gcc (x86_64-w64-mingw32-g++ frontend, triple-prefixed archiver, Linux-host index visibility) - lifecycle: lift the Windows-host gate for the cross package (native mingw stays Windows-only); skip glibc/linux-headers sysroot deps (PE own CRT) - prepare: --target x86_64-w64-mingw32 convention → mingw-cross@16.1.0, static - host≠target fixes surfaced by real end-to-end validation: * gcc.cppm: find std module source under <prefix>/<triple>/include/c++/ * gcc.cppm + flags.cppm: self-contained toolchains (musl+mingw) skip external -B (Linux as can't assemble MinGW PE/SEH output) * flags.cppm: MinGW PE link (-static / -lstdc++exp) keyed on is_mingw_target (target) instead of is_windows (host) — was unreachable on a Linux cross - toolchain: from-source GCC 16.1.0 mingw-w64 MSVCRT cross, published at xlings-res/mingw-cross-gcc + xim-pkgindex (import std verified under wine) - tests: unit MapsMingwCrossSpecToCrossPackage; e2e 102_mingw_cross_wine.sh; run_all mingw-cross/wine caps; CI cross-build-test mingw-cross-wine job - design: .agents/docs/2026-07-15-mingw-linux-cross-windows-design.md Verified end-to-end via the published ecosystem: mcpp toolchain install mingw-cross → mcpp build --target x86_64-w64-mingw32 → PE32+ (KERNEL32+msvcrt only) → wine runs import std + multi-module.
1 parent 6282d5d commit 16c5749

13 files changed

Lines changed: 662 additions & 39 deletions

.agents/docs/2026-07-15-mingw-linux-cross-windows-design.md

Lines changed: 344 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/cross-build-test.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ name: cross-build-test
1111
# target | toolchain | host→target | run
1212
# ----------------------|---------------------------------|---------------|-----
1313
# aarch64-linux-musl | aarch64-linux-musl-gcc@16.1.0 | x86_64→arm64 | qemu
14+
# x86_64-w64-mingw32 | mingw-cross-gcc@16.1.0 (MSVCRT) | linux→windows | wine
15+
#
16+
# The mingw row is OS-cross (same arch, different OS/ABI: ELF→PE), so it lives
17+
# in its own job below with wine verification instead of the qemu arch matrix.
18+
# See .agents/docs/2026-07-15-mingw-linux-cross-windows-design.md.
1419
#
1520
# mcpp resolves a cross `--target <triple>-musl` build to the triple-named cross
1621
# gcc musl toolchain from the xlings ecosystem (xim:<triple>-gcc, see
@@ -163,3 +168,80 @@ jobs:
163168
echo "== xlings --version (best-effort under qemu) =="
164169
xver=$($RUN "$XLINGS_XBIN" --version 2>&1 || true)
165170
echo "$xver"
171+
172+
# ── Linux → Windows MinGW cross (OS-cross, same arch: ELF→PE) ─────────────
173+
# Builds a demo project for x86_64-w64-mingw32 with the from-source GCC-16
174+
# MSVCRT cross toolchain, asserts the artefact is a fully-static PE, and runs
175+
# it under wine. Delegated to the e2e harness (tests/e2e/102_mingw_cross_wine.sh,
176+
# `# requires: mingw-cross wine`) so the run_all cap-gating stays the single
177+
# source of truth. See 2026-07-15-mingw-linux-cross-windows-design.md Part C.
178+
mingw-cross-wine:
179+
name: mingw-cross linux→windows (build + wine run)
180+
runs-on: ubuntu-24.04
181+
timeout-minutes: 60
182+
env:
183+
MCPP_HOME: /home/runner/.mcpp
184+
MCPP_VERBOSE: "1"
185+
steps:
186+
- uses: actions/checkout@v4
187+
188+
- name: Cache mcpp sandbox
189+
uses: actions/cache@v4
190+
with:
191+
path: ~/.mcpp
192+
key: mcpp-sandbox-${{ runner.os }}-mingw-cross-${{ hashFiles('mcpp.toml', '.xlings.json') }}
193+
restore-keys: |
194+
mcpp-sandbox-${{ runner.os }}-mingw-cross-
195+
196+
- name: Cache xlings
197+
uses: actions/cache@v4
198+
with:
199+
path: ~/.xlings
200+
key: xlings-${{ runner.os }}-v2-${{ hashFiles('.xlings.json') }}
201+
restore-keys: |
202+
xlings-${{ runner.os }}-v2-
203+
204+
- name: Install wine
205+
run: |
206+
sudo dpkg --add-architecture i386 || true
207+
sudo apt-get update -qq
208+
sudo apt-get install -y wine64 wine || sudo apt-get install -y wine
209+
wine --version
210+
211+
- name: Bootstrap mcpp via xlings
212+
env:
213+
XLINGS_NON_INTERACTIVE: '1'
214+
XLINGS_VERSION: '0.4.62'
215+
run: |
216+
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
217+
curl -fsSL -o "/tmp/${tarball}" \
218+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}"
219+
tar -xzf "/tmp/${tarball}" -C /tmp
220+
"/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install
221+
export PATH="$HOME/.xlings/subos/default/bin:$PATH"
222+
find "$HOME/.xlings" -name '.xlings-index-cache.json' -delete 2>/dev/null || true
223+
xlings config --mirror GLOBAL 2>/dev/null || true
224+
xlings update -y 2>/dev/null || xlings update 2>/dev/null || true
225+
xlings install mcpp -y
226+
echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV"
227+
echo "MCPP_BOOT=$HOME/.xlings/subos/default/bin/mcpp" >> "$GITHUB_ENV"
228+
229+
- name: Self-host build (fresh host mcpp)
230+
run: |
231+
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
232+
"$MCPP_BOOT" self config --mirror GLOBAL 2>/dev/null || true
233+
"$MCPP_BOOT" build
234+
MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)")
235+
test -x "$MCPP"
236+
"$MCPP" self config --mirror GLOBAL
237+
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
238+
239+
- name: Install mingw-cross toolchain
240+
run: |
241+
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
242+
"$MCPP" toolchain install mingw-cross 16.1.0
243+
244+
- name: "e2e: cross-build + wine run"
245+
run: |
246+
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
247+
bash tests/e2e/102_mingw_cross_wine.sh

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,30 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.92] — 2026-07-15
7+
8+
### 新增
9+
10+
- **Linux → Windows MinGW-w64 交叉工具链(`mingw-cross`)—— host≠target 一等公民**
11+
在 Linux 主机上交叉编译出 Windows x86_64 PE,含 `import std`。补 0.0.89
12+
`msvc-mingw-design` §4.4/§7 登记的延期项(交叉维:host≠target)。
13+
- **工具链**:从源码构建的 GCC 16.1.0 mingw-w64 **MSVCRT** 交叉链(triple
14+
`x86_64-w64-mingw32`,跟随 Rust Tier-1 `x86_64-pc-windows-gnu` 的 CRT 选型);
15+
自包含(自带 binutils/CRT/libstdc++ 含 `bits/std.cc` + `libstdc++exp`);
16+
发布于 `xlings-res/mingw-cross-gcc`(GitHub+GitCode)+ `xim-pkgindex`
17+
- **mcpp 侧**:用户名 `mingw-cross` → xim `mingw-cross-gcc`(前端
18+
`x86_64-w64-mingw32-g++`);解除 MinGW 的 Windows-host 门(Linux 主机可装,native
19+
`mingw` 仍 Windows-only);`--target x86_64-w64-mingw32` 约定解析(默认 static);
20+
跳过 glibc/linux-headers(PE 自带 CRT)。
21+
- **host≠target 化**:PE 产物形态判断一律按 target 而非 host constexpr——std 模块源
22+
探测补 `<prefix>/<triple>/include/c++/` 子目录;自包含工具链跳过外部 binutils `-B`
23+
(musl + mingw);MinGW link 分支(`-static` / `-lstdc++exp`)由 `if constexpr(is_windows)`
24+
host 门提为运行期 `is_mingw_target` target 判定。
25+
- **验证**:e2e `102_mingw_cross_wine.sh`(`# requires: mingw-cross wine`,build --target
26+
→ PE 静态自包含断言 → wine 跑 import std + 多模块);CI `cross-build-test.yml`
27+
`mingw-cross-wine` job(OS-cross,wine)。全链实测闭环(install→build→wine)。
28+
- 设计:`.agents/docs/2026-07-15-mingw-linux-cross-windows-design.md`
29+
630
## [0.0.91] — 2026-07-15
731

832
### 新增

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.91"
3+
version = "0.0.92"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/flags.cppm

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,14 @@ CompileFlags compute_flags(const BuildPlan& plan) {
215215
f.sysroot = link_toolchain_flags;
216216
}
217217

218-
// Binutils -B flag — a GCC/libstdc++ payload concern (musl bundles its
219-
// own as/ld; Clang and MSVC never take an external binutils).
220-
bool isMuslTc = mcpp::toolchain::is_musl_target(plan.toolchain);
218+
// Binutils -B flag — a GCC/libstdc++ payload concern (musl and MinGW-w64
219+
// cross both bundle their own as/ld; Clang and MSVC never take an external
220+
// binutils). MinGW must not get the Linux binutils -B — its PE/SEH output
221+
// is only assemblable by its own x86_64-w64-mingw32-as.
222+
bool isMuslTc = mcpp::toolchain::is_musl_target(plan.toolchain);
223+
bool isMingwTc = mcpp::toolchain::is_mingw_target(plan.toolchain);
221224
std::filesystem::path binutilsBin;
222-
if (!isMuslTc && caps.stdlib_id == "libstdc++") {
225+
if (!isMuslTc && !isMingwTc && caps.stdlib_id == "libstdc++") {
223226
auto ar = mcpp::toolchain::archive_tool(plan.toolchain);
224227
if (!ar.empty())
225228
binutilsBin = ar.parent_path();
@@ -361,6 +364,27 @@ CompileFlags compute_flags(const BuildPlan& plan) {
361364
if (prof.lto) link_extra += " -flto";
362365
if (prof.strip) link_extra += " -s";
363366

367+
// MinGW PE link — keyed on the TARGET (is_mingw_target), NOT the host: a
368+
// Linux-hosted cross build produces exactly the same PE link as a native
369+
// Windows MinGW build (host≠target). No rpath/loader/payload model. Static
370+
// + libstdc++exp (std::print's __open_terminal/__write_to_terminal live in
371+
// libstdc++exp.a, not plain libstdc++). Self-contained binutils → no -B.
372+
if (isMingwTc) {
373+
std::string mingw_static;
374+
std::string mingw_stdexp;
375+
if (caps.stdlib_id == "libstdc++") {
376+
// `-static` for the whole link — MinGW's standalone-exe convention
377+
// (the piecemeal -static-libstdc++ recipe still pulls
378+
// libwinpthread-1.dll). Opt out via [build] static_stdlib=false.
379+
if (f.staticStdlib || f.linkage == "static")
380+
mingw_static = " -static";
381+
mingw_stdexp = " -lstdc++exp";
382+
}
383+
f.ld = std::format("{}{}{}{}{}", mingw_static, static_stdlib,
384+
user_ldflags, mingw_stdexp, link_extra);
385+
return f;
386+
}
387+
364388
if constexpr (mcpp::platform::is_windows) {
365389
if (isMsvcDialect) {
366390
// Native cl.exe: link.exe does the link (SeparateLinker). Search
@@ -374,30 +398,10 @@ CompileFlags compute_flags(const BuildPlan& plan) {
374398
f.ld = libpaths + user_ldflags;
375399
return f;
376400
}
377-
// PE link: no rpath/loader/payload model. MSVC-ABI Clang needs
378-
// nothing extra (MSVC STL/SDK via the driver); MinGW adds the static
379-
// libstdc++/libgcc pair (static_stdlib above) and -B so its own
380-
// binutils resolve, plus `-static` for full static when requested
381-
// (MinGW supports it, unlike MSVC-ABI links).
382-
std::string mingw_static;
383-
std::string mingw_stdexp;
384-
if (caps.stdlib_id == "libstdc++") {
385-
// `-static` for the whole link — winlibs' own recommendation for
386-
// standalone exes. The piecemeal recipe (-static-libstdc++ +
387-
// -Wl,-Bstatic -lwinpthread) verifiably loses to the driver's
388-
// implicit closing libs: CI import tables still showed
389-
// libwinpthread-1.dll. System DLLs (KERNEL32/UCRT) still resolve
390-
// via their import libs. Tied to staticStdlib so
391-
// [build] static_stdlib=false opts back into DLL-coupled links.
392-
if (f.staticStdlib || f.linkage == "static")
393-
mingw_static = " -static";
394-
// std::print's terminal probe (__open_terminal /
395-
// __write_to_terminal, bits/print.h) lives in libstdc++exp.a on
396-
// Windows targets — plain -lstdc++ leaves them undefined.
397-
mingw_stdexp = " -lstdc++exp";
398-
}
399-
f.ld = std::format("{}{}{}{}{}{}", mingw_static, static_stdlib, b_flag,
400-
user_ldflags, mingw_stdexp, link_extra);
401+
// PE link, MSVC-ABI Clang (native MinGW is handled by the target-keyed
402+
// branch above and has already returned): no rpath/loader/payload —
403+
// MSVC STL/SDK come via the driver, nothing extra needed.
404+
f.ld = std::format("{}{}{}", static_stdlib, user_ldflags, link_extra);
401405
} else if constexpr (mcpp::platform::needs_explicit_libcxx) {
402406
// macOS. Two min-version concerns (see xlings
403407
// .agents/docs/2026-06-05-macos-min-version-support.md):

src/build/prepare.cppm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,19 @@ prepare_build(bool print_fingerprint,
664664
&& m->buildConfig.linkage.empty()) {
665665
m->buildConfig.linkage = "static";
666666
}
667+
// Convention: the Windows PE cross target `x86_64-w64-mingw32` without
668+
// an explicit [target.X] override resolves to the from-source GCC-16
669+
// MSVCRT cross toolchain. host≠target — an ELF frontend producing PE.
670+
// Default static linkage: MinGW standalone-exe convention and the clean
671+
// path for running the artifact under wine (no DLL deployment needed).
672+
// See .agents/docs/2026-07-15-mingw-linux-cross-windows-design.md.
673+
if (overrides.target_triple == "x86_64-w64-mingw32"
674+
&& (it == m->targetOverrides.end() || it->second.toolchain.empty()))
675+
{
676+
tcSpec = "mingw-cross@16.1.0";
677+
if (m->buildConfig.linkage.empty())
678+
m->buildConfig.linkage = "static";
679+
}
667680
}
668681
if (overrides.force_static) m->buildConfig.linkage = "static";
669682

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "0.0.91";
21+
inline constexpr std::string_view MCPP_VERSION = "0.0.92";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

src/toolchain/gcc.cppm

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,31 @@ std::optional<std::filesystem::path> find_std_module_source(
7171
auto p = root / "include" / "c++" / std::string(version) / "bits" / "std.cc";
7272
if (std::filesystem::exists(p)) return p;
7373

74+
// Cross toolchains (e.g. MinGW-w64 `x86_64-w64-mingw32-g++`) install the
75+
// TARGET libstdc++ — and its bits/std.cc — under a target-triple subdir:
76+
// <prefix>/<triple>/include/c++/<version>/bits/std.cc
77+
// rather than the host-native <prefix>/include/c++/. Derive the triple from
78+
// the frontend filename (strip the -g++/-gcc/-c++ suffix) and look there.
79+
{
80+
std::error_code ec;
81+
auto stem = cxx_binary.stem().string();
82+
for (std::string_view suf : {"-g++", "-gcc", "-c++"}) {
83+
if (stem.size() > suf.size() && stem.ends_with(suf)) {
84+
std::string triple = stem.substr(0, stem.size() - suf.size());
85+
auto base = root / triple / "include" / "c++";
86+
auto pv = base / std::string(version) / "bits" / "std.cc";
87+
if (std::filesystem::exists(pv, ec)) return pv;
88+
if (std::filesystem::exists(base, ec)) {
89+
for (auto& entry : std::filesystem::directory_iterator(base, ec)) {
90+
auto cand = entry.path() / "bits" / "std.cc";
91+
if (std::filesystem::exists(cand, ec)) return cand;
92+
}
93+
}
94+
break;
95+
}
96+
}
97+
}
98+
7499
// Version-dir scan fallback: the header dir doesn't always equal the
75100
// full driver version (e.g. distro / MinGW-w64 builds using the major
76101
// version, or a patched banner). Any bits/std.cc under include/c++ is
@@ -131,12 +156,13 @@ std::string std_module_build_command(const Toolchain& tc,
131156
const std::filesystem::path& cacheDir,
132157
std::string_view sysrootFlag,
133158
std::string_view cppStandardFlag) {
134-
// musl-cross-make toolchains bundle their own as/ld (and for a cross
135-
// target the host's binutils would mis-assemble, e.g. `as: unrecognized
136-
// option '-EL'`). Only the glibc gcc needs an external binutils package
137-
// wired via -B. Mirrors the guard in build/flags.cppm.
159+
// musl-cross-make AND MinGW-w64 cross toolchains bundle their own as/ld
160+
// (and for a cross target the host's binutils would mis-assemble — e.g.
161+
// the Linux `as` chokes on MinGW's PE/SEH directives `.def`/`.seh_proc`).
162+
// Only the glibc gcc needs an external binutils package wired via -B.
163+
// Mirrors the guard in build/flags.cppm.
138164
std::string bFlag;
139-
if (!is_musl_target(tc)) {
165+
if (!is_musl_target(tc) && !is_mingw_target(tc)) {
140166
if (auto binutilsBin = find_binutils_bin(tc.binaryPath)) {
141167
bFlag = std::format(" -B{}", mcpp::xlings::shq(binutilsBin->string()));
142168
}

src/toolchain/lifecycle.cppm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,12 @@ export int toolchain_install(const mcpp::config::GlobalConfig& cfg,
410410
// Ensure sysroot dependencies (glibc, linux-headers) are installed.
411411
// These are required for C library + kernel headers during compilation.
412412
// musl-gcc is self-contained and doesn't need these; neither do
413-
// Windows (llvm/mingw — PE, own CRT) or macOS (SDK) toolchains.
413+
// Windows (llvm/mingw — PE, own CRT) or macOS (SDK) toolchains, nor a
414+
// Linux-hosted mingw CROSS toolchain (host is Linux but the TARGET is
415+
// Windows PE with its own CRT — glibc/linux-headers are irrelevant).
414416
// Mirrors the platform guard on prepare.cppm's first-run install.
415417
if (!spec->isMusl
418+
&& pkg.ximName != "mingw-cross-gcc"
416419
&& !mcpp::platform::is_windows && !mcpp::platform::is_macos) {
417420
for (auto dep : {"xim:glibc", "xim:linux-headers"}) {
418421
mcpp::log::verbose("toolchain", std::format("installing dep: {}", dep));

src/toolchain/registry.cppm

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ std::vector<std::string> frontend_candidates_for(std::string_view ximName,
107107
if (ximName == "llvm") return mcpp::toolchain::llvm::frontend_candidates();
108108
if (ximName == "msvc") return {"cl.exe"};
109109
if (ximName == "mingw-gcc") return {"g++.exe", "g++"};
110+
// Linux-hosted MinGW-w64 cross toolchain: an ELF frontend that produces
111+
// Windows PE. Triple-prefixed name (like the musl cross tools), never the
112+
// host g++ — a cross build must never silently fall back to native.
113+
if (ximName == "mingw-cross-gcc") return {"x86_64-w64-mingw32-g++"};
110114
return {"g++", "clang++", "x86_64-linux-musl-g++", "cl.exe"};
111115
}
112116

@@ -180,6 +184,11 @@ XimToolchainPackage to_xim_package(const ToolchainSpec& spec) {
180184
// `mingw-gcc` (winlibs GCC+MinGW-w64 UCRT builds mirrored at xlings-res).
181185
if (ximCompiler == "mingw")
182186
ximCompiler = "mingw-gcc";
187+
// Linux→Windows MinGW-w64 cross: user-facing `mingw-cross`, xim package
188+
// `mingw-cross-gcc` (from-source GCC-16 MSVCRT cross, xlings-res). host≠target:
189+
// an ELF toolchain producing PE. See 2026-07-15-mingw-linux-cross-windows-design.
190+
if (ximCompiler == "mingw-cross")
191+
ximCompiler = "mingw-cross-gcc";
183192

184193
pkg.ximName = ximCompiler;
185194
pkg.ximVersion = spec.version;
@@ -233,6 +242,8 @@ std::string display_label(std::string_view compiler, std::string_view version) {
233242
return std::format("gcc {}-musl", version);
234243
if (compiler == "mingw-gcc")
235244
return std::format("mingw {}", version);
245+
if (compiler == "mingw-cross-gcc")
246+
return std::format("mingw-cross {}", version);
236247
return std::format("{} {}", compiler, version);
237248
}
238249

@@ -253,6 +264,12 @@ bool matches_default_toolchain(std::string_view configuredDefault,
253264
&& configuredDefault == std::format("mingw@{}", version)) {
254265
return true;
255266
}
267+
// Cross MinGW: installed-payload row reports xim name (mingw-cross-gcc);
268+
// user-facing spec is mingw-cross@<ver>.
269+
if (compiler == "mingw-cross-gcc"
270+
&& configuredDefault == std::format("mingw-cross@{}", version)) {
271+
return true;
272+
}
256273
return false;
257274
}
258275

@@ -266,9 +283,13 @@ std::vector<std::pair<std::string, std::string>> available_toolchain_indexes() {
266283
{"musl-gcc", "musl-gcc"},
267284
{mcpp::toolchain::llvm::package_name(), "llvm"},
268285
};
269-
// Windows-native toolchain — only meaningful to list on Windows hosts.
286+
// MinGW-w64 toolchains are host-conditional (host≠target axis):
287+
// - native mingw-gcc (produces PE, runs on Windows) → Windows hosts
288+
// - cross mingw-cross-gcc (ELF frontend, produces PE) → Linux hosts
270289
if constexpr (mcpp::platform::is_windows)
271290
out.emplace_back("mingw-gcc", "mingw-gcc");
291+
else if constexpr (mcpp::platform::is_linux)
292+
out.emplace_back("mingw-cross-gcc", "mingw-cross-gcc");
272293
return out;
273294
}
274295

@@ -286,10 +307,17 @@ std::filesystem::path archive_tool(const Toolchain& tc) {
286307
if (is_clang(tc)) return mcpp::toolchain::clang::archive_tool(tc);
287308

288309
// MinGW bundles its own binutils next to the frontend (self-contained,
289-
// like musl) — never an external binutils xpkg.
310+
// like musl) — never an external binutils xpkg. Native (Windows-host) ships
311+
// `ar.exe`; the Linux-hosted cross ships the triple-prefixed ELF tool
312+
// `x86_64-w64-mingw32-ar`. Try the cross form first, then native.
290313
if (is_mingw_target(tc)) {
291-
auto ar = tc.binaryPath.parent_path() / "ar.exe";
292314
std::error_code ec;
315+
auto dir = tc.binaryPath.parent_path();
316+
if (!tc.targetTriple.empty()) {
317+
auto crossAr = dir / (tc.targetTriple + "-ar");
318+
if (std::filesystem::exists(crossAr, ec)) return crossAr;
319+
}
320+
auto ar = dir / "ar.exe";
293321
if (std::filesystem::exists(ar, ec)) return ar;
294322
return {};
295323
}

0 commit comments

Comments
 (0)