Llvm-profdata, grcov and Rust code coverage

I am rediscovering NixOS and trying to setup a development environment for Rust.
So far, I have the following default.nix file in my repo:

# default.nix
with import <nixpkgs> { };
stdenv.mkDerivation {
  name = "dev-environment";
  buildInputs = [
    pkg-config
    openssl
    libgit2
    zlib
    rustc
    cargo
    llvm
    rustc.llvmPackages.llvm
    grcov
    wxGTK30-gtk3
  ];
}

Everything works except code ceverage. Here is what happens when I run grcov:

❯ grcov . -s . --binary-path target/debug/
17:47:35 [ERROR] Error while executing llvm tools: We couldn't find llvm-profdata. Try installing the llvm-tools component with `rustup component add llvm-tools-preview`.

But llvm-profdata does exists on PATH:

❯ llvm-profdata
llvm-profdata: No command specified!
USAGE: llvm-profdata <merge|show|overlap> [args...]

I dont know how to solve it. Please help :slight_smile:

Alright, I have the exact same issue. I did some investigation:

Grcov is raising an error from here because this path is wrong.

The Tool enum comes from the cargo-binutils project which is getting the path from a rustlib() function defined in the rustc.rs file. Here we can see the path where grcov is looking for lvm-profdata :

$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin

This path does not exist (there is no bin folder here so no profdata because even if it’s in our PATH, it lives under a different folder (for my config) :

[nix-shell]$ echo "$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin"
/nix/store/xjx558ys8fnyl0kwxs4ws0806362snj7-rustc-1.60.0/lib/rustlib/x86_64-unknown-linux-gnu/bin

[nix-shell]$ whereis llvm-profdata
llvm-profdata: /nix/store/qp4b28b9x5nqspvl7hsn8m5pp5j1j83x-llvm-14.0.1/bin/llvm-profdata
1 Like

Yeap, I saw it too. This is definitely NixOS related and not an issue in grcov.

I even opened an issue on their GitHub: https://github.com/mozilla/grcov/issues/848.

1 Like

Alright, thank’s for the issue on grcov, the solution on their side would be to add an “LLVM_TOOLS_PATH” environment variable and optionally reading the lib’s location from it.

By the way, i’m thinking : if there is a maintainer on the grcov package he must have been using the tool… I sent an email to justin@restivo.me telling him about this discourse, maybe he will respond.

1 Like

Until we have a clean solution here is a little work-around that i use to run my coverage (if you use podman/docker):

  shellHook = ''
    podman build -t grcov - <<EOF
      FROM docker.io/rust
      WORKDIR /srv
      RUN rustup component add llvm-tools-preview
      RUN cargo install grcov
      ENTRYPOINT ["grcov"]
    EOF
    alias grcov='podman run -v $PWD:/srv grcov'
  '';

You can then use grcov almost as if you had it installed

1 Like

I just tried grcov with my nascent rust environment. First I did cargo install grcov, but then running grcov produced the error about missing llvm-profdata.

Next I did what the error message suggested: rustup component add llvm-tools-preview. That solved the problem; although llvm-profdata is still not on $PATH.

My rust environment is declared in this flake (derived from Rust - NixOS Wiki)

{
  inputs = {
    nixpkgs.url = "git+file:///nix/nixpkgs?ref=upstream/nixpkgs-unstable";
  };

  outputs = inputs@{ self, nixpkgs, ... }:
    let
      pkgs = import nixpkgs { localSystem = "${system}"; };
      system = "x86_64-linux";
      rust_shell_pkgs = with pkgs; [
        llvmPackages_latest.bintools
        llvmPackages_latest.lld
        llvmPackages_latest.llvm
        zlib
        rustup
      ];
    in {
      # https://nixos.wiki/wiki/Rust
      # `nix develop .#rust_shell`
      rust_shell = pkgs.mkShell rec {
        name = "rust_shell";
        buildInputs = rust_shell_pkgs;
        NIX_DEBUG = 1;
        RUST_BACKTRACE = 1;
        RUSTC_VERSION = pkgs.lib.strings.fileContents ./rust-toolchain;
        # https://github.com/rust-lang/rust-bindgen#environment-variables
        LIBCLANG_PATH =
          pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
        # Add some headers to bindgen search path
        BINDGEN_EXTRA_CLANG_ARGS =
          (builtins.map (a: ''-I"${a}/include"'') [ pkgs.glibc.dev ])
          ++ [
            ''
              -I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
            ''-I"${pkgs.glib.dev}/include/glib-2.0"''
            ''-I"${pkgs.glib.out}/lib/glib-2.0/include/"''
          ];
        shellHook = ''
          export PATH="''${CARGO_HOME:-$PWD/.cargo}/bin:$PATH"
          toolchain_bin="toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin"
          export PATH="''${RUSTUP_HOME:-$PWD/.rustup}/$toolchain_bin:$PATH"
          # call `rustup` to initialize toolchain, loading cargo, et. al.
          rustup --version
        '';
      };
      devShell.x86_64-linux = self.rust_shell;
    };
}

Of note is that this shell installs rustup but not cargo; it let’s rustup install (and manage) cargo and other executables. The shell defines $LIBCLANG_PATH and $BINDGEN_EXTRA_CLANG_ARGS; I do not yet know rust well enough to know when these matter.

I’m not yet sure how best to integrate installation of grcov and llvm-tools-preview into this shell. It might be sufficient to add them to the end of the shell hook if there execution would take no time when already present. Or, maybe they could be added to Cargo.toml (assuming it has the notion of dev dependencies).

My full series of commands for success was

nix develop
cargo install grcov
rustup component add llvm-tools-preview
RUSTFLAGS="-Cinstrument-coverage" cargo build
RUSTFLAGS="-Cinstrument-coverage" LLVM_PROFILE_FILE="your_name-%p-%m.profraw" cargo test
grcov . -s . --binary-path target/debug/
1 Like

Yes, this is very similar to the solution I used eventually. Thank you :slight_smile:

Hosted by Flying Circus.