C++ IDE integration (autocomplete etc)

Hi all!

I’m trying to set up a development environment for my C++ project, including IDE integration. I’m currently flexible on which IDE to use, but am actively looking at CLion and VSCode. If it matters, I am working on a Mac and using Nix-Darwin.

So far, I have set up a shell.nix in my development directory to pull in compile dependencies, and also added a direnv .envrc file to automatically load it. This works flawlessly for CMake based builds (I can just cd into projectDir/build and invoke cmake ../ and we are good). And VSCode, with its direnv extension, does this automatically too! Perfect so far.

However, I’m wondering if/how I can extend this beauty to my IDE editor, so it can “see” the header files provided by dependencies, and thus offer real autocomplete and analysis on types they provide.

My understanding on this whole thing is vague, so please correct me if I’m wrong. But I think the compiler is able to find these files only because the clang-wrapper script adds them when the compiler is invoked. Is there a way to help the IDE see them too? If this is possible, Nix as a development platform will be amazing!

If your tooling can use a compile_commands.json file, then you generate one of those by passing -DCMAKE_EXPORT_COMPILE_COMMANDS=YES to cmake. That file will have all the right paths. I use this with ccls as an LSP server with emacs as my editor.

Thanks, @acowley!

I did actually see this (and indeed I am interested in ccls). However, my compile_commands.json comes out as:

[
{
  "directory": "/Users/qoli/git/MyProject/build",
  "command": "/nix/store/f72qi2a6djg9iz27ljxd44ji41lshs6h-clang-wrapper-7.1.0/bin/clang++     -fstandalone-debug -g -arch x86_64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk -mmacosx-version-min=10.12   -std=gnu++17 -o CMakeFiles/MyProject.dir/main.cpp.o -c /Users/qoli/git/MyProject/main.cpp",
  "file": "/Users/qoli/git/MyProject/main.cpp"
}
]%  

Is that sufficient? I think I was expecting that this file would have an actual list of directories in it, rather than just the command to run to compile. Does ccls run this and then parse the output somehow?

Anyhow, with this, ccls doesn’t appear to be working. I made a symlink from this (in the build directory) to the project’s root folder. Perhaps it is my IDE-ccls plugin though? Is there a succinct way to test ccls by itself?

Thanks so much!

If it is useful, I see that:

$ /nix/store/f72qi2a6djg9iz27ljxd44ji41lshs6h-clang-wrapper-7.1.0/bin/clang++ -v -x c++ /dev/null 
...
#include <...> search starts here:
 /nix/store/rbqxvhdvcyfrpqxinlb98w6v3farziqp-boost-1.67.0-dev/include
 /nix/store/2lykcxvy85rh4wk48wj77bjcia6asv7f-libc++-7.1.0/include
 /nix/store/0wvwlf3pbkgh8k5g52q666b3w3s3bm0m-libc++abi-7.1.0/include
 /nix/store/mlm531gxzrwdxhjymz078dqrjn5ijjar-compiler-rt-7.1.0-dev/include
 /nix/store/dnzqnwr0xl4zdfqrqgbdk0nbflfq4m5r-lldb-7.1.0/include
 /nix/store/rzw3d9a81llf3blsadws2yrl97m7bwfz-libsamplerate-0.1.9-dev/include
 /nix/store/6rlip0i5ss8naw6l30ly0rasyfmq5pss-jack2-1.9.12/include
 /nix/store/l932p3sbmwax3angg3rq1bjpxfrm96q2-readline-6.3p08-dev/include
 /nix/store/pr8310va7sjn9qdc1ia3ghbb1pqmbkyh-ncurses-6.1-20190112-dev/include
 /nix/store/4s1rrvqfxprc93rzm7p3qncivwlxmqj5-binutils-2.31.1/include
 /nix/store/2lykcxvy85rh4wk48wj77bjcia6asv7f-libc++-7.1.0/include/c++/v1
 /nix/store/cxksxn1zyb4nxy1dkpxfdmlppbpzhq4p-swift-corefoundation/Library/Frameworks (framework directory)
 /nix/store/f72qi2a6djg9iz27ljxd44ji41lshs6h-clang-wrapper-7.1.0/resource-root/include
 /nix/store/v6y7h7hs82n9zm841l2p8f2pm3vsa1y2-Libsystem-osx-10.12.6/include
