Issue adding a Rust static library to a C++ project using CMake

Hello! This is my first post here, so please bear with me if anything's out of line, and I'd appreciate any advice.

I'm currently working on trying to integrate a Rust library into a C++ project using the CMake system. Everything seems to work quite well when I'm building this project s.t. that library is compiled and linked as a dynamic library .so. However, when I try to switch from using a dynamic library to a static one .a, I end up being unable to compile it, due to what looks like some sort of issue using the println!() macro. I'm interested in eventually being able cross-compile and move the resulting binaries to a different machine, so I think that compiling with a static lib would be the best way to go.

I've put together a GitHub repository with my code and a README explaining the issue in depth at the following link (and would be happy to post the text of the README here if that would be helpful). Any help would be very much appreciated!

https://github.com/quietlychris/cmake_including_rust

1 Like

One of the important differences between static and dynamic libraries, on Unix, is that static libraries cannot communicate their dependencies to the linker. All the linker can see is a list of the symbols that they need. (Dynamic/shared libraries, on the other hand, can specify dependencies.)

Your static library has some dependencies. From your error text, looks like at least dl and pthread, both used by the Rust standard library. When using your static library, your user needs to also include -ldl -lpthread after your static library on the linker command line.

IIRC you should be able to ask Cargo for the list of required libraries for your target, with:

$ cargo rustc -- --print native-static-libs
1 Like

Shameless plug:
@cbiffle is exactly right - you need to be careful about specifying the right native dependencies when linking rust libs statically. The good news is there's an easier way to do it!

I've been working on a project called cmake-cargo for easily integrating a cargo project into CMake, automatically exposing targets for all executables, static libs, and shared libs that the imported Cargo Workspace contains. In fact, I used the project all last week in a hackathon, running into essentially zero issues with it, allowing me to hack pretty efficiently on my project, never having to deal with integration-related build issues. I actually also had your exact scenario of using "cbindgen" to generate the header files from my Rust lib.

I went ahead and forked your project here and modified it to use cmake-cargo. I went through and commented up the CMakeLists.txt file explaining exactly how to integrate. I was able to build successfully with both the shared and static lib on both Windows and Ubuntu (using WSL, but shouldn't be any different on real Ubuntu). Please, go ahead and clone my fork (remember to use git clone ... --recursive to get the cmake-cargo submodule), and give it a try and let me know if it works for you!

There are almost certainly still some sharp corners in cmake-cargo - I'm still hacking on it and wasn't quite ready to share it publicly, but this post was such a perfect prompt, I couldn't help but share! So, if you can kick the tires, please file any issues you run into.

One thing to keep in mind is that I currently require version 3.10 of CMake. It's possible that it works prior to version 3.10, but that's the minimum I've tested with (Ubuntu 18.04 has cmake v3.10 in its package manager).

4 Likes

That seems to have fixed it, thank you! That command does indeed display the dependencies. Previously, I had two separate uses of cmake's TARGET_LINK_LIBRARIES, with the one specifying dl and pthread before (as you pointed out) the one specifying the one that had the static libraries. I consolidated those two statements, and made sure that both dl and pthread came after the .a libraries in the order of the argument list, and it now compiles (resulting binary sizes also match up with the expected relative sizes for using static vs. dynamic libs). I've updated the text of the CMakeLists.txt file to reflect this, and will be updating the README in the GitHub repo to display this accordingly.

Thanks again for you help, it's really appreciated!

1 Like

That's awesome, thanks for putting that it out there! I'm definitely excited to see tools like it get made available, since I think that minimizing the friction with integrating Rust into existing build systems is a big factor in making it possible for more people to use it (especially in cases where people might want to do incremental component replacements). As an aside, while I'd like to claim credit for thinking up the cbindgen integration, but I really pulled that from a similar project here.

I cloned the forked repository, and it looks like everything worked right out of the gate! The only "issue" that I ran into was that the cargo and bindings folders were written into the base-level directory, rather than into the rust_hello library folder, which I think might make it a little bit messy from a user's perspective, especially if someone was concerned about keeping their components strictly separated by language. If you'd like, I'm happy to file that directly on the GH repository. Aside from that, it was great-- I was able to create a new function in lib.rs, call it in src/main.cpp, then run cmake . and make again, and everything worked as expected. I'm definitely interested in finding a place for it in the build system I'm working with at some point. Thanks!

1 Like

Ah, I typically don't do in-source builds (i.e., I usually do mkdir build && cd build && cmake ..), so I didn't give too much thought to the folder it was put in. You can just modify the paths used in the add_custom_command, add_custom_target, and target_include_directories to fix where the cbindgen outputs are put. My project doesn't actually know about cbindgen stuff, only cargo build integration. cbindgen is kind of "outside the scope" of cmake-cargo for now.

Having a single directory for the cargo build is intentional at this point, since the idea is you would want to have a single build directory for all crates, thereby allowing the crates to share dependency builds. "cargo" may be an overly general name for the cargo build directory, though. I may have to consider changing it.

Thank you for testing it out! I'm glad it worked right away. If you use it any more in the future, please don't hesitate to file any issues you run into or ask me any questions you have.

Makes sense! As you say, I think it's one of those things that comes down to personal preference, and isn't too hard to customize to fit in any case. I do like keeping a single build directory for the crate dependencies, but I could see how the cargo naming convention could be a little confusing. In any case, happy to help, and I definitely appreciate having the option to use a tool like this!