cc-rs
cc-rs copied to clipboard
Tell MSVC to link with debug CRT on debug builds
When doing a debug build, emits /MTd
or /MDd
instead of /MT
or /MD
, matching the usual flags for C++ code. When linking statically, link.exe
requires all objects to be built with the same /M*
flags, so hopefully this makes it easier to consume Rust static libraries while debugging.
I suspect that this might not be the way this change should be handled, and furthermore, it smells a little bit like it might be a breaking change when doing debug builds, but it's a start.
Background: I'm building a Rust staticlib
targeting x86_64-pc-windows-msvc
, then linking it against some C++ to make a DLL. To do this I'm using the cxx crate, but my understanding is that this problem would be triggered when consuming any crate containing C++ objects as cxx does.
Everything is fine when doing a release build, but on debug builds, my C++ code gets built using the /MDd
flag for debugging purposes. However, the cc crate unconditionally builds objects using /MD
. As per the Microsoft documentation, this situation is not allowed. Helpfully, the 2019 MSVC toolchain throws up an error, instead of just letting it pass silently, like it used to.
I do have some concerns about my approach here, but I can't really go about addressing them without input from the maintainers:
- My change doesn't provide a way to manually select the debug or non-debug versions without changing debug mode for all of the code. This might not be a problem depending on the API philosophy.
- Depending on how they've worked around it (e.g. doing their debug builds with
/MD
instead of/MDd
), any existing users with the same use-case as mine might have their debug builds broken by this change.
Thanks for this! One thing about this is that I'm not sure how well /MDd
interacts with Rust code. I'm not sure if Rust object files are compiled with this similar directive so they can all intermingle? I know that /MT
vs /MD
works, but I'm not sure if Rust code compiled today can be linked against /MTd
. Mind checking that to see if it works?
Okay, wow, this definitely breaks things. I'm a little embarrassed I didn't think to check letting rustc call the linker. Thanks for the quick response.
Building a staticlib
with the C++ bits compiled with /MDd
, then linking it into a DLL also built with /MDd
seems to work. It happily brings up a tokio RPC server and responds to requests, which I'd guess exercises things pretty well.
Building a cdylib
with the C++ bits compiled with /MDd
fails because rustc wants to link the library with /MD
, as you noted in a comment on an old issue I dug up.
Now that I have a better grasp of what's going on, I'm a little surprised that the staticlib
part works at all. Do Rust object files care which library they're linked with in the end? Did I just get really lucky and not manage to hit anything important?
AFAIK Rust doesn't do anything to indicate /MT
or /MD
in object files it produces. I think it has everything to do with what libraries are linked in the final command line. I don't know what cl.exe
or clang-cl.exe
do to have object files indicate what flag they were compiled with.
As for why things sometimes work and sometimes don't, I'm not sure I'm enough of an MSVC expert to know why :(
/MT
, /MTd
, etc are C/C++ compiler directives (not linker ones) and so, as Alex says, it won't affect compiling Rust code.
Rust itself manually links to the relevant libraries in libc-rs and the way this is done means it's not overrideable outside of the libc crate. So currently any fixes would need to be made there too.
Ideally, instead of hardcoding the libraries on MSVC, Rust would use a linker argument such as:
/DEFAULITLIB:msvcrt
By using the DEFAULITLIB
directive, the library can be overridden by other tools (e.g. cc
) using NODEFAULTLIB
. Or even non-Rust tools. For example, to replace msvcrt
with the debug version, you'd be able to do this:
/NODEFAULTLIB:msvcrt /DEFAULTLIB:msvcrtd
However, I don't think using /DEFAULTLIB
is currently easy to do within a crate.
Another possibility would be to use the Rust specific -l
directive to replace the library:
-l msvcrt:msvcrtd
However, this does not currently work across crates so you can't override anything set by dependencies.
To be honest my hacky solution has been to create an empty msvcrt.lib
and add the folder it's in to the library path. This then means I can use other libraries without conflict. Obviously this is very much a hack and I'm not proposing it as any kind of good solution.
Further reading: C runtime (CRT) and C++ Standard Library (STL) .lib files
Ok, I think this PR as-is should be closed, though I'd be happy to reopen if there's something I'm missing.
I think this needs some more design work (and maybe some changes on the rustc side of things). Mixing CRT libraries is risky so there should be some way to make sure they're all the same for everything that goes into the same linked binary. But figuring out how to do that perhaps needs more than these changes to cc-rs can provide.