Skip to content

Commit f6fd39e

Browse files
authored
feat(toolchain): backend abstraction (CommandDialect) + MinGW-w64 via the xlings ecosystem — 0.0.89 (#211)
* refactor(toolchain): CommandDialect + backend surface normalization (Part A) New mcpp.toolchain.dialect: command-line *spellings* (gnu vs msvc) as a value-type traits table — flags.cppm and ninja rule templates now spell -I/-D/-std=/-c/-o/-O/-g/ar-invocations through it instead of hardcoding GNU syntax. BmiTraits gains the module-flag spellings (compileModulesFlag, stdBmiUsePrefix, moduleOutputPrefix, bmiSearchPrefix), replacing the is_clang()/stdlib_id branches in flags.cppm. ProviderCapabilities gains has_builtin_p1689_scan (drops ninja_backend's is_gcc gate). Toolchain gains envOverrides (EnvVar list — std::pair member trips GCC-16 module pendings) merged into ninja's child env; the MSVC backend will carry INCLUDE/LIB/PATH through it. gcc::std_module_build_commands normalizes the provider surface to the command-sequence shape clang already has. resolve_link_model returns the empty PE model explicitly on Windows. Zero-diff verified: build.ninja byte-identical (modulo the mcpp self-path var) for GCC and LLVM toolchains on a multi-module import-std project; mcpp test 31/31. * feat(toolchain): MinGW-w64 GCC via the xlings ecosystem (mingw@16.1.0) (0.0.89) User-facing 'mingw' spec → xim:mingw-gcc (winlibs GCC 16.1.0 + MinGW-w64 14.0.0 UCRT standalone build, mirrored at xlings-res/mingw-gcc). Reuses the GCC backend end-to-end (gcm module pipeline, bits/std.cc import std); Windows adds static libstdc++/libgcc defaults so produced exes run standalone, and -static under linkage="static". archive_tool resolves the bundled ar.exe; std.cc lookup gains a version-dir scan fallback. Fixes ridden along: toolchain list 'Available' + partial-version resolution read xpkg versions for the HOST platform (was hardcoded linux — empty on win/mac); toolchain install no longer pulls glibc/linux-headers on win/mac. e2e 97 (requires: windows cap) + ci-windows MinGW step + unit tests (dialect rows, mingw spec mapping); docs + CHANGELOG; version 0.0.89. * fix(linkmodel): key the PE early-out on the target, not the host The Windows-host if-constexpr broke the linkmodel contract tests on Windows CI (they resolve synthetic Linux-shaped toolchains anywhere). Target-keyed is also the semantics a future cross build needs. * fix(flags): MinGW links libstdc++exp for std::print terminal symbols std::vprint_unicode's __open_terminal/__write_to_terminal live in libstdc++exp.a on Windows-GCC targets — first real MinGW link on CI failed with undefined references from bits/print.h. * fix(flags): force winpthread static on MinGW links winlibs POSIX-threads libstdc++ otherwise leaves produced exes depending on libwinpthread-1.dll — the standalone-exe e2e caught it (empty output, missing-DLL abort). -Wl,-Bstatic -lwinpthread -Wl,-Bdynamic is the standard recipe; libwinpthread.a ships in the payload. * test(e2e): 97 asserts the exe import table (objdump) + diagnostic output The silent standalone-run failure gave nothing to debug — inspect DLL imports directly and dump them on any failure. * fix(flags): MinGW default links -static — the piecemeal winpthread recipe loses to driver implicit libs CI import table still showed libwinpthread-1.dll after -Wl,-Bstatic -lwinpthread; winlibs' documented answer for standalone exes is a full -static link (system DLLs still resolve via import libs). Gated on staticStdlib so [build] static_stdlib=false opts out.
1 parent 1d646ac commit f6fd39e

18 files changed

Lines changed: 586 additions & 53 deletions

.github/workflows/ci-windows.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,16 @@ jobs:
220220
cd hello_win
221221
"$MCPP_SELF" run
222222
223+
# MinGW-w64 GCC via the xlings ecosystem (xim:mingw-gcc → xlings-res
224+
# winlibs mirror): install → default → modules build/run → standalone
225+
# exe. Same flow as e2e 97 but as a visible CI step. Payload is cached
226+
# via the mcpp sandbox cache after the first run.
227+
- name: "Toolchain: MinGW — install → build → run (xim:mingw-gcc)"
228+
shell: bash
229+
run: |
230+
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
231+
MCPP="$MCPP_SELF" bash tests/e2e/97_mingw_toolchain.sh
232+
223233
# windows-latest ships VS 2022 Enterprise with the VC workload, so
224234
# msvc@system detection MUST succeed here — a failure is a regression
225235
# in the discovery/identification chain (vswhere → env → paths).

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,36 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.89] — 2026-07-13
7+
8+
### 新增
9+
10+
- **MinGW-w64 工具链入 xlings 生态(Windows 原生 GCC,无需 Visual Studio)**
11+
`mcpp toolchain install mingw 16.1.0` / `default mingw@16.1.0`:xim
12+
`mingw-gcc`(winlibs GCC 16.1.0 + MinGW-w64 14.0.0 UCRT 独立构建,镜像于
13+
xlings-res/mingw-gcc,GitHub+GitCode 双端)。复用既有 GCC 后端
14+
(gcm 模块管线、libstdc++ `bits/std.cc``import std`);Windows 上
15+
libstdc++/libgcc 默认静态链接(产物免带 DLL,`[build] static_stdlib=false`
16+
可关),`linkage="static"` 升级全静态。e2e 97 覆盖 install→default→
17+
多模块 build/run→独立 exe 验证;ci-windows 新增专项步骤。
18+
连带修复:`toolchain list` 的 Available 段与部分版本解析此前硬编码按
19+
"linux" 平台读取 xpkg 版本(Windows/macOS 上恒空);`toolchain install`
20+
在 Windows/macOS 不再错误安装 glibc/linux-headers 依赖。
21+
22+
### 重构
23+
24+
- **工具链后端抽象层(Part A,对 GCC/Clang 零行为变化,build.ninja 零 diff
25+
实证)**。新增 `mcpp.toolchain.dialect`(命令行拼写 traits:gnu/msvc 两行
26+
数据,`-I/-D/-std=/-c/-o/-O/-g/ar` 及归档命令模板经其发射);`BmiTraits`
27+
并入模块旗标拼写(`compileModulesFlag`/`stdBmiUsePrefix`/
28+
`moduleOutputPrefix`/`bmiSearchPrefix`),flags.cppm 的 is_clang 分支改由
29+
数据驱动;`ProviderCapabilities.has_builtin_p1689_scan` 取代 ninja 后端的
30+
is_gcc 门;`Toolchain::envOverrides`(EnvVar 表,注入 ninja 子进程环境,
31+
为 MSVC 后端 INCLUDE/LIB/PATH 预留);gcc provider 增加与 clang 同形的
32+
`std_module_build_commands`(命令序列);`resolve_link_model` 在 Windows
33+
显式返回 PE 空模型。设计:`.agents/docs/2026-07-13-toolchain-backend-
34+
abstraction-msvc-mingw-design.md` + 同日触点审计文档。
35+
636
## [0.0.88] — 2026-07-13
737

838
### 新增

docs/03-toolchains.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,30 @@ Available (run `mcpp toolchain install <compiler> <version>`):
6969

7070
The entry marked with `*` is the current default toolchain. `@mcpp/...` is shorthand for `~/.mcpp/...`, used to keep the output narrower.
7171

72+
## MinGW (Windows-native GCC, no Visual Studio required)
73+
74+
On Windows, `mingw` installs a self-contained MinGW-w64 GCC (winlibs
75+
standalone build, UCRT runtime) into mcpp's sandbox — the same managed-xpkg
76+
model as `gcc`/`llvm`, no Visual Studio needed:
77+
78+
```bash
79+
mcpp toolchain install mingw 16.1.0
80+
mcpp toolchain default mingw@16.1.0
81+
```
82+
83+
It uses the regular GCC module pipeline (`gcm.cache`, `import std` via
84+
libstdc++'s `bits/std.cc`). Produced binaries statically link libstdc++ and
85+
libgcc by default, so they run standalone — no `libstdc++-6.dll` needs to
86+
travel next to your exe (opt out with `[build] static_stdlib = false`).
87+
`[build] linkage = "static"` upgrades that to a fully static link.
88+
89+
In a manifest:
90+
91+
```toml
92+
[toolchain]
93+
windows = "mingw@16.1.0"
94+
```
95+
7296
## MSVC (System Toolchain, Windows)
7397

7498
MSVC is different from every other toolchain mcpp manages: it is a **system

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.88"
3+
version = "0.0.89"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/flags.cppm

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import mcpp.build.plan;
1616
import mcpp.platform;
1717
import mcpp.toolchain.clang;
1818
import mcpp.toolchain.detect;
19+
import mcpp.toolchain.dialect;
1920
import mcpp.toolchain.linkmodel;
2021
import mcpp.toolchain.provider;
2122
import mcpp.toolchain.registry;
@@ -122,10 +123,13 @@ std::string atomic_link_flag(const std::vector<std::filesystem::path>& linkDirs,
122123
CompileFlags compute_flags(const BuildPlan& plan) {
123124
CompileFlags f;
124125

125-
// ProviderCapabilities: centralised query point for per-toolchain decisions.
126-
// Prefer caps.* checks over ad-hoc is_clang()/is_musl_target() calls for
127-
// any new branching added to this function.
126+
// Central query points for per-toolchain decisions — prefer these over
127+
// ad-hoc is_clang()/is_gcc() calls:
128+
// caps — what the toolchain can do (scan-deps, stdlib id, …)
129+
// d — how a flag is SPELT (GNU "-I" vs MSVC "/I")
130+
// traits — BMI mechanics + module-flag spellings
128131
auto caps = mcpp::toolchain::capabilities_for(plan.toolchain);
132+
const auto& d = mcpp::toolchain::dialect_for(plan.toolchain);
129133

130134
// macOS minimum supported OS version for produced binaries.
131135
// Precedence: MACOSX_DEPLOYMENT_TARGET env (explicit per-invocation
@@ -152,7 +156,7 @@ CompileFlags compute_flags(const BuildPlan& plan) {
152156
std::string include_flags;
153157
for (auto& inc : plan.manifest.buildConfig.includeDirs) {
154158
auto abs = inc.is_absolute() ? inc : (plan.projectRoot / inc);
155-
include_flags += " -I" + escape_path(abs);
159+
include_flags += std::format(" {}{}", d.includePrefix, escape_path(abs));
156160
}
157161

158162
// Sysroot / payload paths — resolved ONCE by the toolchain link model
@@ -208,11 +212,11 @@ CompileFlags compute_flags(const BuildPlan& plan) {
208212
f.sysroot = link_toolchain_flags;
209213
}
210214

211-
// Binutils -B flag
215+
// Binutils -B flag — a GCC/libstdc++ payload concern (musl bundles its
216+
// own as/ld; Clang and MSVC never take an external binutils).
212217
bool isMuslTc = mcpp::toolchain::is_musl_target(plan.toolchain);
213-
bool isClang = mcpp::toolchain::is_clang(plan.toolchain);
214218
std::filesystem::path binutilsBin;
215-
if (!isMuslTc && !isClang) {
219+
if (!isMuslTc && caps.stdlib_id == "libstdc++") {
216220
auto ar = mcpp::toolchain::archive_tool(plan.toolchain);
217221
if (!ar.empty())
218222
binutilsBin = ar.parent_path();
@@ -231,8 +235,8 @@ CompileFlags compute_flags(const BuildPlan& plan) {
231235
// unless the profile pins -O0.
232236
auto& prof = plan.manifest.buildConfig;
233237
std::string opt_flag = isMuslTc && prof.optLevel != "0"
234-
? " -Og" : (" -O" + prof.optLevel);
235-
if (prof.debug) opt_flag += " -g";
238+
? " -Og" : std::format(" {}{}", d.optPrefix, prof.optLevel);
239+
if (prof.debug) opt_flag += std::format(" {}", d.debugFlags);
236240
if (prof.lto) opt_flag += " -flto";
237241

238242
// User link flags
@@ -247,20 +251,25 @@ CompileFlags compute_flags(const BuildPlan& plan) {
247251
plan.manifest.buildConfig.cStandard.empty() ? "c11" : plan.manifest.buildConfig.cStandard;
248252

249253
// Assemble
250-
// -fmodules is a GCC-only flag; Clang uses a different module ABI and does
251-
// not need it. caps.stdlib_id distinguishes GCC (libstdc++) from Clang
252-
// (libc++ / msvc-stl) without an extra is_clang() call.
253-
std::string module_flag = (caps.stdlib_id == "libstdc++") ? " -fmodules" : "";
254+
// Module-flag spellings come from BmiTraits: GCC needs -fmodules on every
255+
// TU (BMIs implicit); Clang/MSVC reference the staged std BMI and a BMI
256+
// search dir explicitly (spelled -fmodule-file=/-fprebuilt-module-path vs
257+
// /reference//ifcSearchDir).
258+
auto traits = mcpp::toolchain::bmi_traits(plan.toolchain);
259+
std::string module_flag{traits.compileModulesFlag};
254260
std::string std_module_flag;
255-
if (isClang && !plan.stdBmiPath.empty()) {
256-
std_module_flag = " -fmodule-file=std=" + escape_path(staged_std_bmi_path(plan));
261+
if (!traits.stdBmiUsePrefix.empty() && !plan.stdBmiPath.empty()) {
262+
std_module_flag = std::string(traits.stdBmiUsePrefix)
263+
+ escape_path(staged_std_bmi_path(plan));
257264
}
258265
std::string std_compat_module_flag;
259-
if (isClang && !plan.stdCompatBmiPath.empty()) {
266+
if (!traits.stdCompatBmiUsePrefix.empty() && !plan.stdCompatBmiPath.empty()) {
267+
// NOTE: staging path is Clang's today; registry-dispatch when the
268+
// MSVC backend lands (std.compat.ixx staging).
260269
auto compatDst = mcpp::toolchain::clang::staged_std_compat_bmi_path(plan.outputDir);
261-
std_compat_module_flag = " -fmodule-file=std.compat=" + escape_path(compatDst);
270+
std_compat_module_flag = std::string(traits.stdCompatBmiUsePrefix)
271+
+ escape_path(compatDst);
262272
}
263-
auto traits = mcpp::toolchain::bmi_traits(plan.toolchain);
264273
std::string prebuilt_module_flag;
265274
if (traits.needsPrebuiltModulePath) {
266275
// Absolute path: a bare `pcm.cache` / `gcm.cache` works at ninja
@@ -272,22 +281,32 @@ CompileFlags compute_flags(const BuildPlan& plan) {
272281
// resolution fails with `module 'X' not found`. The other
273282
// `-fmodule-file=` flags in this block are already escape_path'd
274283
// (absolute) for the same reason — this one was a leftover.
275-
prebuilt_module_flag = std::format(" -fprebuilt-module-path={}",
276-
escape_path(plan.outputDir / traits.bmiDir));
284+
prebuilt_module_flag = std::string(traits.bmiSearchPrefix)
285+
+ escape_path(plan.outputDir / traits.bmiDir);
277286
}
278287
std::string cxx_std_flag =
279-
plan.cppStandardFlag.empty() ? std::string("-std=c++23") : plan.cppStandardFlag;
288+
plan.cppStandardFlag.empty()
289+
? std::format("{}c++23", d.stdPrefix) : plan.cppStandardFlag;
280290
f.cxx = std::format("{}{}{}{}{}{}{}{}{}{}", cxx_std_flag, module_flag, std_module_flag,
281291
std_compat_module_flag, prebuilt_module_flag,
282292
opt_flag, pic_flag, compile_toolchain_flags, b_flag, include_flags);
283-
f.cc = std::format("-std={}{}{}{}{}{}", c_std, opt_flag, pic_flag, compile_toolchain_flags,
284-
b_flag, include_flags);
293+
f.cc = std::format("{}{}{}{}{}{}{}", d.stdPrefix, c_std, opt_flag, pic_flag,
294+
compile_toolchain_flags, b_flag, include_flags);
285295

286296
// Link flags
287297
f.staticStdlib = plan.manifest.buildConfig.staticStdlib;
288298
f.linkage = plan.manifest.buildConfig.linkage;
289299
std::string full_static = (mcpp::platform::supports_full_static && f.linkage == "static") ? " -static" : "";
290-
std::string static_stdlib = (f.staticStdlib && !isClang && !mcpp::platform::is_windows) ? " -static-libstdc++" : "";
300+
// Static C++ stdlib: a libstdc++ (GCC/MinGW) concern. On Windows a MinGW
301+
// toolchain also statically links libgcc, so produced binaries don't
302+
// depend on libstdc++-6.dll / libgcc_s DLLs from the toolchain dir
303+
// (portable-by-default, same spirit as the macOS static-libc++ path).
304+
std::string static_stdlib;
305+
if (f.staticStdlib && caps.stdlib_id == "libstdc++") {
306+
static_stdlib = " -static-libstdc++";
307+
if constexpr (mcpp::platform::is_windows)
308+
static_stdlib += " -static-libgcc";
309+
}
291310
std::string runtime_dirs;
292311
if constexpr (mcpp::platform::supports_rpath) {
293312
// Toolchain runtime dirs (glibc/gcc) as before...
@@ -320,7 +339,30 @@ CompileFlags compute_flags(const BuildPlan& plan) {
320339
if (prof.strip) link_extra += " -s";
321340

322341
if constexpr (mcpp::platform::is_windows) {
323-
f.ld = user_ldflags + link_extra;
342+
// PE link: no rpath/loader/payload model. MSVC-ABI Clang needs
343+
// nothing extra (MSVC STL/SDK via the driver); MinGW adds the static
344+
// libstdc++/libgcc pair (static_stdlib above) and -B so its own
345+
// binutils resolve, plus `-static` for full static when requested
346+
// (MinGW supports it, unlike MSVC-ABI links).
347+
std::string mingw_static;
348+
std::string mingw_stdexp;
349+
if (caps.stdlib_id == "libstdc++") {
350+
// `-static` for the whole link — winlibs' own recommendation for
351+
// standalone exes. The piecemeal recipe (-static-libstdc++ +
352+
// -Wl,-Bstatic -lwinpthread) verifiably loses to the driver's
353+
// implicit closing libs: CI import tables still showed
354+
// libwinpthread-1.dll. System DLLs (KERNEL32/UCRT) still resolve
355+
// via their import libs. Tied to staticStdlib so
356+
// [build] static_stdlib=false opts back into DLL-coupled links.
357+
if (f.staticStdlib || f.linkage == "static")
358+
mingw_static = " -static";
359+
// std::print's terminal probe (__open_terminal /
360+
// __write_to_terminal, bits/print.h) lives in libstdc++exp.a on
361+
// Windows targets — plain -lstdc++ leaves them undefined.
362+
mingw_stdexp = " -lstdc++exp";
363+
}
364+
f.ld = std::format("{}{}{}{}{}{}", mingw_static, static_stdlib, b_flag,
365+
user_ldflags, mingw_stdexp, link_extra);
324366
} else if constexpr (mcpp::platform::needs_explicit_libcxx) {
325367
// macOS. Two min-version concerns (see xlings
326368
// .agents/docs/2026-06-05-macos-min-version-support.md):

src/build/ninja_backend.cppm

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import mcpp.build.hermetic;
2525
import mcpp.build.compile_commands;
2626
import mcpp.dyndep;
2727
import mcpp.toolchain.detect;
28+
import mcpp.toolchain.dialect;
29+
import mcpp.toolchain.provider;
2830
import mcpp.toolchain.registry;
2931
import mcpp.xlings;
3032
import mcpp.platform;
@@ -258,10 +260,12 @@ std::string emit_ninja_string(const BuildPlan& plan) {
258260
// dyndep requires P1689 scanning capability:
259261
// GCC: built-in -fdeps-format=p1689r5
260262
// Clang: external clang-scan-deps tool (same P1689 output format)
261-
bool has_scanner = mcpp::toolchain::is_gcc(plan.toolchain)
262-
|| !plan.scanDepsPath.empty();
263+
// (MSVC /scanDependencies is the future third driver — scanner design §3a)
264+
auto caps = mcpp::toolchain::capabilities_for(plan.toolchain);
265+
bool has_scanner = caps.has_builtin_p1689_scan || !plan.scanDepsPath.empty();
263266
bool dyndep = dyndep_mode_enabled() && has_scanner;
264267
auto traits = mcpp::toolchain::bmi_traits(plan.toolchain);
268+
const auto& dial = mcpp::toolchain::dialect_for(plan.toolchain);
265269
std::string out;
266270
auto append = [&](std::string s) { out += std::move(s); };
267271

@@ -331,53 +335,65 @@ std::string emit_ninja_string(const BuildPlan& plan) {
331335
// Runtime library paths for private toolchain executables are scoped onto
332336
// the ninja subprocess instead of being emitted into each visible rule.
333337

338+
// Command spellings come from the toolchain's CommandDialect (gnu vs
339+
// msvc); the rule *structure* is shared across compilers.
334340
std::string module_output_flag = traits.needsExplicitModuleOutput
335-
? " -fmodule-output=$bmi_out" : "";
341+
? std::string(traits.moduleOutputPrefix) + "$bmi_out" : "";
342+
const std::string compile_tail = std::format(
343+
"{} $in {}$out", dial.compileOnly, dial.outputObjPrefix);
336344
append("rule cxx_module\n");
337345
if constexpr (mcpp::platform::is_windows) {
338346
// Windows: skip BMI restat optimization (requires POSIX shell).
339347
append(std::format(" command = "
340-
"$cxx $local_includes $cxxflags $unit_cxxflags{} -c $in -o $out\n", module_output_flag));
348+
"$cxx $local_includes $cxxflags $unit_cxxflags{} {}\n",
349+
module_output_flag, compile_tail));
341350
} else {
342351
append(std::format(" command = "
343352
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out\" ]; then "
344353
"cp -p \"$bmi_out\" \"$bmi_out.bak\"; "
345354
"fi && "
346-
"$cxx $local_includes $cxxflags $unit_cxxflags{} -c $in -o $out && "
355+
"$cxx $local_includes $cxxflags $unit_cxxflags{} {} && "
347356
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out.bak\" ] && "
348357
"cmp -s \"$bmi_out\" \"$bmi_out.bak\"; then "
349358
"mv \"$bmi_out.bak\" \"$bmi_out\"; "
350359
"else "
351360
"rm -f \"$bmi_out.bak\"; "
352-
"fi\n", module_output_flag));
361+
"fi\n", module_output_flag, compile_tail));
353362
}
354363
append(" description = MOD $out\n");
355364
if (dyndep)
356365
append(" restat = 1\n");
357366
append("\n");
358367

359368
append("rule cxx_object\n");
360-
append(" command = $cxx $local_includes $cxxflags $unit_cxxflags -c $in -o $out\n");
369+
append(std::format(
370+
" command = $cxx $local_includes $cxxflags $unit_cxxflags {}\n",
371+
compile_tail));
361372
append(" description = OBJ $out\n");
362373
if (dyndep)
363374
append(" restat = 1\n");
364375
append("\n");
365376

366377
if (need_c_rule) {
367378
append("rule c_object\n");
368-
append(" command = $cc $local_includes $cflags $unit_cflags -c $in -o $out\n");
379+
append(std::format(
380+
" command = $cc $local_includes $cflags $unit_cflags {}\n",
381+
compile_tail));
369382
append(" description = CC $out\n");
370383
if (dyndep)
371384
append(" restat = 1\n");
372385
append("\n");
373386
}
374387

388+
// Link rule: driver-style today (g++/clang++ act as the linker). The
389+
// dialect's LinkStyle::SeparateLinker (link.exe /OUT: + rspfile) is the
390+
// MSVC backend's insertion point — unreachable until that lands.
375391
append("rule cxx_link\n");
376392
append(" command = $cxx $in -o $out $ldflags $unit_ldflags\n");
377393
append(" description = LINK $out\n\n");
378394

379395
append("rule cxx_archive\n");
380-
append(" command = $ar rcs $out $in\n");
396+
append(std::format(" command = {}\n", dial.archiveCmd));
381397
append(" description = AR $out\n\n");
382398

383399
append("rule cxx_shared\n");
@@ -811,6 +827,11 @@ std::expected<BuildResult, BuildError> NinjaBackend::build(const BuildPlan& plan
811827
std::vector<std::pair<std::string, std::string>> nenv;
812828
if (r.runtimeEnvKey != "-" && !r.runtimeEnvValue.empty())
813829
nenv.emplace_back(r.runtimeEnvKey, r.runtimeEnvValue);
830+
// Toolchain-declared env (empty for GCC/Clang; MSVC's INCLUDE/LIB/PATH).
831+
// NOTE: not persisted in the fast-path cache yet — revisit when the MSVC
832+
// backend lands (its fast path must re-derive these from detection).
833+
for (auto& ev : plan.toolchain.envOverrides)
834+
nenv.emplace_back(ev.key, ev.value);
814835

815836
auto cap = mcpp::platform::process::capture_exec(nargv, nenv);
816837
std::string out = cap.output;

0 commit comments

Comments
 (0)