diff --git a/src/build/flags.cppm b/src/build/flags.cppm index 2e9a8f8a..b86eddb4 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, + // 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); 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..cc2dde15 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,21 @@ 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 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 == "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 {}; +} + namespace { bool exists_file(const std::filesystem::path& p) { @@ -321,6 +338,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..512b668c 100644 --- a/tests/unit/test_build_resources.cpp +++ b/tests/unit/test_build_resources.cpp @@ -7,6 +7,26 @@ 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"}; + 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()); +} + namespace { mcpp::manifest::Package sample_package() { diff --git a/tests/unit/test_ninja_backend.cpp b/tests/unit/test_ninja_backend.cpp index 6b27d119..6fc5e228 100644 --- a/tests/unit/test_ninja_backend.cpp +++ b/tests/unit/test_ninja_backend.cpp @@ -75,6 +75,27 @@ BuildPlan minimal_plan() { } // namespace +TEST(NinjaBackend, WindowsDriverLinksRetainTheSelectedTargetForCAndCxx) { + // 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"}) { + 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;