Search code examples
neovimclangd

Making Clangd work with Conan & Cmake (Clangd can't find headers outside project folder)


Problem description

I am cross compiling with arm-none-eabi-g++ and I am using conan C/C++ binary package manager together with cmake build system.


My project consists of C, C++ and ARM assembly files. And it uses our customer's headers which are all located inside the /home/xenlauz/.conan/data (this is a standard directory where conan keeps binaries/libraries and header files).

My project is located elsewhere but it builds fine.


I want to make cross-referencing work inside "Neovim" so that I can "go to definition", "go to reference", "rename all symbols at once"... I know that this can be implemented by using clangd language server (LS).

In order to achieve this, I first had to configure my /home/xenlauz/.config/nvim/init.lua like shown below...

In "Neovim", I am using package manager "Packer" which is set up like this in order to be able to install the plugins nvim-lsp-installer and nvim-lspconfig:

local plugin_packer = require('packer')
plugin_packer.startup(
    function()
        use {
            "williamboman/nvim-lsp-installer",  -- LS manager.
            requires = {
                "neovim/nvim-lspconfig"         -- LS configuration engine.
            }
        }   
    end
)

Plugin nvim-lsp-installer is a LS manager which helps us to install and manage any kind of LS while plugin nvim-lspconfig is a native LS configurator that makes sure "Neovim" can talk with the installed LS. This plugin needs no configuration and we shouldn't configure it at any point because nvim-lsp-installer is used to configure the individual servers and then pass configuration to nvim-lspconfig like this:

local plugin_lspinstaller = require("nvim-lsp-installer")

--  NOTE: Store the project directory (Neovim has to be opened in project directory)
local myroot_dir = function()
    return vim.fn.getcwd()
end

plugin_lspinstaller.on_server_ready(
    function(server)

        -- NOTE: Create options.
        local server_opts = {}

        if server.name == "clangd" then
            server_opts = {
                cmd = {
                    "clangd"
                },
                filetypes = {
                    "c",
                    "cpp",
                    "objc",
                    "objcpp",
                    "cuda",
                    "proto"
                },
                root_dir = myroot_dir
            }
        end

        -- NOTE: Pass options to the `nvim-lspconfig` to apply them.
        server:setup(server_opts)

    end
)

With this settings I am able to open "Neovim" and install the clangd using a "Neovim" command :LspInstall clangd. This installs a local copy of clangd executable inside the /home/xenlauz/.local/share/nvim/lsp_servers/clangd/ and I can verify that it is working by running it:

~/.local/share/nvim/lsp_servers/clangd/clangd/bin/clangd --version
clangd version 15.0.6 (https://github.com/llvm/llvm-project 
088f33605d8a61ff519c580a71b1dd57d16a03f8)
Features: linux+grpc
Platform: x86_64-unknown-linux-gnu

This clangd binary automatically runs if I (a) open "Neovim" in my project's directory and then (b) open any .cpp file.


Unfortunately clangd does not recognize custom types defined in headers located in the /home/xenlauz/.conan/data so, e.g. "go to definition" does not work.

I know that clangd relies on the compile_commands.json file which contains array of objects, where each object typically contains the following fields:

  • directory (directory where compilation command command should be executed)
  • command (compilation command that transforms source file to binary)
  • file (source file being compiled)

In my case compile_commands.json configuration file is generated inside the project's subfodler ./build/compile_commands.json and is generated by cmake whose CMakeLists.txt includes two crucial lines:

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include_directories(/home/xenlauz/.conan/data)

First line instructs cmake to generate compile_commands.json and second command adds -I /home/xenlauz/.conan/data flag in every command member inside the compile_commands.json. I verified that this flag is really added.

This still will not make clangd recognize headers inside the /home/xenlauz/.conan/data. The log shows that compile_commands.json file is found:

[ERROR][2023-03-10 09:12:26] .../vim/lsp/rpc.lua:733    "rpc"   "clangd"    "stderr"    "I[09:12:26.581] Loaded compilation database from /mnt/c/Users/xenlauz/Documents/002--projects/003--sick--workpackage--microengine/ksz8851-test-at-f429zi/worktrees/develop/build/compile_commands.json

but there is an error regarding the .s ARM assembly file (last line):

[ERROR][2023-03-10 09:12:27] .../vim/lsp/rpc.lua:733    "rpc"   "clangd"    "stderr"    "E[09:12:27.426] Indexing /mnt/c/Users/xenlauz/Documents/002--projects/003--sick--workpackage--microengine/ksz8851-test-at-f429zi/worktrees/develop/src/microengine_at_nucleo_stm32F429/Device/startup_stm32f429xx.s failed: Couldn't build compiler invocation\nI[09:12:27.426] --> $/progress\n"

Note that both lines start with [ERROR] but later in both lines we can see I[09:12:26.581] (for info) and E[09:12:27.426] for error. What a weird log!


I was thinking that maybe I have to configure clangd a bit. To configure it I have two options...

A: clangd offers a lot of parameters which I can see if I call it like this:

~/.local/share/nvim/lsp_servers/clangd/clangd/bin/clangd --help

These parameters can be included in my "Neovim" configuration file /home/xenlauz/.config/nvim/init.lua by slightly editing clangd configuration that I already referenced like this:

cmd = {
    "clangd",
    "--log=verbose"
}

Here I added a parameter that enables me a more verbose log which unfortunately I don't understand and I can not paste it here, because post size restrictions.

B: the other option is to add a .clangd configuration file (link) in the project's main folder. I did add this configuration file and clangd responds to the changes in this file as well. This is why I tried setting it like this:

CompileFlags:
    CompilationDatabase: ./build
    Add: -I/home/xenlauz/.conan/data

2nd line points clangd to the compile_commands.json file while 3rd line was suppose to add flag -I/home/xenlauz/.conan/data somehow to the compile commands... Well even after this "go to definition" still does not work...


Any suggestions how to configure clangd to work for me. It has already taken a lot of time...


ADD1:

After suggestion from user "HighCommander4" I tried editing the startup parameters of clangd like this:

cmd = {
    "clangd",
        "--log=verbose",
        "--compile-commands-dir=./build",
        "--query-driver=/usr/bin/arm-none-eabi-g++,/usr/local/bin/arm-none-eabi-g++,/usr/bin/arm-none-eabi-cpp,/usr/local/bin/arm-none-eabi-cpp,arm-none-eabi-gcc,/usr/bin/arm-none-eabi-gcc,/usr/local/bin/arm-none-eabi-gcc"
},

Unfortunately error: In the included file "stdexcept" file not found error from verbose log remains. I double checked the paths to compilers by using, e.g. whereis arm-none-.eabi-g++. I also tried different combinations of compilers in the list but error remains. Instroction about --query-driver= says:

clangd compilation flags options:

  --compile-commands-dir=<string>     - Specify a path to look for compile_commands.json. If path is invalid, clangd will look in the current directory and parent paths of each source file
  --query-driver=<string>             - Comma separated list of globs for white-listing gcc-compatible drivers that are safe to execute. Drivers matching any of these globs will be used to extract system includes. e.g. /usr/bin/**/clang-*,/path/to/repo/**/g++-*

ADD2:

After ADD1 I was getting really frustrated and decided to search for the string arm-none-eabi-b++ from within the ~/.conan/ folder. I read a bit and quickly found out that my programs are not being compiled by the system compilers, e.g. /usr/bin/arm-none-eabi-g++ but the ones from the ~/.conan/ directory:

/home/xenlauz/.conan/data/euler_toolchain_arm_rtos/1.1.0/sick/release/package/cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-g++

After I found this I changed my "Neovim" configuration like this:

            cmd = {
                "clangd",
                "--log=verbose",
                "--compile-commands-dir=./build",
                "--query-driver=/home/xenlauz/.conan/data/euler_toolchain_arm_rtos/1.1.0/sick/release/package/cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-g++"
            },

and it worked. The lesson I learned here is that I should use clangd with --query-driver= and always check the logs to find out which compiler is being used. I was a bit naive and believed that system compilers are being used.


Solution

  • The logs show that the file being opened (Ksz8851snlDriverTests.cpp) contains errors:

    • In included file: 'cstddef' file not found"
      • with the "included file" being euler/abidefs/abi.h
    • then a bunch of Unknown type name 'IBase_latest'
    • and finally: Too many errors emitted, stopping now

    Just to confirm, do you see these errors in your editor? If not, there may be an issue with your editor configuration where it's not showing diagnostics from clangd. If you do see them, well, they're the reason why go-to-definition is not working :)

    The last error in particular, Too many errors emitted, stopping now, indicates that clangd gives up trying to build an AST for the file due to the previous errors. Without a correct AST, most clangd features including go-to-definition will not work correctly.

    So, you need to start by fixing the above errors. Let's take a look at them more closely.


    The first error, In included file: 'cstddef' file not found" indicates that clangd is having trouble finding standard library headers such as <cstddef>.

    This is a somewhat common problem, addressed in this FAQ question on the clangd website.

    Since you're using a cross-compiler, arm-none-eabi-g++, the last paragraph of that section is applicable:

    If you’re using an unusual compiler (e.g. a cross-compiler for a different platform) you may want to pass --query-driver=/path/to/mygcc to allow clangd to extract the include paths from it directly.

    I would recommend trying --query-driver as suggested (there are more details about its usage in clangd --help).


    The remaining errors I suspect are downstream of the <cstddef> one, though I can't be sure. If you've fixed the <cstddef> error and are still seeing other errors, please feel free to share a new set of logs and I can take another look.