So we have a cucumber test crate that tests an FFI crate. We include the FFI library in the cargo.toml file with :
ffi_lib = { path = "../base/ffi_lib" }
Then its linked into the code via:
#[link(name = "ffi_lib")]
#[allow(dead_code)]
extern "C" {
pub fn create_vector(tag: TTypeTag) -> *mut TVector;
Now this code works on Rust nightly-2024-08-03
and earlier, but as soon as you increase the version to nightly-2024-08-04
or later it breaks with:
error: linking with `cc` failed: exit status: 1
note: "cc" "/var/folders/47/s7....{}
= note: some arguments are omitted. use `--verbose` to show all linker arguments
= note: ld: warning: ignoring duplicate libraries: '-liconv'
ld: library 'ffi_lib' not found
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I am struggling to find what rustc versions are used by these nightly version or docs about what could have changed. For what its worth this was done on a Mac M1, but fails on Unix CI as well with the same issue, so I doubt its a OSX only issue.
I can also see the ffi_lib
library is built, and all its files are in the target folder.
Edit:
Running cargo +nightly-2025-05-01 test --all-features --release -v
I can see that rustc is adding in the -lffi_lib
Edit2: project structure looks as follows:
root
+---base
+---ffi_lib
+---cargo.toml
+---integration_tests
+---cargo.toml
The test are in integration_tests
The cargo.toml
of ffi_lib contains this:
[lib]
crate-type = ["staticlib", "cdylib"]
You are relying on incidental linking behavior that was not guaranteed. Before Rust 1.82, Cargo provided /target/.../deps
as a library search path by default, but a change in 1.82 removed that.
From this issue from someone with a similar problem afterwards:
I don't think this was supposed to work out of the box. Either you should make my_lib a cdylib (requiring you to build the cdylib from within the build script rather than specifying it as dependency, at least until artifact dependencies are stable) and link it like a C library (which includes adding it to the linker search path from a build script) or you should link it like a regular rust library and avoid
#[link(name = "my_lib.dll")] extern "C" {}
.
So you should define a build script with the search path as you would with any other C library.
On that same note, adding ffi_lib = { path = "../base/ffi_lib" }
to your Cargo.toml dependencies is similarly relying on behavior that is not guaranteed. You are using it to ensure libffi_lib.so
is built which incidentally ends up working because the Rust compiler will produce all crate types, but Cargo dependencies are for Rust libraries not C/system libraries. I'm frankly surprised it is accepted at all without an "rlib"
crate type and it seems if you try this same trick with a modern compiler, you'd see this warning:
warning: The package `ffi_lib` provides no linkable target. The compiler might raise an error while compiling `integration_tests`. Consider adding 'dylib' or 'rlib' to key `crate-type` in `ffi_lib`'s Cargo.toml. This warning might turn into a hard error in the future.
Just adding "rlib"
to the list of crate-types wouldn't be a complete solution because with the #[link]
attribute you'd essentially be compiling and linking ffi_lib
into your crate twice, once as a C library and once as a Rust library. Sounds like a recipe for confusion.
So from a combination of a few factors your code worked, but was never a supported workflow. Remove the Cargo dependency and add a build script that locates your library.