Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/build/flags.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/build/prepare/windows_resources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
19 changes: 19 additions & 0 deletions src/build/resources.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ struct RcTool {
std::optional<RcTool> 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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<std::string> 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
Expand Down
80 changes: 80 additions & 0 deletions tests/e2e/889_windows_driver_target_and_coff_resources.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF
[package]
name = "probe"
version = "0.1.0"
[toolchain]
windows = "llvm@20.1.7"
[build]
cxx_runtime = "host-coupled"
[target.$arch-windows-msvc]
sysroot = "msvc@system"
[targets.probe]
kind = "$kind"
$main_line
windows_auto_export = false
EOF
# The first build isolates the driver regression from the resource one.
"$MCPP" build --target "$arch-windows-msvc" > 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"
20 changes: 20 additions & 0 deletions tests/unit/test_build_resources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/test_ninja_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading