Clang-tooling woes on NixOS

I’ve been trying to get a good working C++ project environment in NixOS. My project requires that I use clang and cmake. These work great, with the help of the clang wrapper which sets the stdlib paths that make libraries like iostream available. The trouble comes with using clang-tooling like clang-tidy. These aren’t wrapped, and are unable to find these system libraries. The compile_commands.json file produced by cmake also doesn’t contain reference to the system library paths, because that’s encapsulated within the clang binary wrapper. I couldn’t find any reference C++ projects that are built with nix. Any suggestions on how I can get it to clang-tidy to also locate stdlibs during compilation?

2 Likes

This is definitely something that I think we should try to figure out. Right now the experience is pretty bad. You can do something like this:

CFLAGS=$(cat $(nix-build '<nixpkgs>' -A stdenv.cc)/nix-support/{cc,libc}-cflags)
LDFLAGS=$(cat $(nix-build '<nixpkgs>' -A stdenv.cc)/nix-support/cc-ldflags))

and pass these into clang-tidy. But ideally the user wouldn’t have to set this up.

Thanks for getting back. Could you illustrate how you’d pass them to clang-tidy? I’ve tried it with clang-tidy -p=build/ path/to/srcfile.cpp -- -DCFLAGS=$CFLAGS -DLDFLAGS=$LDFLAGS to no avail.

Would clang-tidy -extra-arg="$CFLAGS" work? In the end this should become a wrapper we can have in nixpkgs similar to what we do with ccls/cquery.

1 Like
[nix-shell:~/projects/SPA]$ clang-tidy -extra-arg="$CFLAGS" src/spa/src/simple_parser/parser.cpp 
Error while trying to load a compilation database:
Could not auto-detect compilation database for file "src/spa/src/simple_parser/parser.cpp"
No compilation database found in /home/jethro/projects/SPA/src/spa/src/simple_parser or any parent directory
json-compilation-database: Error while opening JSON database: No such file or directory
Running without flags.
1 warning and 1 error generated.
Error while processing /home/jethro/projects/SPA/src/spa/src/simple_parser/parser.cpp.
warning:  -B/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib/lib
-B/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ -idirafter /nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev/include -idirafter /nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0/lib/gcc/*/*/include-fixed: 'linker' input unused [clang-diagnostic-unused-command-line-argument]
/home/jethro/projects/SPA/src/spa/src/simple_parser/parser.cpp:1:10: error: 'simple_parser/exceptions.h' file not found [clang-diagnostic-error]
#include "simple_parser/exceptions.h"
         ^

[nix-shell:~/projects/SPA]$ echo $CFLAGS
-B/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib/lib -B/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ -idirafter /nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev/include -idirafter /nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0/lib/gcc/x86_64-unknown-linux-gnu/7.4.0/include-fixed

unfortunately not ): here it is missing project dependencies. If I pass it the cmake build directory:

[nix-shell:~/projects/SPA]$ clang-tidy -p=build/ -extra-arg="$CFLAGS" src/spa/src/simple_parser/parser.cpp 
1 warning and 1 error generated.
Error while processing /home/jethro/projects/SPA/src/spa/src/simple_parser/parser.cpp.
warning:  -B/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib/lib
-B/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ -idirafter /nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev/include -idirafter /nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0/lib/gcc/*/*/include-fixed: 'linker' input unused [clang-diagnostic-unused-command-line-argument]
/home/jethro/projects/SPA/src/spa/src/simple_parser/exceptions.h:10:10: error: 'stdexcept' file not found [clang-diagnostic-error]
#include <stdexcept>

I managed to get this working. For -extra-arg to work, you have to put the flag on every single argument, not the whole batch of them.

You can also pass in the lot of flags after a double dash.

clang-tidy src/spa/src/simple_parser/parser.cpp -- "$CFLAGS"

The double dash would be less suitable for a wrapper, but easier to get working in a one off right now.

Here is how I handled -extra-arg in a makefile

1 Like

Hey @ylixir i just had a look at your makefile. This looks awesome, but unfortunately the variable

$NIX_x86_64_unknown_linux_gnu_CXXSTDLIB_COMPILE

is completely empty for me, while

$NIX_x86_64_unknown_linux_gnu_CFLAGS_COMPILE

contains all the normal things.

When i grep through the whole gcc/clang packages, i can see that only the actual g++/clang++ wrappers contain the interesting C++ STL include paths.

