Handling binary dependencies when building cargo projects

Most cargo projects just assume some libraries to be installed in the global environment and do not declare the binary dependencies they require. What is the correct way of dealing with this issue?

For example, I’m trying to build smithay on nixos. It requires some x11 and related libraries. I’m in a nix shell with these buildInputs:

  buildInputs = [
    binutils
    wayland
    wayland-protocols
    libudev
    libxkbcommon
    libinput
    libGL
    mesa

The cargo build -p anvil succeeds but an attempt to run the binary results in an error saying libX11 not found. I exit the current shell, add libX11 to the list, start a new shell and get the same error. The error disappears if I delete the build directory and rebuild the project, but I get another error saying libXcursor not found. Some dependency of this project requires these libraries but it can’t be static since in that case cargo build would have failed.

libX11 is not in the ldd output of resulting binary.

The current approach seems to be to figure out the exact dependencies by trial and error and commit the package to nixpkgs for everyone to use. Can this be automated in some way?

most likely means that they doing dlopen to try and access the library. You would want to do:

 let
  rpath = lib.makeLibraryPath buildInputs;
in
rustPlatform.buildRustPackage rec {
  ....

  postFixup = ''
    patchelf --set-rpath ${rpath} $out/bin/smithay
  '';

this will edit the executable’s runpath to include the directories you want it to traverse, here’s dotnet:

$ object -x $(readlink -f $(which dotnet))
...
RUNPATH              /nix/store/ikp2dbb5cwvr41yvx5anq8cf8ddifz12-gcc-9.2.0-lib/lib:/nix/store/na7p3x2lccy1zcbqhy17lxxcbym0zld2-libunwind-1.3.1/lib:/nix/store/ba71naa4575dwmimqqqnyiwfnv5wdxz2-util-linux-2.33.2/lib:/nix/store/k4w1vm7hrn0bpjgjna7sxzp4kf5g6a98-icu4c-64.2/lib:/nix/store/i8l8gcnz3jya1dr4205gbb92akay3f1f-openssl-1.1.1d/lib:/nix/store/9fg34dq7x05ydjslihy1hdjx2h6dv0am-zlib-1.2.11/lib:/nix/store/al7l5yx5j8zllg8zjs7nrn27mcybxf6y-curl-7.68.0/lib
...

from man ld

           The linker uses the following search paths to locate required shared libraries:

           1.  Any directories specified by -rpath-link options.

           2.  Any directories specified by -rpath options.  The difference between -rpath and -rpath-link is that directories specified by -rpath options are included in the executable and used at runtime,
               whereas the -rpath-link option is only effective at link time. Searching -rpath in this way is only supported by native linkers and cross linkers which have been configured with the
               --with-sysroot option.

           3.  On an ELF system, for native linkers, if the -rpath and -rpath-link options were not used, search the contents of the environment variable "LD_RUN_PATH".

           4.  On SunOS, if the -rpath option was not used, search any directories specified using -L options.

           5.  For a native linker, search the contents of the environment variable "LD_LIBRARY_PATH".

           6.  For a native ELF linker, the directories in "DT_RUNPATH" or "DT_RPATH" of a shared library are searched for shared libraries needed by it. The "DT_RPATH" entries are ignored if "DT_RUNPATH"
               entries exist.

           7.  The default directories, normally /lib and /usr/lib.

           8.  For a native linker on an ELF system, if the file /etc/ld.so.conf exists, the list of directories found in that file.

           If the required shared library is not found, the linker will issue a warning and continue with the link.

you may also be interested in autoPatchelfHook which will try and patch dependencies listeds in the executable’s headers. It can also set the rpath if you list the packages as runtimeDependencies in your derivation