Ashley Arthur

More Rust + Nix + Bindgen

How to set up nix packaging for a rust project which also links against an external C lib ? Well read on…

First credit where credit is due, I base a lot of my understanding on this excellent article: link. The goal is to create a derivation to let nix build the rust lib but also a nix shell to let us do local development whilst sharing the same declared dependencies. I’ll be using the nix setup for my freeswitch_rust project as an example.

{ pkgs ? import <nixpkgs> { 
    overlays = [ 
      (import (fetchTarball "https://github.com/oxalica/rust-overlay/archive/2e7ccf572ce0f0547d4cf4426de4482936882d0e.tar.gz"))
    ];
  } 
}:
let
  rustPlatform = pkgs.makeRustPlatform {
    rustc = pkgs.rust-bin.stable.latest.default;
    cargo = pkgs.rust-bin.stable.latest.default;
  };
in
rustPlatform.buildRustPackage rec {  
  pname = "freeswitch_rs";
  version = "0.1";
  nativeBuildInputs = with pkgs; [ 
    rustPlatform.bindgenHook
    pkgs.fs
  ] 
  cargoLock.lockFile = ./Cargo.lock;

  src = pkgs.lib.cleanSource ./.;
}

Firstly we’re using one of the excellent overlays available to define exactly what version of the rust toolchain we want to use. Having done that - we can have a rustified mkDerivation experience using rustPlatform.buildRustPackage.

Worth noting is the use of the bindgenHook to allow Bindgen’s build script successfully locate the native libs provided by nix. The internet has many good articles about early experimentation on how to successfully setup nix and Bindgen plumbing but weirdly I didn’t see many mentions on what I assume is the solution that emerged later on and now a bonafide nixpkg helper.

For those curious on what’s happening, the open source nature of nix naturally obliges:

populateBindgenEnv () {
    export LIBCLANG_PATH=@libclang@/lib
    BINDGEN_EXTRA_CLANG_ARGS="$(< @clang@/nix-support/cc-cflags) $(< @clang@/nix-support/libc-cflags) $(< @clang@/nix-support/libcxx-cxxflags) $NIX_CFLAGS_COMPILE"
    export BINDGEN_EXTRA_CLANG_ARGS
}

It seems the hook will pipe our native inputs into cargo/bindgen via BINDGEN_EXTRA_CLANG_ARGS env var, which is the direction alot of the early experimentation hit upon. It also helps bindgen find LIBCLANG_PATH - handy!

The final piece is to create dev shell based on our derivation, using inputsFrom to copy the nativeInputs for us.

{ pkgs ? import <nixpkgs> { 
    overlays = [ 
      (import (fetchTarball "https://github.com/oxalica/rust-overlay/archive/2e7ccf572ce0f0547d4cf4426de4482936882d0e.tar.gz"))
    ];
  }
}:
pkgs.mkShell {
  inputsFrom = [ (pkgs.callPackage ./default.nix { }) ];
  buildInputs = with pkgs; [
    rust-bin.stable.latest.rust-analyzer # LSP Server
    rust-bin.stable.latest.rustfmt       # Formatter
    rust-bin.stable.latest.clippy        # Linter
  ];
}