So it looks like i can only get at those include paths by running the c++ wrappers with NIX_DEBUG=1 and then extract the headers from there, is that correct?

So… i got at least include-what-you-use to work with the following frankenstein-trick that reuses the clang++ wrapper script to wrap iwyu.

{
  include-what-you-use,
  clangStdenv
}:
clangStdenv.mkDerivation {
  name = "include-what-you-use-fix";
  src = include-what-you-use;

  phases = [ "installPhase" ];

  installPhase = ''
    mkdir $out/
    cp -r $src/bin $out
    chmod +w -R $out/bin

    # copy the c++ wrapper and patch the last exec line to use iwyu
    mv $out/bin/include-what-you-use $out/bin/.include-what-you-use
    cp ${clangStdenv.cc}/bin/c++ $out/bin/include-what-you-use
    sed -i "s;^exec[^\\]*;exec $out/bin/.include-what-you-use ;" $out/bin/include-what-you-use

    # the python tool is hardcoded to the original iwyu, fix that too
    sed -i "s;${include-what-you-use};$out;" $out/bin/iwyu_tool.py
  '';
}

For my use case, this turned out to work pretty well.

Of course that does not work with clang-tidy… although i got that running by creating an “echo wrapper” of the clang++ wrapper, and a tiny python script that simply “fixes” all command lines in the compile_commands.json file. This of course still does not fix the cmake integration of clang-tidy

2 Likes

I got the following clang-tidy workflow to work:

# assume we are in CMake C++ project folder
$ mkdir build && cd build
$ cmake -DCMAKE_CXX_CLANG_TIDY="clang-tidy" ..
$ make
# clang-tidy is automatically run by cmake on every compiled file

This works by wrapping clang-tidy: I provide a package clang-tools-wrapper here: https://github.com/tfc/nur-packages

It’s not perfectly generic, but at least one can get static analysis during normal compilation

That’s fantastic. I finally got around to sitting down to make this thing nicer and…tada, I’m not going to bother because what you have looks much nicer than what I was going to do.

How would one go about getting that thing merged into the nix repo?

For me, clang tools are pretty useless in their current form. Something like what you have in nur-packages is gold

1 Like

Awesome to hear that it looks useful to fellow nix users, too!

I am not sure. It feels like “the right way” to do this is to ship the clang package (since it contains clang-tidy) with the same kind of wrapper around clang-tidy as clang++. I don’t however dare to modify this nix expression and wrapper (yet) as it’s horribly complicated.

Until then i would battle-test this wrapper more to be more certain that it works for all possible use cases. I also started writing tests and run them regularly on unstable nixpkgs. (https://hydra.kosmosgame.com/jobset/github/nur-packages#tabs-jobs)

@tfc: Thanks for the example about borrowing the clang wrapper! I’m using this to get a working stand-alone clang-tidy:

$ cat .config/nixpkgs/overlays/clang-tidy.nix
# From https://discourse.nixos.org/t/clang-tooling-woes-on-nixos/2314/9
self: super:
{
  clang-tools = super.symlinkJoin {
    name = super.clang-tools.name;
    paths = [ super.clang-tools ];
    postBuild = ''
      mv $out/bin/{,.}clang-tidy
      cp ${self.clangStdenv.cc}/bin/c++ $out/bin/clang-tidy
      sed -i "
        /^exec[^\\]*/{
          s;;exec $out/bin/.clang-tidy ;
          ifor arg in \''${extraBefore+\"\''${extraBefore[@]}\"};do
          i  extraBeforePrefixed+=( -extra-arg \"\$arg\" )
          idone
          ifor arg in \''${extraAfter+\"\''${extraAfter[@]}\"};do
          i  extraAfterPrefixed+=( -extra-arg \"\$arg\" )
          idone
          iextraBefore=(\''${extraBeforePrefixed+\"\''${extraBeforePrefixed[@]}\"})
          iextraAfter=(\''${extraAfterPrefixed+\"\''${extraAfterPrefixed[@]}\"})
        }
      " $out/bin/clang-tidy
    '';
  };
}
1 Like

@chkno:

Your wrapper script looks great.

It works for me if i run it like clang-tidy main.cpp, but if i use it through cmake with -DCMAKE_CXX_CLANG_TIDY=clang-tidy, then clang-tidy fails to find the STL includes. Does that work for you?

Might be solved by: https://github.com/NixOS/nixpkgs/pull/73345 (Thx: @Mic92)