How to debug `cargo test --doc` failing to link a doc test

cargo test --doc fails to link a test and I don't understand why. Versions 1.80.1, 1.81.0, +beta all fail.

I have a project with a package. The package has a build.rs that downloads libone.a and libtwo.a. libone.a depends on libtwo.a. The package has a module with wrapper functions for the functions in libone.a.

The rust unittests exercising the C API run fine:

$ cargo test -r -p packageA  moduleA::test::filter_ --lib

I add a doctest which simply calls one of the wrapper functions and it can't be linked:

$ cargo test -r -p packageA --doc
...
          /usr/bin/ld: /home/me/project/target/release/build/packageA-xxxx/out/libone.a(entrypoint.cc.o): in function `functionA':
          /home/me/full-original-path-to/entrypoint.cc:178: undefined reference to `some::namespace::SomeClass::someFunction()'

Strangely, the objects from libone.a are found, but not the ones from libtwo.a even though the cc linking command is instructed to use both: -lone -ltwo "-L" "/home/me/project/target/release/build/packageA-xxxx/out".

I'm trying to debug the cc command but when I run it manually, I get this error:

/usr/bin/ld: cannot open output file /tmp/rustdoctesttpJF2c/rust_out: No such file or directory

How exactly can I run cargo test -r -p packageA --doc such that it does not remove the directory it creates in /tmp? I've seen some mentions of -Csave-temps but I can't figure out how to use it. Where do I put it? :slight_smile:

Any idea why this might fail?

If I use cargo +nightly as opposed to cargo 1.80.1, the same doctest can be linked and passes! I tried cargo clean but I still get the same broken behavior with 1.80.1. cargo +beta also fails.

Using Build Scripts - The Cargo Book in build.rs or linking like this makes no difference:

    #[link(name = "one")]
    #[link(name = "two")]
    extern "C" { ... }

I don't have any advice about linking problems, but since you say "the rust unittests exercising the C API run fine" — how about trying integration tests, i.e. tests defined in project/tests/some_tests.rs? Like doctests and unlike unit tests, these are compiled as separate crates, but they have proper names and aren't temporary like doctests, so nothing gets sent to /tmp.

Good to know about integration tests. I added one, it builds and runs fine.

(I change the name of the test (so it's rebuilt) then run cargo test -r -p packageA -v -v, but I can't see what command is used to build it (only the command used to run it). How can I see what command is used to build it?)

Doc tests are built and run by rustdoc, so you will need to convince rustdoc to print what it’s doing, not cargo.

Unfortunately, I don't see any options in rustdoc --help that look relevant; -v didn't work, and in fact cargo rustdoc -p mypackage -- --test fails to compile the test because it can't find the base library.

I replaced cc with a wrapper to store the files from /tmp in a different place, before they are deleted, to be able to easily re-run and debug the failing cc command:

% cat /usr/bin/cc
#!/bin/bash

T=$(mktemp -d /tmp/dddXXXXX)
for i in $(echo "$@" | tr " " "\n" | grep "^/tmp/")
do
  mkdir -p $T/$(dirname $i)
  test -f $i && cp $i $T/$(dirname $i)
done

for i in $(env); do echo "export $i"; done >> $T/x
echo "/usr/bin/ccc $@" | sed "s=/tmp/=$T/tmp/=g" >> $T/x

cat $T/x | tr " " "\n" > $T/xx

echo $T > /tmp/ddd

/usr/bin/ccc "$@"

Diffing the good vs the bad xx files revealed nightly, passes -fuse-ld=lld to cc, which makes it succeed, whereas stable and beta do not enforce any linker. If I add the flag to the "bad" cc command, it works as it should. So ld fails, lld succeeds.

I spotted no significant flags differing between the rustdoc invocations when using 1.81.0 vs nightly.

Why is nightly rustdoc enforcing lld?

$ RUST_FLAGS="-Clink-arg=-fuse-ld=lld" cargo test --doc --release -p packageA

.. rebuilds everything but shows the same /usr/bin/ld failure to find the objects.

$ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=ld.lld cargo test --doc --release -p packageA
[...]
  = note: ld.lld: error: unknown argument '-fuse-ld=/usr/local/bin/mold'
          ld.lld: error: unable to find library -lgcc_s
          ld.lld: error: unable to find library -lutil
          ld.lld: error: unable to find library -lrt
          ld.lld: error: unable to find library -lpthread
          ld.lld: error: unable to find library -lm
          ld.lld: error: unable to find library -ldl
          ld.lld: error: unable to find library -lc

Given we're actually using mold in this project:

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=/usr/local/bin/mold", "-Ctarget-cpu=x86-64-v3"]

.. I tried with mold:

[...]
  = note: mold: fatal: unknown -m argument: 64

Luckily a colleague found the relevant github issue: rustdoc ignores `cargo` and linker settings when compiling doc tests · Issue #125657 · rust-lang/rust · GitHub. As suggested, the fix is forcing a good linker through rustdocflags.

In case of using clang+mold:

# https://doc.rust-lang.org/cargo/appendix/glossary.html#target
[target.x86_64-unknown-linux-gnu]
# Set mold as linker (and use it as if using clang).
# https://github.com/rui314/mold#how-to-use
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=/usr/local/bin/mold", "-Ctarget-cpu=x86-64-v3"]

[build]
# Set mold as linker also for rustdoc since the standard `ld` linker fails.
# https://users.rust-lang.org/t/117969
#
# Rustdoc is not able to use the general linker settings.
# https://github.com/rust-lang/rust/issues/125657
#
# https://doc.rust-lang.org/rustdoc/command-line-arguments.html
rustdocflags = "-Clinker=clang -Clink-arg=-fuse-ld=/usr/local/bin/mold -Ctarget-cpu=x86-64-v3"

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.