HOW-TO: Use a C++ Language Server with the OpenCPI Runtime


I’ve always wanted to get a language server working with the OpenCPI source code to make searching it a lot easier, but until I discovered a new tool this week I thought it was way too much effort to try to work on.

Introduction to LSP

For the unaware, a language server is the piece of software that lets your text editor do things like “Go to Definition”, or “Go to references”, or code actions like “Rename”. These let you avoid using more primitive mechanisms to achieve these results, like using grep or rg to find strings across the repo, or using sed to do find and replace. These alternatives are inherently error prone, and can miss some occurrences or rename occurrences that aren’t related.

For many modern languages, these language servers are quite easy to configure due to the largely very predictable way the source code of the language is laid out (python and rust are really good examples of this). This is not the case for C or C++ projects that can layout their source in arbitrary ways, and can use a myriad of different build tools.


However, some tools can create files that help a language server parse a project. clang based tooling can produce something called compile_commands.json, which is a full list of all arguments given to all parts of the build process. From the perspective of a language server, this most usefully contains the -I includes, allowing a dependency tree to be parsed for each file.

Unfortunately, as far as I could tell, there is no built in way for autotools based flows (like OpenCPI) to generate this or a similar file.

Using bear on the OpenCPI Runtime

Recently, I became aware of a tool called bear. Very quick summary: bear intercepts all processes related to C/C++ compilation that come out of an arbitrary build script and generates a compile_commands.json. I thought I might as well try this out on OpenCPI, and it was incredibly easy to get working.

I decided to use the C++ LSP Server ccls. My text editor is Neovim, so I used the setup available in nvim-lspconfig. There is also an integration for VSCode. Other C++ LSP Servers are available, and I imagine can be integrated into other editors too.

To start, install your LSP Server and bear. On Ubuntu 22.04:

sudo apt-get install -y bear ccls

You could run bear on the whole scripts/ script, but bear slows down the build process it analyses so this may not be a good idea. Also, it will intercept the prerequisites build processes as well, which could cause a very large compile_commands.json file.

The main part of the OpenCPI build process that we want to intercept is the call to make framework, but we would also like the make driver part too. Honestly, the easiest way to do this is just to build OpenCPI once to completion and then rebuild the bits we need. There is probably a way to save time here but I already had a built copy, so:

./scripts/ --minimal  # Only run this if you are in a fresh clone
source ./cdk/ -r
make cleanframework cleandriver
bear -- make framework driver

This runs slower than the normal compile, as bear is doing its magic in the background. If all goes well, compile_commands.json will now exist in the OCPI_ROOT_DIR. Now just open your editor with the compile_commands.json compatible LSP Server installed and you should now have access to all of the LSP features.

I tested this with Neovim and VSCode configurations mentioned above and it worked!

My resulting compile_commands.json file is 119,741 lines long! ccls doesn’t seem to have a problem with this, and is very responsive in Neovim.

One extra thing. Obviously it’d be useful to have LSP working for all the RCC Workers that you care about. You could run your full build with bear (bear -- ./scripts/ --minimal) but this is going to take a while and intercept the prerequisites files too.

Instead you can incrementally recompile just the stuff that you want LSP to work for, and use the --append flag to bear:

source ./cdk/ -r
ocpidev clean -d projects/core/components/file_read.rcc
bear --append -- ocpidev build -d projects/core/components/file_read.rcc --no-doc

This adds to compile_commands.json rather than overwriting it.

If anyone else tries this out and encounters difficulties, feel free to reply to this or direct message me for help :smile: