From f015de22888cf9f11949b7f02d48994978eadde1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez?= Date: Wed, 9 Jul 2025 18:31:02 +0200 Subject: [PATCH] Set `windres` target explicitly to better support LLVM cross-compilation [As stated on its man page](https://man7.org/linux/man-pages/man1/windres.1.html), `windres` uses a default binutils`BFD format when the `--target` option is not explicitly set. This usually works fine with the binutils `windres` implementation even in cross-compilation scenarios, as these have to be set up with a toolchain that's already wired to produce objects for the desired target architecture. However, the LLVM toolchain ships with a `windres` implementation that has a significant behavioral difference in this regard: LLVM is a native cross-platform compiler, which means that no separate toolchains are needed to target a different platform. As such, [LLVM's `windres` defaults to a target that matches the host platform](https://github.com/llvm/llvm-project/blob/1431f8f76fa2270cedc88efdebcc236bf374b144/llvm/tools/llvm-rc/llvm-rc.cpp#L423-L436), producing artifacts that target the wrong platform when cross-compiling. To fix this situation, let's explicitly pass a target to `windres` that matches the desired target architecture (all Windows targets can be assumed to generate PE files), which helps ensuring that no different defaults cause miscompilations. I've verified this change to make cross-compilation from an AArch64 MacOS host to the `x86_64-pc-windows-gnullvm` target possible, without breaking cross-compilation to `x86_64-pc-windows-gnu` via the more classical binutils-based (non-LLVM) MinGW toolchain. --- lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib.rs b/lib.rs index 442b6a4..bbdbfd1 100644 --- a/lib.rs +++ b/lib.rs @@ -623,11 +623,24 @@ impl WindowsResource { } fn compile_with_toolkit_gnu(&self, input: &str, output_dir: &str) -> io::Result<()> { + let bfd_target = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { + "x86_64" => &["--target", "pe-x86-64"][..], + "x86" => &["--target", "pe-i386"], + // The case below would be correct for AArch64, but LLVM's windres does not handle + // the conversion from this BFD target to its LLVM target, treating it as a native + // LLVM target instead, which causes an error. Obviously, passing a LLVM target + // is not portable to the binutils windres implementation. So, to prevent breaking + // native AArch64 compilation with the LLVM toolchain, let's fall back to the default + // target, which would provide the highest success rate + //"aarch64" => &["--target", "pe-aarch64-little"], + _ => &[], // A quite strange arch - use the default windres target and hope for the best + }; let output = PathBuf::from(output_dir).join("resource.o"); let input = PathBuf::from(input); let status = process::Command::new(&self.windres_path) .current_dir(&self.toolkit_path) .arg(format!("-I{}", env::var("CARGO_MANIFEST_DIR").unwrap())) + .args(bfd_target) .arg(format!("{}", input.display())) .arg(format!("{}", output.display())) .status()?;