From 98712e2850d866beb5db7e4dbf584a0841f38844 Mon Sep 17 00:00:00 2001 From: julixian Date: Tue, 6 Oct 2026 02:00:49 +0800 Subject: [PATCH 1/4] fix: preserve Windows target architecture in driver links and resources --- src/build/flags.cppm | 3 +- src/build/prepare/windows_resources.cpp | 3 + src/build/resources.cppm | 16 ++++ ...indows_driver_target_and_coff_resources.sh | 80 +++++++++++++++++++ tests/unit/test_build_resources.cpp | 15 ++++ tests/unit/test_ninja_backend.cpp | 18 +++++ 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/889_windows_driver_target_and_coff_resources.sh diff --git a/src/build/flags.cppm b/src/build/flags.cppm index 2e9a8f8a..eb075d92 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -1955,7 +1955,8 @@ CompileFlags compute_flags(const BuildPlan& plan) { // ask the driver for a different CRT than the objects already have. const std::string msvcAbiCrtLd = msvcAbiCrtWord.empty() ? std::string{} : (" " + msvcAbiCrtWord); - f.ld = std::format("{}{} -fuse-ld=lld{}{}{}{}", full_static, + // 链接驱动也必须保留目标架构,否则 clang 会按宿主架构选择 CRT。 + f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}{}", full_static, crossTarget, msvcAbiCrtLd, msvcSysroot, link_intent_ld, user_ldflags, link_extra); f.ldC = f.ld; // the CRT model applies to C TUs on this ABI too diff --git a/src/build/prepare/windows_resources.cpp b/src/build/prepare/windows_resources.cpp index 399cf0d7..ab202249 100644 --- a/src/build/prepare/windows_resources.cpp +++ b/src/build/prepare/windows_resources.cpp @@ -126,6 +126,9 @@ step13_resource_compiler(PrepareState& state, BuildContext& ctx) { } } for (auto& ru : ctx.plan.resourceUnits) { + if (auto target = rsrc::coff_target_flag(*tool, state.tc->targetTriple); + !target.empty()) + ru.flags.push_back(std::move(target)); ru.flags.push_back(msvcStyle ? "/C" : "--codepage=65001"); if (msvcStyle) ru.flags.push_back("65001"); for (auto const& d : ru.includeDirs) ru.flags.push_back(ip + d.string()); diff --git a/src/build/resources.cppm b/src/build/resources.cppm index 4be4c20f..fff48afc 100644 --- a/src/build/resources.cppm +++ b/src/build/resources.cppm @@ -80,6 +80,8 @@ struct RcTool { std::optional find_rc_tool(const mcpp::toolchain::Toolchain& tc, std::string_view dialectId); +std::string coff_target_flag(const RcTool& tool, std::string_view targetTriple); + // Split a Windows environment list (PATH, INCLUDE, LIB) into its entries. // // `;` is the ONLY separator, and that is not a simplification. Every value that @@ -166,6 +168,18 @@ compile_utf8_manifest(const mcpp::toolchain::Toolchain& tc, namespace mcpp::build::resources { +std::string coff_target_flag(const RcTool& tool, std::string_view targetTriple) { + if (tool.style != "gnu") return {}; + auto trip = mcpp::toolchain::triple::parse(targetTriple); + if (!trip) return {}; + // LLVM 接受完整 triple;GNU windres 则使用 BFD 格式名。 + if (tool.name().find("llvm-windres") != std::string::npos) + return "--target=" + trip->llvm_triple(); + if (trip->arch == "i686") return "--target=pe-i386"; + if (trip->arch == "x86_64") return "--target=pe-x86-64"; + return {}; +} + namespace { bool exists_file(const std::filesystem::path& p) { @@ -321,6 +335,8 @@ compile_utf8_manifest(const mcpp::toolchain::Toolchain& tc, // The same spelling as the `rc_object` rule of the ninja backend. std::vector argv = {tool->path.string()}; + if (auto target = coff_target_flag(*tool, tc.targetTriple); !target.empty()) + argv.push_back(std::move(target)); if (msvcStyle) for (auto a : {"/nologo", "/C", "65001", "/fo"}) argv.emplace_back(a); else diff --git a/tests/e2e/889_windows_driver_target_and_coff_resources.sh b/tests/e2e/889_windows_driver_target_and_coff_resources.sh new file mode 100644 index 00000000..11330dd8 --- /dev/null +++ b/tests/e2e/889_windows_driver_target_and_coff_resources.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# requires: msvc +# mcpp#775: the driver link and COFF resource must target the selected machine. +set -e +source "$(dirname "$0")/_host_path.sh" +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT +fail() { echo "FAIL: $1"; shift; for f in "$@"; do cat "$f" 2>/dev/null; done; exit 1; } +MCPP="${MCPP:-mcpp}" +REGISTRY_HOST=$(host_path "${MCPP_HOME:-$HOME/.mcpp}/registry") +export MCPP_HOME="$TMP/home" +mkdir -p "$MCPP_HOME" +printf '[xlings]\nhome = "%s"\n' "$REGISTRY_HOST" > "$MCPP_HOME/config.toml" +cd "$TMP" +hexof() { od -An -v -tx1 "$1" | tr -d ' \n'; } +pe_machine() { + local offset + offset=$(od -An -tu4 -j60 -N4 "$1" | tr -d ' \n') + od -An -tu2 -j"$((offset + 4))" -N2 "$1" | tr -d ' \n' +} +for arch in i686 x86_64; do + machine=332 + [ "$arch" != x86_64 ] || machine=34404 + for case_name in c cxx dll; do + mkdir -p "$arch/$case_name/src" + cd "$arch/$case_name" + kind=bin + suffix=exe + main_line='main = "src/main.cpp"' + if [ "$case_name" = dll ]; then + kind=shared + suffix=dll + main_line='' + printf 'extern "C" __declspec(dllexport) int answer() { return 42; }\n' > src/main.cpp + elif [ "$case_name" = c ]; then + main_line='main = "src/main.c"' + printf 'int main(void) { return 0; }\n' > src/main.c + else + printf 'int main() { return 0; }\n' > src/main.cpp + fi + cat > mcpp.toml < first.log 2>&1 \ + || fail "$arch/$case_name: driver link failed" first.log + image=$(find target -name "probe.$suffix" | head -1) + [ -n "$image" ] || fail "$arch/$case_name: image missing" first.log + [ "$(pe_machine "$image")" = "$machine" ] || fail "wrong PE machine: $image" + [ "$kind" != bin ] || "$image" || fail "executable failed: $image" + printf '\n[resources]\nfiles = ["app.rc"]\n' >> mcpp.toml + printf '101 RCDATA { 0x1357, 0x2468, 0xabcdef01L }\n' > app.rc + "$MCPP" build --target "$arch-windows-msvc" > resource.log 2>&1 \ + || fail "$arch/$case_name: resource link failed" resource.log + object=$(find target -path '*/res/*.o' | head -1) + [ -n "$object" ] || fail "$arch/$case_name: COFF resource missing" resource.log + [ "$(od -An -tu2 -N2 "$object" | tr -d ' \n')" = "$machine" ] \ + || fail "wrong COFF machine: $object" + [[ "$(hexof "$image")" == *5713682401efcdab* ]] || fail "resource data missing" + printf '101 RCDATA { 0x7654, 0x3210, 0xfedcba98L }\n' > app.rc + "$MCPP" build --target "$arch-windows-msvc" > rebuild.log 2>&1 \ + || fail "$arch/$case_name: resource rebuild failed" rebuild.log + [ "$(pe_machine "$image")" = "$machine" ] || fail "rebuild changed PE machine" + [[ "$(hexof "$image")" == *5476103298badcfe* ]] || fail "resource data stayed stale" + cd "$TMP" + done +done +echo "PASS: 889 Windows driver target and COFF resources" diff --git a/tests/unit/test_build_resources.cpp b/tests/unit/test_build_resources.cpp index 312b9f80..9768295d 100644 --- a/tests/unit/test_build_resources.cpp +++ b/tests/unit/test_build_resources.cpp @@ -7,6 +7,21 @@ import mcpp.build.resources; namespace res = mcpp::build::resources; namespace fs = std::filesystem; +TEST(BuildResources, CoffTargetFollowsTheTargetInsteadOfTheHost) { + const res::RcTool llvm{"/selected/bin/llvm-windres.exe", "gnu"}; + EXPECT_EQ(res::coff_target_flag(llvm, "i686-windows-msvc"), + "--target=i686-pc-windows-msvc"); + EXPECT_EQ(res::coff_target_flag(llvm, "x86_64-windows-msvc"), + "--target=x86_64-pc-windows-msvc"); + EXPECT_EQ(res::coff_target_flag(llvm, "aarch64-windows-msvc"), + "--target=aarch64-pc-windows-msvc"); + const res::RcTool gnu{"/selected/bin/windres", "gnu"}; + EXPECT_EQ(res::coff_target_flag(gnu, "i686-windows-gnu"), "--target=pe-i386"); + EXPECT_EQ(res::coff_target_flag(gnu, "x86_64-windows-gnu"), "--target=pe-x86-64"); + EXPECT_TRUE(res::coff_target_flag({"rc.exe", "msvc"}, "i686-windows-msvc").empty()); + EXPECT_TRUE(res::coff_target_flag({"llvm-rc.exe", "msvc"}, "x86_64-windows-msvc").empty()); +} + namespace { mcpp::manifest::Package sample_package() { diff --git a/tests/unit/test_ninja_backend.cpp b/tests/unit/test_ninja_backend.cpp index 6b27d119..8b3e4204 100644 --- a/tests/unit/test_ninja_backend.cpp +++ b/tests/unit/test_ninja_backend.cpp @@ -75,6 +75,24 @@ BuildPlan minimal_plan() { } // namespace +TEST(NinjaBackend, WindowsDriverLinksRetainTheSelectedTargetForCAndCxx) { + for (const auto arch : {"i686", "x86_64", "aarch64"}) { + auto plan = minimal_plan(); + plan.toolchain.compiler = mcpp::toolchain::CompilerId::Clang; + plan.toolchain.binaryPath = "/selected/bin/clang++"; + plan.toolchain.targetTriple = std::string(arch) + "-pc-windows-msvc"; + plan.toolchain.crossTargetFlag = "--target=" + plan.toolchain.targetTriple; + plan.manifest.buildConfig.cxxRuntime = "host-coupled"; + plan.targetSide.compiler.interfaceName = "llvm"; + plan.targetSide.kernelAbi.interfaceName = "windows"; + plan.targetSide.cAbi.interfaceName = "msvc"; + plan.targetSide.cxx.interfaceName = "msvc"; + auto flags = compute_flags(plan); + EXPECT_NE(flags.ld.find(plan.toolchain.crossTargetFlag), std::string::npos); + EXPECT_NE(flags.ldC.find(plan.toolchain.crossTargetFlag), std::string::npos); + } +} + TEST(NinjaBackend, BitcodeExportToolsComeFromTheSelectedLlvmAndAreQuotedAsWords) { auto plan = minimal_plan(); plan.toolchain.compiler = mcpp::toolchain::CompilerId::Clang; From 68701f94a0cf78b81828a1f9f0109605bf238ef1 Mon Sep 17 00:00:00 2001 From: julixian Date: Tue, 6 Oct 2026 02:18:04 +0800 Subject: [PATCH 2/4] test: scope PE driver link regression to Windows hosts --- tests/unit/test_ninja_backend.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/test_ninja_backend.cpp b/tests/unit/test_ninja_backend.cpp index 8b3e4204..6661c10c 100644 --- a/tests/unit/test_ninja_backend.cpp +++ b/tests/unit/test_ninja_backend.cpp @@ -76,6 +76,9 @@ BuildPlan minimal_plan() { } // namespace TEST(NinjaBackend, WindowsDriverLinksRetainTheSelectedTargetForCAndCxx) { + // 本次修复针对 Windows 宿主的 PE 驱动链接分支,其他宿主走不同分支。 + if constexpr (!mcpp::platform::is_windows) + GTEST_SKIP() << "the PE driver link branch is selected on Windows hosts"; for (const auto arch : {"i686", "x86_64", "aarch64"}) { auto plan = minimal_plan(); plan.toolchain.compiler = mcpp::toolchain::CompilerId::Clang; From c25f570b2b0e96b9e37329122d234c6960bac1c5 Mon Sep 17 00:00:00 2001 From: julixian Date: Tue, 6 Oct 2026 02:44:16 +0800 Subject: [PATCH 3/4] docs: use English comments for Windows target propagation --- src/build/flags.cppm | 2 +- src/build/resources.cppm | 3 ++- tests/unit/test_ninja_backend.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/build/flags.cppm b/src/build/flags.cppm index eb075d92..b86eddb4 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -1955,7 +1955,7 @@ CompileFlags compute_flags(const BuildPlan& plan) { // ask the driver for a different CRT than the objects already have. const std::string msvcAbiCrtLd = msvcAbiCrtWord.empty() ? std::string{} : (" " + msvcAbiCrtWord); - // 链接驱动也必须保留目标架构,否则 clang 会按宿主架构选择 CRT。 + // Keep the target on the driver link too, or clang selects the host CRT. f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}{}", full_static, crossTarget, msvcAbiCrtLd, msvcSysroot, link_intent_ld, user_ldflags, link_extra); diff --git a/src/build/resources.cppm b/src/build/resources.cppm index fff48afc..0a257f02 100644 --- a/src/build/resources.cppm +++ b/src/build/resources.cppm @@ -172,7 +172,8 @@ std::string coff_target_flag(const RcTool& tool, std::string_view targetTriple) if (tool.style != "gnu") return {}; auto trip = mcpp::toolchain::triple::parse(targetTriple); if (!trip) return {}; - // LLVM 接受完整 triple;GNU windres 则使用 BFD 格式名。 + // LLVM accepts a full triple; GNU windres takes a BFD format name. + // BFD's pe-i386 denotes 32-bit x86 COFF, including i686 targets. if (tool.name().find("llvm-windres") != std::string::npos) return "--target=" + trip->llvm_triple(); if (trip->arch == "i686") return "--target=pe-i386"; diff --git a/tests/unit/test_ninja_backend.cpp b/tests/unit/test_ninja_backend.cpp index 6661c10c..6fc5e228 100644 --- a/tests/unit/test_ninja_backend.cpp +++ b/tests/unit/test_ninja_backend.cpp @@ -76,7 +76,7 @@ BuildPlan minimal_plan() { } // namespace TEST(NinjaBackend, WindowsDriverLinksRetainTheSelectedTargetForCAndCxx) { - // 本次修复针对 Windows 宿主的 PE 驱动链接分支,其他宿主走不同分支。 + // This regression covers the Windows-host PE driver link branch. if constexpr (!mcpp::platform::is_windows) GTEST_SKIP() << "the PE driver link branch is selected on Windows hosts"; for (const auto arch : {"i686", "x86_64", "aarch64"}) { From 99917e50a3f4482de45e94e1b45957d299b6e2d0 Mon Sep 17 00:00:00 2001 From: julixian Date: Tue, 6 Oct 2026 02:48:39 +0800 Subject: [PATCH 4/4] fix: map all 32-bit x86 spellings to the windres COFF format --- src/build/resources.cppm | 4 +++- tests/unit/test_build_resources.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/build/resources.cppm b/src/build/resources.cppm index 0a257f02..cc2dde15 100644 --- a/src/build/resources.cppm +++ b/src/build/resources.cppm @@ -176,7 +176,9 @@ std::string coff_target_flag(const RcTool& tool, std::string_view targetTriple) // BFD's pe-i386 denotes 32-bit x86 COFF, including i686 targets. if (tool.name().find("llvm-windres") != std::string::npos) return "--target=" + trip->llvm_triple(); - if (trip->arch == "i686") return "--target=pe-i386"; + if (trip->arch == "x86" || trip->arch == "i386" || trip->arch == "i486" + || trip->arch == "i586" || trip->arch == "i686") + return "--target=pe-i386"; if (trip->arch == "x86_64") return "--target=pe-x86-64"; return {}; } diff --git a/tests/unit/test_build_resources.cpp b/tests/unit/test_build_resources.cpp index 9768295d..512b668c 100644 --- a/tests/unit/test_build_resources.cpp +++ b/tests/unit/test_build_resources.cpp @@ -16,7 +16,12 @@ TEST(BuildResources, CoffTargetFollowsTheTargetInsteadOfTheHost) { EXPECT_EQ(res::coff_target_flag(llvm, "aarch64-windows-msvc"), "--target=aarch64-pc-windows-msvc"); const res::RcTool gnu{"/selected/bin/windres", "gnu"}; - EXPECT_EQ(res::coff_target_flag(gnu, "i686-windows-gnu"), "--target=pe-i386"); + for (const auto arch : {"x86", "i386", "i486", "i586", "i686"}) { + EXPECT_EQ(res::coff_target_flag(gnu, std::string(arch) + "-windows-gnu"), + "--target=pe-i386") << arch; + } + EXPECT_EQ(res::coff_target_flag(llvm, "i386-windows-msvc"), + "--target=i386-pc-windows-msvc"); EXPECT_EQ(res::coff_target_flag(gnu, "x86_64-windows-gnu"), "--target=pe-x86-64"); EXPECT_TRUE(res::coff_target_flag({"rc.exe", "msvc"}, "i686-windows-msvc").empty()); EXPECT_TRUE(res::coff_target_flag({"llvm-rc.exe", "msvc"}, "x86_64-windows-msvc").empty());