cc-rs
cc-rs copied to clipboard
Cannot cross-compile .a file from linux to windows
Given the following Cargo.toml
:
[package]
name = "cc-example"
version = "0.1.0"
[build-dependencies]
cc = "1"
build.rs
:
extern crate cc;
fn main() {
cc::Build::new()
.file("example.c")
.compile("example.a");
}
and example.c
:
int f() {
return 0;
}
the cc
crate will incorrectly try to use the Windows toolchain instead of the host toolchain.
Full output from cargo build
$ cargo build --target x86_64-pc-windows-msvc
Compiling cc-example v0.1.0 (/home/joshua/src/rust/cc-example)
error: failed to run custom build command for `cc-example v0.1.0 (/home/joshua/src/rust/cc-example)`
Caused by:
process didn't exit successfully: `/home/joshua/.local/lib/cargo/target/debug/build/cc-example-88984b21b33634e3/build-script-build` (exit code: 1)
--- stdout
TARGET = Some("x86_64-pc-windows-msvc")
OPT_LEVEL = Some("0")
HOST = Some("x86_64-unknown-linux-gnu")
CC_x86_64-pc-windows-msvc = None
CC_x86_64_pc_windows_msvc = None
TARGET_CC = None
CC = None
CROSS_COMPILE = None
CFLAGS_x86_64-pc-windows-msvc = None
CFLAGS_x86_64_pc_windows_msvc = None
TARGET_CFLAGS = None
CFLAGS = None
CRATE_CC_NO_DEFAULTS = None
DEBUG = Some("true")
CARGO_CFG_TARGET_FEATURE = Some("fxsr,sse,sse2")
running: "cc" "-O0" "-ffunction-sections" "-fdata-sections" "-g" "-fno-omit-frame-pointer" "-m64" "-Wall" "-Wextra" "-Fo/home/joshua/.local/lib/cargo/target/x86_64-pc-windows-msvc/debug/build/cc-example-183c547311261870/out/example.o" "-c" "example.c"
exit code: 0
AR_x86_64-pc-windows-msvc = None
AR_x86_64_pc_windows_msvc = None
TARGET_AR = None
AR = None
running: "lib.exe" "-out:/home/joshua/.local/lib/cargo/target/x86_64-pc-windows-msvc/debug/build/cc-example-183c547311261870/out/libexample.a.a" "-nologo" "/home/joshua/.local/lib/cargo/target/x86_64-pc-windows-msvc/debug/build/cc-example-183c547311261870/out/example.o"
--- stderr
error occurred: Failed to find tool. Is `lib.exe` installed?
Note that it correctly detects the host and target platforms, but still tries to use lib.exe
instead of ld
.
Thanks for the report, but you'll need to configure the toolchain if it's non-standard, such as in this case. Otherwise you'll need to arrange to have an appropriate toolchain installed.
you'll need to configure the toolchain if it's non-standard
Could you give an example of what you mean? The crate docs say
This crate will automatically detect situations such as cross compilation or other environment variables set by Cargo and will build code appropriately.
So I was under the impression things like this would be detected automatically. I don't see anything on Build
that would help except https://docs.rs/cc/1.0.56/cc/struct.Build.html#method.target, which says it's set automatically from TARGET
.
This crate performs a best effort, but it can't figure out everything under the sun. More common than not the toolchain doesn't even exist on the system, but unfortunately this crate can't really figure that out.
If anyone wants to work on this then PRs would be very welcome!
The problem description is misleading. Would cargo build --target=x86_64-pc-windows-msvc
work on Linux with pure Rust code? Without involving cc-rs that is. No(*). Is it going to be fixed? ... Does it even make sense to do it? Either way, even if it does, cc-rs is not the right spot to start. Because as it stands now, no matter which hoops you jump through, it's going to fail even harder later on. If one wants to cross-compile on Linux for Windows, the answer is cargo build --target=x86_64-pc-windows-gnu
[with mingw-w64, or equivalent, installed separately]. It's working and even supported.
(*) It might be possible to install and run MSVC compiler under wine, but then it would be more appropriate to run matching Rust installation there too.
I don't mind if this doesn't work honestly, I just wish the error message were more helpful, rather than looking like a bug in the cc crate.
It's definitely possible to cross compile Rust to msvc targets using clang-cl
. The only thing missing is the libraries which would need to be acquired and added. In fairness this is also true on Windows but in that case, once installed, cc-rs can autodetect them.
As far as cc-rs is concerned, cross compiling could be made to work in theory, assuming that clang-cl
is in the environment (or a versioned equivalent such as clang-cl-14
). And the logic in cc-rs would need to be updated to work with or without .exe
extensions (depending on the host, e.g. llvm-lib
vs. llvm-lib.exe
).
I think an immediate fix here would be, as jyn suggests, to provide a more helpful error when using a GNU tool for msvc targets because this is incompatible. This would still allow manual overrides (e.g. by setting TARGET_CC
et al).
Attempting to automatically find and compile with clang-cl (or even clang proper, but that might be more work) could also be useful but is much more involved and may still requires some manual set up by the user. A more limited approach would be to correctly use llvm tools if clang-cl
is set (aka Tool::MSVC { clang: true }
) so that it's not necessary to e.g. set llvm-lib
manually.
In any case, better diagnostics are good even if we try something more involved later.
It's definitely possible to cross compile Rust to msvc targets using
clang-cl
.
Well, while Linux clang-cl is indeed capable of generating PE-COFF .obj files, you lack headers, libraries and link.exe(*). In other words, you still can't do the actual useful stuff, like compiling arbitrary C(**) or link an executable binary. Be it just Rust or a mixture of [again, "pure"!] C and Rust.
(*) No, clang-cl does not do the linking, but relies on MSVC link.exe being available. Just like rustc. (**) You're limited to so to say "pure" C, which effectively means "no references to CRT."
llvm-link
can be used instead of link.exe
.
llvm-link
can be used instead oflink.exe
.
llvm-link is a bitcode linker. It won't emit PE-COFF (or any other) executable.
Sorry, I mistyped that. lld-link is what I meant.
Oh! And so you mean that you'd be able to actually replace MSVC linker with it? Why doesn't clang-cl call it? BTW, Rust comes with lld-link. As far as I understand it's used with no-std targets, like embedded ones. Would Rust's lld-link up to the job?
Rust's lld-link
should indeed work. It's just a front-end to rust-lld
which in turn is basically just LLVM's lld
renamed. On Windows I have compiled code without msvc tools (though with the necessary libs). E.g.:
rustc -C linker="lld-link" main.rs
or
rustc -C linker="rust-lld" main.rs
Wow! Well, it's still only one out of three missing components on Linux, headers, libraries, ~link.exe~. Yet I'd still argue that it makes less sense to make it, the cross-compilation in question, work. Because by doing so you would take up a maintenance burden of walking each potential user through an error-prone manual procedure.
Hmm, #758 could have picked it up with $CC
set to clang-cl
...
Hm, adjusting that code to search for llvm-lib
on non-windows hosts (instead of llvm-lib.exe
) should work, I think. Or better yet, use std::env::consts::EXE_SUFFIX
so it's always the right extension for whatever the host is.
The which()
function does use EXE_SUFFIX, but omitting .exe on the line 2678 wouldn't be sufficient, because customarily there is no llvm-lib on Linux $PATH
, but llvm-lib-N. One can ~for~ go for "another possibility" mentioned in the commentary. I.e. instead of replacing clang-cl with llvm-lib, one would replace it with clang, call it with --print-search-dirs and find llvm-lib there...
My Ubuntu 22.04 install does have llvm-lib
in both /usr/lib/llvm-14/bin
and /usr/bin
so it would work for at least some distros (llvm-lib-14
is only in /usr/bin
though):
/usr/bin$ ls llvm-lib*
llvm-lib llvm-lib-14 llvm-libtool-darwin-14
Granted though, a more thorough search would be nice.
Well, I for one don't have llvm-lib on the PATH. So one probably shouldn't count on it. But either way, the question about #758 not picking up llvm-lib was rather "academic." As it won't make the cross-compilation scenario in question maintainable. If one wants to cross-compile for Windows, mingw is the answer.