diff --git a/.agents/docs/2026-07-15-mingw-linux-cross-windows-design.md b/.agents/docs/2026-07-15-mingw-linux-cross-windows-design.md new file mode 100644 index 0000000..3233865 --- /dev/null +++ b/.agents/docs/2026-07-15-mingw-linux-cross-windows-design.md @@ -0,0 +1,344 @@ +# Linux → Windows MinGW 交叉工具链 — 设计方案 + +> 2026-07-15 · 基于 0.0.91(CommandDialect + MSVC 原生后端 + c++fly 已发布)代码审计 +> 姊妹文档:`2026-07-13-toolchain-backend-abstraction-msvc-mingw-design.md`(§4.4/§7 登记本方案为延期项)、 +> `2026-06-23-aarch64-musl-gcc-canadian-cross-rebuild.md`(canadian-cross 打包先例)、 +> `2026-07-07-hermetic-toolchain-link-model-design.md`(链接模型)、 +> 记忆:[[release-publish-pipeline]]、[[toolchain-dialect-and-mingw]]、[[aarch64-musl-static-ninja-closure]] + +## 0. 一句话 + +把 `msvc-mingw-design` §4.4/§7 **登记但延期**的「Linux→Windows 交叉」补上——它验证工具链抽象层的 +最后一维:**host ≠ target**(native MinGW 保持 host==target==Windows,交叉强制两者分离)。**后端零新增** +(复用 0.0.89 的 `kGnuDialect` 拼写 + `is_mingw_target` PE 链接模型,二者早已按 target 而非 host 求值), +工作量集中在三处:**A. 解除 host 门**(把 install/select 侧的「MinGW 仅 Windows」假设改成 host+target +组合)、**B. 自包含 tarball 打包**(从源码 canadian-cross 出 GCC-16 mingw-w64 **MSVCRT** 交叉链,xlings-res +双端镜像,与 llvm/musl-gcc 同模式)、**C. Wine 真实验证**(host 跑不了 PE,靠 Wine 端到端断言)。 +CRT 选型**跟随 Rust**:Rust Tier-1 `x86_64-pc-windows-gnu` 链 MSVCRT、UCRT 另立 `gnullvm` 目标—— +本期取 MSVCRT triple `x86_64-w64-mingw32`(恰与现 `abi.cppm:76` mingw→msvcrt 一致,无需细化),UCRT 登记为 +triple 可选 follow。 + +## 1. 现状评估(审计结论,file:line 落地) + +### 1.1 已有的、正确的抽象缝(交叉直接复用,零改) + +| 抽象 | 位置 | 对交叉的意义 | +|---|---|---| +| PE 链接模型按 **target** 求值 | linkmodel.cppm:231-236 | `is_msvc_target \|\| is_mingw_target → CLibMode::None`;注释明说「so a future cross-compile resolves by what it builds FOR」——本方案就是那个 future | +| `is_mingw_target(tc)` | model.cppm:107-110 | triple 含 `mingw32` 即 PE 目标,与 host 无关 | +| `kGnuDialect`(命令拼写) | dialect.cppm:72-87 | GCC/Clang/MinGW 共享 gnu 方言;交叉链是 GCC 家族 → 拼写零差异 | +| cfg 谓词 `mingw → windows` | prepare.cppm:69 | `[target.'cfg(windows)'.build]` 对交叉产物正确求值(按解析后 target,非 host) | +| Windows 链接三坑已就位 | flags.cppm:364-400 | `-static` / `-lstdc++exp` / `-static-libstdc++ -static-libgcc`——Windows 分支按 target 命中,交叉原样吃 | +| musl 交叉的 `-g++` 前端机制 | registry.cppm:99-104,167-173 | 交叉前端解析的现成模板;`x86_64-w64-mingw32-g++` 套同一机制 | +| `BuildOverrides.target_triple` | prepare.cppm:403-411 | `--target x86_64-w64-mingw32` 入口已在 | +| 交叉下跳过 L3 build.mcpp | prepare.cppm:914-919 | host 构建程序不为 target 跑,交叉语义已对 | +| `abi.cppm` mingw → `msvcrt` | abi.cppm:76 | **恰好匹配本期 MSVCRT 选型**——无需 `msvc-mingw-design` §4.2 计划的 ucrt 细化 | + +**结论**:抽象层已经把两条正交轴分开了——`CommandDialect` = **命令拼写**(gnu),`is_mingw_target`/linkmodel += **链接语义**(PE)。交叉恰好落在「gnu 拼写 + PE 语义」交点,而这两点都已按 target 而非 host 决策。所以 +mcpp 侧改动很小,真正的工作在打包与验证。 + +### 1.2 需要解除的 host 假设(本方案 Part A 的对象) + +MinGW 目前是 **Windows 原生**工具链,install/select 侧硬编码了「host 必须是 Windows」: + +1. **install 门**:`lifecycle.cppm:186-191` `mingw_wrong_host()` 在非 Windows host 硬失败(:190), + `385`、`506` 亦引用。交叉要求 Linux host 能装。 +2. **索引可见性**:`registry.cppm:270-271` `available_toolchain_indexes` 仅在 Windows host 列 `mingw-gcc`。 + 交叉要求 Linux host 列 `mingw-cross-gcc`。 +3. **doctor 探测**:`doctor.cppm:132-149` 探 `xim:mingw-gcc`,host 假设需并入组合判断。 +4. **注册名与包名**:`registry.cppm:179-182` `mingw → mingw-gcc`(winlibs Windows zip)。交叉是**另一个包** + (`mingw-cross-gcc`,Linux host 制品),不能复用同一 xim 名(host≠target,tarball 不同)。 + +这四处的共同根因:**工具链的 host 维与 target 维被 `is_windows()` host constexpr 合并了**。Part A 把判断从 +「host==Windows」改为「(host, target) 组合」——native MinGW = (Windows, Windows),交叉 MinGW = (Linux, Windows)。 + +### 1.3 结论 + +延续既定原则(2026-05-15 §2.3):**值类型 traits + per-compiler provider + 中心查询点,不引入虚函数**。 +交叉不新增后端,只是让既有 gnu 后端在 Linux host 上被允许、被发现、被解析。 + +## 2. 目标模型:host / target 二维化 + +### 2.1 核心:host≠target 一等公民 + +`Toolchain::targetTriple`(model.cppm)已是交叉/MinGW/MSVC 唯一 keying 字段。缺的不是数据结构,是 +**决策点仍按 host 短路**。本方案确立不变量: + +> 凡涉及「产物形态」的判断(链接模型、CRT、exe 后缀、DLL 部署、PE 校验)一律按 **target triple**; +> 凡涉及「工具能否在此机器运行/安装」的判断(install 门、索引可见性、前端候选)按 **(host, target) 组合**; +> **绝不**用裸 `is_windows()` host constexpr 代表产物形态。 + +`linkmodel.cppm:236` 已是前者的样板;Part A 把后者从 `mingw_wrong_host()` 的单维 host 检查升级为组合表: + +``` +支持矩阵(本期) + target: windows-msvcrt linux macos +host: linux mingw-cross-gcc ✓ musl/gcc — +host: windows mingw-gcc(native) — — +host: macos — — clang +``` + +### 2.2 CRT 选型:跟随 Rust,triple 编码 CRT + +Rust 的处理是本期依据:`x86_64-pc-windows-gnu`(Tier-1)链 **MSVCRT**(`msvcrt.dll`),UCRT 未改这个目标而是 +**另立** `x86_64-pc-windows-gnullvm`。mingw-w64 自身与 Debian 也用 **triple 编码 CRT**(`x86_64-w64-mingw32` = +msvcrt、`x86_64-w64-mingw32ucrt` = ucrt)。据此: + +- **本期 target = MSVCRT triple `x86_64-w64-mingw32`**。理由:① 跟随 Rust Tier-1 的稳定选择; + ② 恰与现 `abi.cppm:76` mingw→msvcrt 一致,**无需**改 abi;③ Wine 下最稳(`msvcrt.dll` 恒在,UCRT 需 + `ucrtbase.dll` 及一族 api-ms-win-*);④ 与 native winlibs(UCRT)**故意分成两个 triple**,而非二选一—— + 用户可各取所需,artifact 不混淆。 +- **UCRT 登记为 follow**:triple `x86_64-w64-mingw32ucrt`,Rust `gnullvm` 式独立目标;届时 abi.cppm 细化 ucrt + (即 `msvc-mingw-design` §4.2 原计划,留给它自己那期)。 + +**注意与 native MinGW 的 ABI 分叉**:native = UCRT、cross = MSVCRT,所以「交叉构建的产物 ≠ 原生构建的产物」。 +这是**有意**的——CRT 进 triple,两者是不同 target,不是同一 target 的两种构建。用户要 UCRT 交叉,等 follow。 + +### 2.3 明确不做(见 §9) + +不引入虚基类后端;不改 native winlibs MinGW(Part B)分毫;不做 i686/arm64;不做 UCRT 交叉(本期); +不动 `mcpp pack` 的 PE 支持。 + +## 3. Part A — host≠target 一等公民(mcpp 侧改动,小) + +| 步 | 内容 | 位置 | 验收 | +|---|---|---|---| +| A1 | install 门二维化:`mingw_wrong_host()` → `mingw_host_target_ok(host, target)`;native mingw-gcc 仍 Windows-only,mingw-cross-gcc 允许 Linux host | lifecycle.cppm:186-191,385,506 | 单测:四象限矩阵;native 路径零行为变化 | +| A2 | registry 新条目 `mingw-cross`:用户名 `mingw-cross`(或 `mcpp toolchain install mingw --target x86_64-w64-mingw32`);xim 名 `mingw-cross-gcc`;前端候选 `{"x86_64-w64-mingw32-g++"}`;`display_label` "mingw-cross {ver}";`matches_default_toolchain` 认 `mingw-cross@` | registry.cppm:99-109,167-182,234-255 | 单测:名映射;`mcpp toolchain list`(Linux)见 mingw-cross | +| A3 | 索引可见性组合化:`available_toolchain_indexes` 在 **Linux host** 列 mingw-cross-gcc、**Windows host** 列 mingw-gcc | registry.cppm:270-271 | e2e:Linux/Windows 各见对应项 | +| A4 | 检测走既有 GCC enrich:`x86_64-w64-mingw32-g++ -dumpmachine → x86_64-w64-mingw32` → `is_mingw_target` 命中 → gnu 方言 + PE linkmodel,全零改 | detect.cppm / model.cppm(无改预期) | 检测出 `CompilerId::GCC` + mingw triple | +| A5 | 约定解析套 musl 交叉模板:`--target x86_64-w64-mingw32` → 解析 `mingw-cross-gcc@` → 前端 `x86_64-w64-mingw32-g++`;交叉下 L3 build.mcpp 跳过(已有) | prepare.cppm:634-667,914-919 | e2e:交叉 build 产 `.exe` | +| A6 | archive/binutils 自包含:交叉 tarball 自带 `x86_64-w64-mingw32-ar` 于前端旁(同 musl「self-contained」) | registry.cppm:290-295 | 静态库 e2e | +| A7 | doctor 组合探测:Linux host 探 `xim:mingw-cross-gcc` | doctor.cppm:132-149 | `mcpp doctor` 报交叉链状态 | + +flags/link **无新增分支**:Windows 目标分支(flags.cppm:364-400)按 target 命中,`-static`/`-lstdc++exp`/ +`-static-libstdc++ -static-libgcc` 原样;交叉 sysroot 由 driver 自带(`/x86_64-w64-mingw32/{include,lib}` +编进 GCC,**无需** `--sysroot`)。这是 A 段「小」的根本原因——链接语义早在 0.0.89 就位。 + +## 4. Part B — 自包含 xlings-res tarball(打包,主要工作量) + +### 4.1 定位与选型依据(调研结论) + +调研确认:**无现成的 GCC-16 Linux-host 自包含 tarball**——winlibs 只出 Windows-host、Debian/Ubuntu 卡在 +GCC 13(无 `import std`)、只有 Arch `mingw-w64-gcc 16.1.0` 是发行版包(非自包含、仅 Arch)。mcpp 全线是 +**hermetic 自包含链**(llvm/musl-gcc/winlibs 皆 xlings-res 制品),故走**从源码 canadian-cross 自出 tarball**, +与 [[aarch64-musl-gcc-canadian-cross-rebuild]] 同路子。 + +### 4.2 构建配方(4-5 阶段 autotools bootstrap) + +上游 = **GCC 16.1.0 + mingw-w64-crt(MSVCRT)+ mingw-w64-headers + winpthreads**。选 GCC 16 与全线 +gcc@16.1.0 floor 对齐(GCC 15 有模块模板实例化丢失,见 remediation A2)。阶段: + +``` +1. binutils --target=x86_64-w64-mingw32 → 交叉 as/ld/ar +2. mingw-w64-headers --host=x86_64-w64-mingw32 install → Windows API 头 +3. gcc all-gcc install-gcc (core,尚无 libc) → 交叉 C/C++ 前端 +4. mingw-w64-crt (--with-default-msvcrt=msvcrt) + winpthreads → MSVCRT CRT + 线程 +5. gcc make / make install → 建 libstdc++(连带 import std 源 bits/std.cc) +``` + +关键旋钮:`--with-default-msvcrt=msvcrt`(本期 MSVCRT)、`--enable-threads=posix`(或 `mcf`)。crosstool-ng +的 `scripts/build/libc/mingw-w64.sh` 可自动化;本仓沿用 aarch64-musl 的手写脚本先落地、后收编亦可。产物 +重命名为 xlings-res 约定 `mingw-cross-gcc--linux-x86_64.tar.*`。 + +### 4.3 发布门禁 — `import std` 是唯一未证链路(make-or-break,先做原型) + +调研的核心风险:libstdc++ 自 GCC 15 起 target-independent 地装 `bits/std.cc`,mingw 交叉**预期**能用, +但**无人实证** `import std` 走 `x86_64-w64-mingw32` target。**打包前必须先跑通这一条端到端**,再谈发布: + +```sh +# 原型 gate(在候选 tarball 上验证,通不过就不发布) +x86_64-w64-mingw32-g++ -std=c++23 -fmodules \ + -c /x86_64-w64-mingw32/include/c++/16/bits/std.cc \ + -o std.o # ① std 模块 BMI 可出? +# ② trivial `import std;` 程序 → -static 链接 → 出 app.exe +# ③ wine ./app.exe 打印预期 → 运行期 import std 真通 +``` + +**发布门断言**(打包脚本硬检查,不是假设): +- tarball 内 `x86_64-w64-mingw32/include/c++/16/bits/std.cc` **存在且非空**(Ubuntu/Homebrew 有过空 modules 文件的坑); +- `libstdc++.modules.json` 路径可解析; +- `libstdc++exp.a` 在(``/`` 的 `-lstdc++exp` 依赖它); +- 前端旁有 `x86_64-w64-mingw32-{ar,ld,as}`(自包含)。 + +任一不满足 → 不发布,回构建配方。**这一段是全方案的真正风险所在**——A/C 都是机械工作,B 的 import std +可行性是决定「做不做得成」的那颗石头。 + +### 4.4 xlings-res 双端镜像 + xim-pkgindex(与 llvm/musl-gcc 同) + +工具链制品**不随 mcpp release 走**,是独立 xlings-res 制品仓 + 索引条目,版本跟随上游 GCC/mingw(同 llvm 模式, +见 [[release-publish-pipeline]] 第 4b 段解耦): + +1. **建仓/传资产**:`gh release create --repo xlings-res/mingw-cross-gcc ` + + `gtc release create/upload xlings-res/mingw-cross-gcc --tag `(GitCode 同名仓)。 +2. **双端逐资产 GET 核验**:`curl -sL -o /dev/null -w '%{http_code} %{size_download}'` 要 `200`+真实字节数, + **两端都验、必须 GET**(HEAD 会骗人;见 [[release-publish-pipeline]] 的「列出但下载 404」「gtc 假错」「api. 主机 404」三坑)。 +3. **索引条目**:xim-pkgindex 新增 `pkgs/m/mingw-cross-gcc.lua`——**linux 块**(host=linux),`[""] = "XLINGS_RES"` + + `["latest"] = { ref = "" }` + 每平台 sha256,格式抄 mcpp.lua 的 checked XLINGS_RES 条目。走 PR + + `gh pr merge --squash --admin`;合入后 `publish-artifact.yml` 自动重打包索引(内容哈希名)。 +4. **端到端验证**:`xlings update && xlings install mingw-cross-gcc@`(或 `mcpp toolchain install mingw-cross`)→ + 前端可 `-dumpmachine`。 + +## 5. Part C — Wine 真实验证(host 跑不了 PE) + +### 5.1 e2e 脚本 + +新增 `102_mingw_cross_wine.sh`(编号接现有 101;`# requires: mingw-cross wine` 于第 2 行,run_all.sh 约定): + +``` +install mingw-cross → mcpp new → build --target x86_64-w64-mingw32 + → 断言产出 target/x86_64-w64-mingw32/**/app.exe + → objdump/x86_64-w64-mingw32-objdump -p app.exe:导入表无 libstdc++-6.dll / libgcc_s / libwinpthread + (证 -static 生效;静态是 Wine 干净跑的前提,免拷 DLL 进 WINEPREFIX) + → WINEPREFIX=<隔离目录> wine ./app.exe:断言 stdout +覆盖:hello → 多模块 → import std(运行期)→ -lstdc++exp(/ 运行期)→ 静态库 → 增量(dyndep) +``` + +### 5.2 run_all.sh 能力探测 + +`run_all.sh` 的 `# requires:` 机制(:114-121)需加两个 cap 探针: +- `mingw-cross` — xim 是否装了 `x86_64-w64-mingw32-g++`(不吃 PATH,须显式用装的那份); +- `wine` — `command -v wine`(64-bit-capable;现代 wine 统一,不再单独 `wine64`)。 + +### 5.3 CI + +- **落 `cross-build-test.yml`**(已有交叉语义,musl aarch64 在此)或 `ci-linux-e2e.yml` 新步骤 + "Toolchain: MinGW cross → build → wine run"。**隔离 `WINEPREFIX`**(临时目录,用后即删),`-static` 免 DLL 部署。 +- **runner 自带 mingw 干扰**:Ubuntu runner 的 apt `g++-mingw-w64`(GCC 13)在 PATH,测试必须显式用 xim 装的 + GCC-16 那份,不吃 PATH(教训同 run_all.sh:70 native mingw 注释)。 +- 迭代走「临时分支只留相关 CI」省时模式(0.0.88 已验证)。 + +### 5.4 Wine 运行坑(调研) + +- **必须静态链接**:动态 mingw exe 在 Wine 下缺 `libstdc++-6.dll`/`libgcc_s_seh-1.dll`/`libwinpthread-1.dll` 会挂, + 除非拷进 WINEPREFIX;`-static` 是干净路径(本期链接默认已 `-static`,见 flags.cppm:386-393)。 +- **MSVCRT 选型正好利于 Wine**:`msvcrt.dll` 恒在 Wine 中,免 UCRT 的 `ucrtbase.dll` 依赖(§2.2 选型的附带收益)。 + +## 6. 开发规范流程(brainstorm → 交付) + +沿用 mcpp 既定研发流程,本方案的特殊约束标 **★**: + +1. **设计**:本文档(brainstorm 产出)→ 需要时 writing-plans 出实施计划。 +2. **★ 原型先行**:**先跑 §4.3 的 import std gate**,再动 mcpp 侧代码。这是唯一未证链路,失败则整个方案回炉; + 不能等 A/C 写完才在打包期开盲盒。 +3. **TDD + 零 diff 门**:Part A 对 native MinGW / 现有工具链**零行为变化**——验收沿用 0.0.89/0.0.90 的 + `build.ninja` 逐字节比对门(仅允许注释/变量重排白名单差异)。抽象错了立刻显形。 +4. **模块命名**:新模块/函数按职责命名,禁口袋名(ops/manager/utils),见 [[naming-no-grab-bag-modules]]。 +5. **两条独立发布轨**(见 §7):toolchain 制品轨 与 mcpp feature 轨,分开评审、分开发布。 +6. **门最后删**:若 docs/03-toolchains.md 有「cross not supported」注记,**最后一个 commit** 撤它——门在, + 中间态可安全合入 main;门删,即宣布支持(同 msvc-mingw-design §5.6 模式)。 + +## 7. PR / 版本(两条独立轨) + +### 7.1 轨一:toolchain 制品(`mingw-cross-gcc`)—— 不碰 mcpp 版本 + +独立 xlings-res 制品仓 + 索引条目,**版本跟随上游 GCC/mingw**,不 bump mcpp(同 llvm/musl-gcc)。流程 = +§4.4:建仓 → 双端镜像 → GET 核验 → xim-pkgindex PR(pkgs/m/mingw-cross-gcc.lua)→ `--squash --admin` → +publish-artifact.yml 自动发索引 → `xlings install` 验证。**先于**轨二可用(mcpp 侧代码要能解析到这个包)。 + +### 7.2 轨二:mcpp feature PR(Part A/C 代码)—— 带 mcpp 版本 bump + +Part A(host 二维化)+ Part C(e2e/CI)是一个带版本号的 mcpp feature PR: + +- **版本号两处同步(同一 commit)**:`mcpp.toml` `version` + `src/toolchain/fingerprint.cppm` `MCPP_VERSION` + (不一致 release smoke 失败;见 skill `.agents/skills/mcpp-release`)。版本可放「最后一个 PR」随合入带上。 +- **触发 release**:`gh workflow run release.yml --ref main`(或 push tag `v`)。 +- **publish-ecosystem 自动**(0.0.88 起):tag 触发后自动镜像 mcpp 四平台产物到 xlings-res/mcpp 双端 + + 向 xim-pkgindex 开 bump PR。**剩余手动**:① 双端 GET 核验;② `gh pr merge --repo openxlings/xim-pkgindex + --squash --admin`;③ `xlings install mcpp@` 验证;④ bump 本仓 `.xlings.json` bootstrap pin 直推 main。 + +**合入顺序**:轨一(制品可下)→ 轨二(mcpp 认它)。轨二的 e2e `# requires: mingw-cross` 依赖轨一已发布。 + +## 8. xlings 打通 + 真实验证(端到端闭环清单) + +一条命令都不能少的验收链(全绿才算「支持」): + +``` +① 原型 gate(§4.3) import std 走 mingw 交叉 target 真通(先决,失败即止) +② xlings 装制品 xlings install mingw-cross-gcc@ → -dumpmachine=x86_64-w64-mingw32 +③ mcpp 解析 mcpp toolchain install mingw-cross → mcpp toolchain list(Linux)见之 +④ 交叉构建 mcpp new demo && mcpp build --target x86_64-w64-mingw32 → app.exe +⑤ PE 校验 objdump -p app.exe 导入表无 libstdc++/libgcc/winpthread DLL(-static 生效) +⑥ Wine 运行 WINEPREFIX= wine ./app.exe → stdout 断言(运行期 import std) +⑦ C++23 库特性 / 程序 → -lstdc++exp → wine 跑通 +⑧ CI 常态化 cross-build-test.yml 每次跑 ②–⑦(隔离 WINEPREFIX,显式用 xim 的 GCC-16) +⑨ 生态闭环 xim-pkgindex 索引发布 + bootstrap 用户 `xlings install` 复现全链 +``` + +**真实验证 ≠ 单测**:§4.3 明确了 import std / libstdc++exp 的功能性「靠测不靠文档」——④–⑦ 的 Wine 运行断言 +是本方案能否宣布「支持」的唯一凭据,不能以「构建成功 + PE 有效」代替运行期验证(那会重蹈 [[issue195-hermetic-link-model]] +的「宿主污染假绿」)。 + +## 9. 非目标(本期明确不做) + +- **UCRT 交叉**:登记为 triple 可选 follow(`x86_64-w64-mingw32ucrt`,Rust `gnullvm` 式独立目标;届时 + abi.cppm 细化 ucrt,即 msvc-mingw-design §4.2 原计划)。 +- **i686 / arm64 mingw 交叉**:仅 x86_64;其余登记后续(机制同,triple 换)。 +- **llvm-mingw(Clang/libc++)**:mstorsjo/llvm-mingw 是 Linux-host 现成 tarball,但 Clang+libc++(另一条 + `import std` 路,非 `bits/std.cc`)——仅当 GCC mingw std 模块被证坏时的 fallback,不是本期后端。 +- **native winlibs MinGW(Part B)不动**:它是 (Windows,Windows,UCRT),本方案是 (Linux,Windows,MSVCRT), + 两条独立 target,互不干扰。 +- **`mcpp pack` 的 PE 支持**:pack/pipeline.cppm 是 ELF/patchelf 专属,独立议题。 +- **MSYS2/Cygwin 环境适配**;**Wine 之外的运行方式**(真机 Windows CI 是 native MinGW 的事)。 + +## 10. 排期与依赖 + +``` +★ 原型 gate(§4.3,import std 走 mingw 交叉)—— 先决,通不过则全案回炉 +│ +├─→ 轨一 B:mingw-cross-gcc 制品(canadian-cross 构建 + xlings-res 双端 + 索引 PR) +│ └ 独立版本,先于轨二可用 +│ +└─→ 轨二 A+C:mcpp feature PR + ├ A(host 二维化,对 native 零 diff:A1-A7) + └ C(Wine e2e + CI + 删门) + └ 硬依赖轨一制品已发布(e2e `# requires: mingw-cross`) +``` + +每轮沿用 0.0.88 流程:临时分支精简 CI 迭代 → 带版本号真 PR(轨二)→ 全 CI → 合入 → release → +生态闭环(publish-ecosystem 自动镜像 mcpp + 索引 PR;制品仓是额外手动 gh+gtc 双端 + GET 核验)。 + +## 11. 实测验证结果(2026-07-15,全链闭环) + +本方案不止纸面——已从源码建出 GCC-16.1.0 mingw-w64 MSVCRT 交叉链、发布到生态、端到端跑通。 + +### 11.1 制品与发布(轨一,已执行) + +- **构建**:from-source canadian-cross(binutils 2.44 + GCC 16.1.0 + mingw-w64 v12.0.0 CRT, + `--with-default-msvcrt=msvcrt`)。三坑已解:① PATH `$PREFIX/bin:/usr/bin:…`(target 工具优先、 + 裸 gcc 是真 host);② 勿全局 export CC(`--host` 的 CRT/libstdc++ 须用 target gcc,否则 + `_mingw_mac.h no`);③ `MAKEINFO=true`(texinfo 缺时 gccint.info 段错)。 +- **瘦身**:597MB→**104MB**(cc1plus/cc1/lto1/lto-dump 各 ~400MB 带调试符 → `strip --strip-unneeded`; + 删 share/{info,man,doc}、删 lto-dump)。strip 后 import std + wine 仍通。 +- **发布**:`xlings-res/mingw-cross-gcc` GitHub + GitCode 双端(gh 需先 push README 建默认分支; + gtc 需 `repo push` 建 main;gtc 是 python 脚本、须 `/usr/bin/python3` 绕开坏 shim);两端 ranged + GET 206 核验;`xim-pkgindex` PR #389 合入 → 索引重发 `xim-index-a220160`。 + +### 11.2 make-or-break gate 通过(§4.3) + +`x86_64-w64-mingw32-g++ -std=c++23 -fmodules bits/std.cc` 出 std.gcm → `import std;` 程序 +`-static -lstdc++exp` 链成 PE32+(仅 KERNEL32+msvcrt 导入)→ **wine 跑出** `std::println/vector/ +ranges::sort/print`。`bits/std.cc` 113KB、`libstdc++exp.a` 就位。 + +### 11.3 全链闭环(经真发布的生态) + +`mcpp toolchain install mingw-cross 16.1.0`(从 gitcode xlings-res 下 104MB、校验 sha256、装好) +→ `mcpp build --target x86_64-w64-mingw32`(mcpp 自身管线:xim 解析工具链→std BMI staging→ +多模块→PE 链)→ `crosswin.exe`(PE32+,仅 KERNEL32+msvcrt)→ wine 打印 import std + 多模块输出。 +e2e `102_mingw_cross_wine.sh` 过、单测 33/33。 + +### 11.4 真实验证抓到的 4 个 host≠target bug(纸面/单测抓不到,已全修) + +「真实验证 ≠ 单测」(§8)的活教材——这 4 个只有真跑 mcpp 全链才暴露: + +| # | 位置 | 症状 | 修 | +|---|---|---|---| +| 1 | gcc.cppm `find_std_module_source` | 交叉的 `bits/std.cc` 在 `//include/c++//`,只找 host `/include/c++/`→"provides no std module source" | 按前端名剥 `-g++` 得 triple 补查 target 子目录 | +| 2 | gcc.cppm `std_module_build_command` | `!is_musl` 才跳外部 -B → 交叉误接 Linux binutils,Linux `as` 编 PE/SEH asm(`.def`/`.seh_proc`)报错 | `!is_musl && !is_mingw`(自包含) | +| 3 | flags.cppm 主链 -B | 同因 | 加 `!isMingwTc` | +| 4 | flags.cppm PE link 分支 | 整段(`-static`/`-lstdc++exp`)在 `if constexpr(is_windows)` **host** 门内→Linux 交叉永不进→`std::print` 的 `__open_terminal` 未定义 | 提为运行期 `if(isMingwTc)` **按 target** 求值、host 无关(early return);native Windows mingw 亦走此支(统一) | + +Bug 4 正是本文档反复强调的 host≠target 违例——PE 产物形态判断必须按 target 而非 host constexpr。 +Part A 表(§3)相应扩展:除 registry/lifecycle/prepare,还须改 gcc.cppm 探测/预编译 + flags.cppm +link 分支(host≠target 化)。 diff --git a/.github/workflows/cross-build-test.yml b/.github/workflows/cross-build-test.yml index 50a1d4f..088bf8b 100644 --- a/.github/workflows/cross-build-test.yml +++ b/.github/workflows/cross-build-test.yml @@ -11,6 +11,11 @@ name: cross-build-test # target | toolchain | host→target | run # ----------------------|---------------------------------|---------------|----- # aarch64-linux-musl | aarch64-linux-musl-gcc@16.1.0 | x86_64→arm64 | qemu +# x86_64-w64-mingw32 | mingw-cross-gcc@16.1.0 (MSVCRT) | linux→windows | wine +# +# The mingw row is OS-cross (same arch, different OS/ABI: ELF→PE), so it lives +# in its own job below with wine verification instead of the qemu arch matrix. +# See .agents/docs/2026-07-15-mingw-linux-cross-windows-design.md. # # mcpp resolves a cross `--target -musl` build to the triple-named cross # gcc musl toolchain from the xlings ecosystem (xim:-gcc, see @@ -163,3 +168,80 @@ jobs: echo "== xlings --version (best-effort under qemu) ==" xver=$($RUN "$XLINGS_XBIN" --version 2>&1 || true) echo "$xver" + + # ── Linux → Windows MinGW cross (OS-cross, same arch: ELF→PE) ───────────── + # Builds a demo project for x86_64-w64-mingw32 with the from-source GCC-16 + # MSVCRT cross toolchain, asserts the artefact is a fully-static PE, and runs + # it under wine. Delegated to the e2e harness (tests/e2e/102_mingw_cross_wine.sh, + # `# requires: mingw-cross wine`) so the run_all cap-gating stays the single + # source of truth. See 2026-07-15-mingw-linux-cross-windows-design.md Part C. + mingw-cross-wine: + name: mingw-cross linux→windows (build + wine run) + runs-on: ubuntu-24.04 + timeout-minutes: 60 + env: + MCPP_HOME: /home/runner/.mcpp + MCPP_VERBOSE: "1" + steps: + - uses: actions/checkout@v4 + + - name: Cache mcpp sandbox + uses: actions/cache@v4 + with: + path: ~/.mcpp + key: mcpp-sandbox-${{ runner.os }}-mingw-cross-${{ hashFiles('mcpp.toml', '.xlings.json') }} + restore-keys: | + mcpp-sandbox-${{ runner.os }}-mingw-cross- + + - name: Cache xlings + uses: actions/cache@v4 + with: + path: ~/.xlings + key: xlings-${{ runner.os }}-v2-${{ hashFiles('.xlings.json') }} + restore-keys: | + xlings-${{ runner.os }}-v2- + + - name: Install wine + run: | + sudo dpkg --add-architecture i386 || true + sudo apt-get update -qq + sudo apt-get install -y wine64 wine || sudo apt-get install -y wine + wine --version + + - name: Bootstrap mcpp via xlings + env: + XLINGS_NON_INTERACTIVE: '1' + XLINGS_VERSION: '0.4.62' + run: | + tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz" + curl -fsSL -o "/tmp/${tarball}" \ + "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" + tar -xzf "/tmp/${tarball}" -C /tmp + "/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install + export PATH="$HOME/.xlings/subos/default/bin:$PATH" + find "$HOME/.xlings" -name '.xlings-index-cache.json' -delete 2>/dev/null || true + xlings config --mirror GLOBAL 2>/dev/null || true + xlings update -y 2>/dev/null || xlings update 2>/dev/null || true + xlings install mcpp -y + echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" + echo "MCPP_BOOT=$HOME/.xlings/subos/default/bin/mcpp" >> "$GITHUB_ENV" + + - name: Self-host build (fresh host mcpp) + run: | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" + "$MCPP_BOOT" self config --mirror GLOBAL 2>/dev/null || true + "$MCPP_BOOT" build + MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") + test -x "$MCPP" + "$MCPP" self config --mirror GLOBAL + echo "MCPP=$MCPP" >> "$GITHUB_ENV" + + - name: Install mingw-cross toolchain + run: | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" + "$MCPP" toolchain install mingw-cross 16.1.0 + + - name: "e2e: cross-build + wine run" + run: | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" + bash tests/e2e/102_mingw_cross_wine.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 9606263..06415e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ > 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。 > 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。 +## [0.0.92] — 2026-07-15 + +### 新增 + +- **Linux → Windows MinGW-w64 交叉工具链(`mingw-cross`)—— host≠target 一等公民**。 + 在 Linux 主机上交叉编译出 Windows x86_64 PE,含 `import std`。补 0.0.89 + `msvc-mingw-design` §4.4/§7 登记的延期项(交叉维:host≠target)。 + - **工具链**:从源码构建的 GCC 16.1.0 mingw-w64 **MSVCRT** 交叉链(triple + `x86_64-w64-mingw32`,跟随 Rust Tier-1 `x86_64-pc-windows-gnu` 的 CRT 选型); + 自包含(自带 binutils/CRT/libstdc++ 含 `bits/std.cc` + `libstdc++exp`); + 发布于 `xlings-res/mingw-cross-gcc`(GitHub+GitCode)+ `xim-pkgindex`。 + - **mcpp 侧**:用户名 `mingw-cross` → xim `mingw-cross-gcc`(前端 + `x86_64-w64-mingw32-g++`);解除 MinGW 的 Windows-host 门(Linux 主机可装,native + `mingw` 仍 Windows-only);`--target x86_64-w64-mingw32` 约定解析(默认 static); + 跳过 glibc/linux-headers(PE 自带 CRT)。 + - **host≠target 化**:PE 产物形态判断一律按 target 而非 host constexpr——std 模块源 + 探测补 `//include/c++/` 子目录;自包含工具链跳过外部 binutils `-B` + (musl + mingw);MinGW link 分支(`-static` / `-lstdc++exp`)由 `if constexpr(is_windows)` + host 门提为运行期 `is_mingw_target` target 判定。 + - **验证**:e2e `102_mingw_cross_wine.sh`(`# requires: mingw-cross wine`,build --target + → PE 静态自包含断言 → wine 跑 import std + 多模块);CI `cross-build-test.yml` 加 + `mingw-cross-wine` job(OS-cross,wine)。全链实测闭环(install→build→wine)。 + - 设计:`.agents/docs/2026-07-15-mingw-linux-cross-windows-design.md`。 + ## [0.0.91] — 2026-07-15 ### 新增 diff --git a/mcpp.toml b/mcpp.toml index 4b2982c..bf58e45 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "0.0.91" +version = "0.0.92" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/src/build/flags.cppm b/src/build/flags.cppm index 5417163..dce0b41 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -215,11 +215,14 @@ CompileFlags compute_flags(const BuildPlan& plan) { f.sysroot = link_toolchain_flags; } - // Binutils -B flag — a GCC/libstdc++ payload concern (musl bundles its - // own as/ld; Clang and MSVC never take an external binutils). - bool isMuslTc = mcpp::toolchain::is_musl_target(plan.toolchain); + // Binutils -B flag — a GCC/libstdc++ payload concern (musl and MinGW-w64 + // cross both bundle their own as/ld; Clang and MSVC never take an external + // binutils). MinGW must not get the Linux binutils -B — its PE/SEH output + // is only assemblable by its own x86_64-w64-mingw32-as. + bool isMuslTc = mcpp::toolchain::is_musl_target(plan.toolchain); + bool isMingwTc = mcpp::toolchain::is_mingw_target(plan.toolchain); std::filesystem::path binutilsBin; - if (!isMuslTc && caps.stdlib_id == "libstdc++") { + if (!isMuslTc && !isMingwTc && caps.stdlib_id == "libstdc++") { auto ar = mcpp::toolchain::archive_tool(plan.toolchain); if (!ar.empty()) binutilsBin = ar.parent_path(); @@ -361,6 +364,27 @@ CompileFlags compute_flags(const BuildPlan& plan) { if (prof.lto) link_extra += " -flto"; if (prof.strip) link_extra += " -s"; + // MinGW PE link — keyed on the TARGET (is_mingw_target), NOT the host: a + // Linux-hosted cross build produces exactly the same PE link as a native + // Windows MinGW build (host≠target). No rpath/loader/payload model. Static + // + libstdc++exp (std::print's __open_terminal/__write_to_terminal live in + // libstdc++exp.a, not plain libstdc++). Self-contained binutils → no -B. + if (isMingwTc) { + std::string mingw_static; + std::string mingw_stdexp; + if (caps.stdlib_id == "libstdc++") { + // `-static` for the whole link — MinGW's standalone-exe convention + // (the piecemeal -static-libstdc++ recipe still pulls + // libwinpthread-1.dll). Opt out via [build] static_stdlib=false. + if (f.staticStdlib || f.linkage == "static") + mingw_static = " -static"; + mingw_stdexp = " -lstdc++exp"; + } + f.ld = std::format("{}{}{}{}{}", mingw_static, static_stdlib, + user_ldflags, mingw_stdexp, link_extra); + return f; + } + if constexpr (mcpp::platform::is_windows) { if (isMsvcDialect) { // Native cl.exe: link.exe does the link (SeparateLinker). Search @@ -374,30 +398,10 @@ CompileFlags compute_flags(const BuildPlan& plan) { f.ld = libpaths + user_ldflags; return f; } - // PE link: no rpath/loader/payload model. MSVC-ABI Clang needs - // nothing extra (MSVC STL/SDK via the driver); MinGW adds the static - // libstdc++/libgcc pair (static_stdlib above) and -B so its own - // binutils resolve, plus `-static` for full static when requested - // (MinGW supports it, unlike MSVC-ABI links). - std::string mingw_static; - std::string mingw_stdexp; - if (caps.stdlib_id == "libstdc++") { - // `-static` for the whole link — winlibs' own recommendation for - // standalone exes. The piecemeal recipe (-static-libstdc++ + - // -Wl,-Bstatic -lwinpthread) verifiably loses to the driver's - // implicit closing libs: CI import tables still showed - // libwinpthread-1.dll. System DLLs (KERNEL32/UCRT) still resolve - // via their import libs. Tied to staticStdlib so - // [build] static_stdlib=false opts back into DLL-coupled links. - if (f.staticStdlib || f.linkage == "static") - mingw_static = " -static"; - // std::print's terminal probe (__open_terminal / - // __write_to_terminal, bits/print.h) lives in libstdc++exp.a on - // Windows targets — plain -lstdc++ leaves them undefined. - mingw_stdexp = " -lstdc++exp"; - } - f.ld = std::format("{}{}{}{}{}{}", mingw_static, static_stdlib, b_flag, - user_ldflags, mingw_stdexp, link_extra); + // PE link, MSVC-ABI Clang (native MinGW is handled by the target-keyed + // branch above and has already returned): no rpath/loader/payload — + // MSVC STL/SDK come via the driver, nothing extra needed. + f.ld = std::format("{}{}{}", static_stdlib, user_ldflags, link_extra); } else if constexpr (mcpp::platform::needs_explicit_libcxx) { // macOS. Two min-version concerns (see xlings // .agents/docs/2026-06-05-macos-min-version-support.md): diff --git a/src/build/prepare.cppm b/src/build/prepare.cppm index bae3091..fa49709 100644 --- a/src/build/prepare.cppm +++ b/src/build/prepare.cppm @@ -664,6 +664,19 @@ prepare_build(bool print_fingerprint, && m->buildConfig.linkage.empty()) { m->buildConfig.linkage = "static"; } + // Convention: the Windows PE cross target `x86_64-w64-mingw32` without + // an explicit [target.X] override resolves to the from-source GCC-16 + // MSVCRT cross toolchain. host≠target — an ELF frontend producing PE. + // Default static linkage: MinGW standalone-exe convention and the clean + // path for running the artifact under wine (no DLL deployment needed). + // See .agents/docs/2026-07-15-mingw-linux-cross-windows-design.md. + if (overrides.target_triple == "x86_64-w64-mingw32" + && (it == m->targetOverrides.end() || it->second.toolchain.empty())) + { + tcSpec = "mingw-cross@16.1.0"; + if (m->buildConfig.linkage.empty()) + m->buildConfig.linkage = "static"; + } } if (overrides.force_static) m->buildConfig.linkage = "static"; diff --git a/src/toolchain/fingerprint.cppm b/src/toolchain/fingerprint.cppm index 98bf167..b9b24ef 100644 --- a/src/toolchain/fingerprint.cppm +++ b/src/toolchain/fingerprint.cppm @@ -18,7 +18,7 @@ import mcpp.toolchain.detect; export namespace mcpp::toolchain { -inline constexpr std::string_view MCPP_VERSION = "0.0.91"; +inline constexpr std::string_view MCPP_VERSION = "0.0.92"; struct FingerprintInputs { Toolchain toolchain; diff --git a/src/toolchain/gcc.cppm b/src/toolchain/gcc.cppm index fb5a070..aedaf7a 100644 --- a/src/toolchain/gcc.cppm +++ b/src/toolchain/gcc.cppm @@ -71,6 +71,31 @@ std::optional find_std_module_source( auto p = root / "include" / "c++" / std::string(version) / "bits" / "std.cc"; if (std::filesystem::exists(p)) return p; + // Cross toolchains (e.g. MinGW-w64 `x86_64-w64-mingw32-g++`) install the + // TARGET libstdc++ — and its bits/std.cc — under a target-triple subdir: + // //include/c++//bits/std.cc + // rather than the host-native /include/c++/. Derive the triple from + // the frontend filename (strip the -g++/-gcc/-c++ suffix) and look there. + { + std::error_code ec; + auto stem = cxx_binary.stem().string(); + for (std::string_view suf : {"-g++", "-gcc", "-c++"}) { + if (stem.size() > suf.size() && stem.ends_with(suf)) { + std::string triple = stem.substr(0, stem.size() - suf.size()); + auto base = root / triple / "include" / "c++"; + auto pv = base / std::string(version) / "bits" / "std.cc"; + if (std::filesystem::exists(pv, ec)) return pv; + if (std::filesystem::exists(base, ec)) { + for (auto& entry : std::filesystem::directory_iterator(base, ec)) { + auto cand = entry.path() / "bits" / "std.cc"; + if (std::filesystem::exists(cand, ec)) return cand; + } + } + break; + } + } + } + // Version-dir scan fallback: the header dir doesn't always equal the // full driver version (e.g. distro / MinGW-w64 builds using the major // 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, const std::filesystem::path& cacheDir, std::string_view sysrootFlag, std::string_view cppStandardFlag) { - // musl-cross-make toolchains bundle their own as/ld (and for a cross - // target the host's binutils would mis-assemble, e.g. `as: unrecognized - // option '-EL'`). Only the glibc gcc needs an external binutils package - // wired via -B. Mirrors the guard in build/flags.cppm. + // musl-cross-make AND MinGW-w64 cross toolchains bundle their own as/ld + // (and for a cross target the host's binutils would mis-assemble — e.g. + // the Linux `as` chokes on MinGW's PE/SEH directives `.def`/`.seh_proc`). + // Only the glibc gcc needs an external binutils package wired via -B. + // Mirrors the guard in build/flags.cppm. std::string bFlag; - if (!is_musl_target(tc)) { + if (!is_musl_target(tc) && !is_mingw_target(tc)) { if (auto binutilsBin = find_binutils_bin(tc.binaryPath)) { bFlag = std::format(" -B{}", mcpp::xlings::shq(binutilsBin->string())); } diff --git a/src/toolchain/lifecycle.cppm b/src/toolchain/lifecycle.cppm index da926ad..5dd278d 100644 --- a/src/toolchain/lifecycle.cppm +++ b/src/toolchain/lifecycle.cppm @@ -410,9 +410,12 @@ export int toolchain_install(const mcpp::config::GlobalConfig& cfg, // Ensure sysroot dependencies (glibc, linux-headers) are installed. // These are required for C library + kernel headers during compilation. // musl-gcc is self-contained and doesn't need these; neither do - // Windows (llvm/mingw — PE, own CRT) or macOS (SDK) toolchains. + // Windows (llvm/mingw — PE, own CRT) or macOS (SDK) toolchains, nor a + // Linux-hosted mingw CROSS toolchain (host is Linux but the TARGET is + // Windows PE with its own CRT — glibc/linux-headers are irrelevant). // Mirrors the platform guard on prepare.cppm's first-run install. if (!spec->isMusl + && pkg.ximName != "mingw-cross-gcc" && !mcpp::platform::is_windows && !mcpp::platform::is_macos) { for (auto dep : {"xim:glibc", "xim:linux-headers"}) { mcpp::log::verbose("toolchain", std::format("installing dep: {}", dep)); diff --git a/src/toolchain/registry.cppm b/src/toolchain/registry.cppm index ec793de..cbb660b 100644 --- a/src/toolchain/registry.cppm +++ b/src/toolchain/registry.cppm @@ -107,6 +107,10 @@ std::vector frontend_candidates_for(std::string_view ximName, if (ximName == "llvm") return mcpp::toolchain::llvm::frontend_candidates(); if (ximName == "msvc") return {"cl.exe"}; if (ximName == "mingw-gcc") return {"g++.exe", "g++"}; + // Linux-hosted MinGW-w64 cross toolchain: an ELF frontend that produces + // Windows PE. Triple-prefixed name (like the musl cross tools), never the + // host g++ — a cross build must never silently fall back to native. + if (ximName == "mingw-cross-gcc") return {"x86_64-w64-mingw32-g++"}; return {"g++", "clang++", "x86_64-linux-musl-g++", "cl.exe"}; } @@ -180,6 +184,11 @@ XimToolchainPackage to_xim_package(const ToolchainSpec& spec) { // `mingw-gcc` (winlibs GCC+MinGW-w64 UCRT builds mirrored at xlings-res). if (ximCompiler == "mingw") ximCompiler = "mingw-gcc"; + // Linux→Windows MinGW-w64 cross: user-facing `mingw-cross`, xim package + // `mingw-cross-gcc` (from-source GCC-16 MSVCRT cross, xlings-res). host≠target: + // an ELF toolchain producing PE. See 2026-07-15-mingw-linux-cross-windows-design. + if (ximCompiler == "mingw-cross") + ximCompiler = "mingw-cross-gcc"; pkg.ximName = ximCompiler; pkg.ximVersion = spec.version; @@ -233,6 +242,8 @@ std::string display_label(std::string_view compiler, std::string_view version) { return std::format("gcc {}-musl", version); if (compiler == "mingw-gcc") return std::format("mingw {}", version); + if (compiler == "mingw-cross-gcc") + return std::format("mingw-cross {}", version); return std::format("{} {}", compiler, version); } @@ -253,6 +264,12 @@ bool matches_default_toolchain(std::string_view configuredDefault, && configuredDefault == std::format("mingw@{}", version)) { return true; } + // Cross MinGW: installed-payload row reports xim name (mingw-cross-gcc); + // user-facing spec is mingw-cross@. + if (compiler == "mingw-cross-gcc" + && configuredDefault == std::format("mingw-cross@{}", version)) { + return true; + } return false; } @@ -266,9 +283,13 @@ std::vector> available_toolchain_indexes() { {"musl-gcc", "musl-gcc"}, {mcpp::toolchain::llvm::package_name(), "llvm"}, }; - // Windows-native toolchain — only meaningful to list on Windows hosts. + // MinGW-w64 toolchains are host-conditional (host≠target axis): + // - native mingw-gcc (produces PE, runs on Windows) → Windows hosts + // - cross mingw-cross-gcc (ELF frontend, produces PE) → Linux hosts if constexpr (mcpp::platform::is_windows) out.emplace_back("mingw-gcc", "mingw-gcc"); + else if constexpr (mcpp::platform::is_linux) + out.emplace_back("mingw-cross-gcc", "mingw-cross-gcc"); return out; } @@ -286,10 +307,17 @@ std::filesystem::path archive_tool(const Toolchain& tc) { if (is_clang(tc)) return mcpp::toolchain::clang::archive_tool(tc); // MinGW bundles its own binutils next to the frontend (self-contained, - // like musl) — never an external binutils xpkg. + // like musl) — never an external binutils xpkg. Native (Windows-host) ships + // `ar.exe`; the Linux-hosted cross ships the triple-prefixed ELF tool + // `x86_64-w64-mingw32-ar`. Try the cross form first, then native. if (is_mingw_target(tc)) { - auto ar = tc.binaryPath.parent_path() / "ar.exe"; std::error_code ec; + auto dir = tc.binaryPath.parent_path(); + if (!tc.targetTriple.empty()) { + auto crossAr = dir / (tc.targetTriple + "-ar"); + if (std::filesystem::exists(crossAr, ec)) return crossAr; + } + auto ar = dir / "ar.exe"; if (std::filesystem::exists(ar, ec)) return ar; return {}; } diff --git a/tests/e2e/102_mingw_cross_wine.sh b/tests/e2e/102_mingw_cross_wine.sh new file mode 100644 index 0000000..8fe39df --- /dev/null +++ b/tests/e2e/102_mingw_cross_wine.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# requires: mingw-cross wine +# Linux → Windows MinGW cross: build a multi-module + `import std` project for +# x86_64-w64-mingw32, verify the PE is statically self-contained, run it under +# wine. Realizes Part C of 2026-07-15-mingw-linux-cross-windows-design.md. +set -e + +TMP=$(mktemp -d) +trap "rm -rf $TMP" EXIT +cd "$TMP" + +"$MCPP" new crosswin +cd crosswin + +# A named module that pulls in `import std` — exercises the cross libstdc++ +# std module (bits/std.cc built for the mingw target). +cat > src/banner.cppm <<'EOF' +export module crosswin.banner; +import std; +export auto banner() -> void { + std::println("hello from x86_64-w64-mingw32, running under wine"); +} +EOF + +cat > src/main.cpp <<'EOF' +import std; +import crosswin.banner; +int main() { + banner(); + std::println("mingw-cross import-std + multi-module OK"); + return 0; +} +EOF + +TRIPLE=x86_64-w64-mingw32 + +# ── build for the Windows PE target ──────────────────────────────────────── +"$MCPP" build --target "$TRIPLE" > "$TMP/build.log" 2>&1 || { + echo "cross build failed:"; cat "$TMP/build.log"; exit 1; } + +EXE="$(find target/$TRIPLE -name '*.exe' -type f | head -1)" +[[ -n "$EXE" && -f "$EXE" ]] || { echo "no .exe produced under target/$TRIPLE"; exit 1; } + +# PE sanity: must be a PE32+ console binary. +file "$EXE" | grep -q "PE32+" || { echo "artifact is not PE32+: $(file "$EXE")"; exit 1; } + +# ── static self-containment: no C++/gcc/pthread runtime DLLs in the import +# table (the clean path for wine — nothing to copy into the prefix). Only +# the OS DLLs (kernel32/msvcrt/user32…) are allowed. ───────────────────── +OBJDUMP="$(dirname "$(command -v x86_64-w64-mingw32-g++ 2>/dev/null || \ + find "$HOME/.xlings" "${MCPP_HOME:-$HOME/.mcpp}" -name 'x86_64-w64-mingw32-g++' 2>/dev/null | head -1)")/x86_64-w64-mingw32-objdump" +if [[ -x "$OBJDUMP" ]]; then + if "$OBJDUMP" -p "$EXE" | grep -iE "DLL Name: (libstdc\+\+|libgcc|libwinpthread)" ; then + echo "artifact dynamically depends on a runtime DLL — expected fully static" + exit 1 + fi +fi + +# ── run under wine in an isolated prefix (deleted on exit) ────────────────── +export WINEPREFIX="$TMP/wineprefix" WINEDEBUG=-all +out="$(wine "$EXE" 2>/dev/null)" +[[ "$out" == *"running under wine"* ]] || { echo "banner() output missing: $out"; exit 1; } +[[ "$out" == *"import-std + multi-module OK"* ]] || { echo "main output missing: $out"; exit 1; } + +# ── ninja sanity: std BMI + module BMI were built for the cross target ────── +build_ninja="$(find target/$TRIPLE -name build.ninja | head -1)" +grep -q "gcm.cache/std.gcm" "$build_ninja" || { echo "ninja missing std BMI"; exit 1; } +grep -q "gcm.cache/crosswin.banner.gcm" "$build_ninja" || { echo "ninja missing module BMI"; exit 1; } + +echo "OK" diff --git a/tests/e2e/run_all.sh b/tests/e2e/run_all.sh index 1914207..b604dee 100755 --- a/tests/e2e/run_all.sh +++ b/tests/e2e/run_all.sh @@ -52,6 +52,16 @@ case "$OS" in || [[ -x "${MCPP_HOME}/registry/data/xpkgs/xim-x-musl-gcc/15.1.0/bin/x86_64-linux-musl-g++" ]]; then CAPS+=(musl) fi + # mingw-cross: the Linux-hosted MinGW-w64 cross toolchain (xim + # mingw-cross-gcc, GCC 16 MSVCRT). Must be the xim-managed GCC-16 build, + # NOT the distro apt g++-mingw-w64 (GCC 13 — no `import std`). Probe the + # xlings/mcpp payload location, mirroring the musl probe above. + if [[ -x "$HOME/.xlings/data/xpkgs/xim-x-mingw-cross-gcc/16.1.0/bin/x86_64-w64-mingw32-g++" ]] \ + || [[ -x "${MCPP_HOME}/registry/data/xpkgs/xim-x-mingw-cross-gcc/16.1.0/bin/x86_64-w64-mingw32-g++" ]]; then + CAPS+=(mingw-cross) + fi + # wine: run cross-built Windows PE artifacts on the Linux host. + command -v wine &>/dev/null && CAPS+=(wine) # pack capability: ELF + patchelf both required if [[ " ${CAPS[*]} " == *" patchelf "* ]]; then CAPS+=(pack) diff --git a/tests/unit/test_toolchain_registry.cpp b/tests/unit/test_toolchain_registry.cpp index fc41f89..fb3706a 100644 --- a/tests/unit/test_toolchain_registry.cpp +++ b/tests/unit/test_toolchain_registry.cpp @@ -32,6 +32,25 @@ TEST(ToolchainRegistry, MapsGccMuslSuffixToMuslGccPackage) { EXPECT_FALSE(pkg.needsGccPostInstallFixup); } +TEST(ToolchainRegistry, MapsMingwCrossSpecToCrossPackage) { + // Linux → Windows MinGW cross: user-facing `mingw-cross` maps to the xim + // package `mingw-cross-gcc`, an ELF frontend producing PE. Distinct from the + // Windows-native `mingw`/`mingw-gcc`. No ELF post-install fixup (PE target). + auto spec = parse_toolchain_spec("mingw-cross@16.1.0"); + ASSERT_TRUE(spec.has_value()) << spec.error(); + + auto pkg = to_xim_package(*spec); + EXPECT_FALSE(spec->isMusl); + EXPECT_EQ(pkg.ximName, "mingw-cross-gcc"); + EXPECT_EQ(pkg.ximVersion, "16.1.0"); + ASSERT_FALSE(pkg.frontendCandidates.empty()); + EXPECT_EQ(pkg.frontendCandidates.front(), "x86_64-w64-mingw32-g++"); + EXPECT_FALSE(pkg.needsGccPostInstallFixup); + EXPECT_EQ(display_label("mingw-cross-gcc", "16.1.0"), "mingw-cross 16.1.0"); + EXPECT_TRUE(matches_default_toolchain("mingw-cross@16.1.0", + "mingw-cross-gcc", "16.1.0")); +} + TEST(ToolchainRegistry, MapsLlvmAndClangAliasesToLlvmPackage) { auto llvmSpec = parse_toolchain_spec("llvm", "20.1.7"); auto clangSpec = parse_toolchain_spec("clang@20.1.7");