End of search list.
...

The output I see from ccls includes references to both the locations:

/nix/store/2lykcxvy85rh4wk48wj77bjcia6asv7f-libc++-7.1.0/include

and

/nix/store/v6y7h7hs82n9zm841l2p8f2pm3vsa1y2-Libsystem-osx-10.12.6/include

which are of course present in the output above. So it seems as though something is working.

However, in main.cpp I am including the header file <jack/jack.h>, which is located inside:

/nix/store/6rlip0i5ss8naw6l30ly0rasyfmq5pss-jack2-1.9.12/include

a location which is also present in the output above. Yet, this location is not mentioned in the ccls output, and the VSCode is highlighting the header as “not found”.

Does this make sense?

Perhaps your CMakeLists.txt isn’t setting up the include path sufficiently. With nix-shell you don’t always need to do this because nix tells the compiler how to find things, but the IDE tooling typically does want those include paths. Admittedly, this can be tricky because nix works hard to make things work automatically, but extra tooling isn’t aware of the same magic. I actually use my own derivation for ccls and a project-aware helper. I then define a helper function in my config.nix:

ccls-fun = buildInputs: pkgs.callPackage ./nix/ccls/nixAware.nix {} {
    inherit buildInputs;
};

and finally setup a development shell.nix like this,

{ pkgs ? import <nixpkgs> {}}:
pkgs.mkShell rec {
  nativeBuildInputs = with pkgs; [ (ccls-fun buildInputs) pkg-config cmake ];
  buildInputs = [ pkgs.jack2 ];
  cmakeFlags = [ "-DCMAKE_EXPORT_COMPILE_COMMANDS=YES" ];
}

The CMakeLists.txt I used for this test is this,

cmake_minimum_required(VERSION 3.10)
project(qoli-jack)
find_package(PkgConfig)
pkg_check_modules(JACK REQUIRED IMPORTED_TARGET jack)
add_executable(hey main.cpp)
target_link_libraries(hey PkgConfig::JACK)

And then all the lsp goodness works in emacs thanks to direnv.

I will write up a more complete presentation of my setup as a blog post if anyone would find that helpful.

I actually use my own derivation for ccls and a project-aware helper. I then define a helper function in my config.nix

Oh, wow! This is super neat!

So, given the way you designed nixAware.nix, it seems like it’s necessary to externally parse the output of clang -v. Is that “expected”? As in, not doing something along these lines simply can’t work? It is not clear to me how simply knowing the compile command could help any tool to discern these paths if they don’t parse this themselves, but in that case, why isn’t ccls doing this internally?

Anyway. I will give this a try, for sure. This looks like a great solution, and as a bonus will work with any IDE that has ccls support.

In your opinion, is this something that could be upstreamed to nixpkgs in some sense?


Also, if you might indulge me, what is the purpose of your custom ccls derivation? It seems like the magic of parsing the buildInputs locations and adding them to extraArgs is all done in your nixAware.nix. And that should work with the upstream derivation, right? (I also have a bunch of questions about the upstream derivation, but I think they’re not actually good questions, they’re just due to my inexperience with all these tools.)


I will write up a more complete presentation of my setup as a blog post if anyone would find that helpful.

One million percent yes!

Thanks so much for your help. You’ve gone way above and beyond!

It is an issue for upstream ccls, too. I don’t at the moment recall exactly how it is setup now. The author is extremely knowledgeable, and has been a huge help to me over the years; I’m sure he helped me get it all working way back when. I think that whatever mechanism ccls uses to get those paths is some interaction with libclang, but nix wraps clang rather thoroughly, making the paths ccls is able to obtain wrong for us.

My ccls derivation pre-dates the one in nixpkgs. ccls can also be a fast-moving package, which is a bit frustrating to work with in nixpkgs since one sometimes has to be fairly persistent and patient to get PRs merged. As ccls development slows down, I think it will make more sense to care more closely for what’s in nixpkgs.