Skip to content

Instantly share code, notes, and snippets.

@Strus
Last active September 2, 2024 20:29
Show Gist options
  • Save Strus/042a92a00070a943053006bf46912ae9 to your computer and use it in GitHub Desktop.
Save Strus/042a92a00070a943053006bf46912ae9 to your computer and use it in GitHub Desktop.
How to use clangd C/C++ LSP in any project

How to use clangd C/C++ LSP in any project

tl;dr: If you want to just know the method, skip to How to section

Clangd is a state-of-the-art C/C++ LSP that can be used in every popular text editors like Neovim, Emacs or VS Code. Even CLion uses clangd under the hood. Unfortunately, clangd requires compile_commands.json to work, and the easiest way to painlessly generate it is to use CMake.

For simple projects you can try to use Bear - it will capture compile commands and generate compile_commands.json. Although I could never make it work in big projects with custom or complicated build systems.

But what if I tell you you can quickly hack your way around that, and generate compile_commands.json for any project, no matter how compilcated? I have used that way at work for years, originaly because I used CLion which supported only CMake projects - but now I use that method succesfully with clangd and Neovim.

Method summary

Basically what we need to achieve is to create a CMake file that will generate a compile_commands.json file with information about:

  1. All source files
  2. All include directories
  3. External libraries
  4. Precompiler definitions

We can do that easily without really caring about if the CMake-generate result will compile at all - we don't need to rewrite our existing build system, just hack a CMake file that will generate enough information for Clangd to work.

Prerequisities

  1. CMake
  2. clangd

How to

First, create a CMakeLists.txt file in the root folder of your projects, with content similar to this:

cmake_minimum_required(VERSION 3.8)
project(my_project)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Change path from /src if needed, or add more directories
file(GLOB_RECURSE sources
        "${CMAKE_SOURCE_DIR}/src/*.c"
        "${CMAKE_SOURCE_DIR}/src/*.cpp"
        )
# Add precompiler definitions like that:
add_definitions(-DSOME_DEFINITION)

add_executable(my_app ${sources})

# Add more include directories if needed
target_include_directories(my_app PUBLIC "{CMAKE_SOURCE_DIR}/include")

# If you have precompiled headers you can add them like this
target_precompiled_headers(my_app PRIVATE "${CMAKE_SOURCE_DIR}/src/pch.h")

(If your project already uses CMake, then you just need to add set(CMAKE_EXPORT_COMPILE_COMMANDS ON) to your main CMakeLists.txt file.)

Modify hacky CMakeLists.txt according to your project structure, and run:

cmake -S . -G "Unix Makefiles" -B cmake

which will generate the CMake output inside cmake directory. Check if compile_commands.json is there.

NOTE: You need to run that command every time you add/remove a source file in your project.

If you need more (ex. include external libraries like Boost), check out CMake documentation

Now you have two options:

  1. Symlink compile_commands.json to your root project folder:
ln -s cmake/compile_commands.json .

OR

  1. Create .clangd file in your root project folder, with the following contents:
CompileFlags:
  CompilationDatabase: "cmake"

Now open the project in you editor and everything should work (assuming clangd LSP is started).

@leolangberg
Copy link

Beautiful!

@sowmith1999
Copy link

This was helpful, thank you.

The important thing to have in your CMakeLists.txt is set(CMAKE_EXPORT_COMPILE_COMMANDS ON), this makes cmake create compile_commands.json in the build dir and then use either a .clangd file or symlink to point clangd to it.

@DanielHidalgoChica
Copy link

This is the best day of my life. Thank you so much.

@PowerUser64
Copy link

It might be good to mention Bear - https://github.com/rizsotto/Bear. All you do is run bear -- (your compile command) and it gets all the commands that your build system runs and spits out a compile_commands.json for you.

For example, I ran bear -- cmake --build ./build in a repository that I needed to use clangd in and compile_commands.json was created automagically by bear in my working directory.

I found Bear a while after finding this gist, so I hope this can help some others who are in the same situation I was in.

@Strus
Copy link
Author

Strus commented Jun 26, 2024

@PowerUser64 Yeah, I know this tool - unfortunately I was never able to use it in practice. It works for small projects with simple build systems, but most big C/C++ projects that use Makefiles or some custom mambo-jumbo make it very hard to inject bear there. At work I always ended up with a hack CMake like this.

But I will add a mention that for simple projects one could try bear or similar tools.

BTW, in your example - if you use cmake there is no need to use bear. Just add set(CMAKE_EXPORT_COMPILE_COMMANDS ON) to your main CMakeLists.txt.

@PowerUser64
Copy link

if you use cmake [...], just add set(CMAKE_EXPORT_COMPILE_COMMANDS ON) to your main CMakeLists.txt.

Ooh, I didn't know that! Thought the other stuff in the example was somehow required too. Might be good to mention that on its own too somewhere. Thanks for the tip!

@Strus
Copy link
Author

Strus commented Jun 26, 2024

Good idea, I've added a mention about that.

@ktgon
Copy link

ktgon commented Jul 3, 2024

Thank you for this helpful post !!!

@DrLarck
Copy link

DrLarck commented Aug 20, 2024

Thank you man! Now I can get rid of proprietary IDEs!

@SmilingJack1
Copy link

Is it possible to make a script that generates the CMakeLists.txt file?

@Strus
Copy link
Author

Strus commented Aug 21, 2024

@SmilingJack1 It depends. A generic script? No. A specific script that would generate a CMake for the needs of your project? Yes, as it is just a text document. Although you would need to do it on your own.

For example I did that a few times for my work projects, where there was a custom build system that was using some file format to specify which repositories should be used by a specific project - so I parsed that files, and then generated a CMake file similar to the one you see in the gist, but which paths to different repositories.

@youssef-lr
Copy link

Thanks a lot for this!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment