cxxd
is a C/C++ language server which offers rich support for features that aid the process of source code navigation, editing, source code formatting, static analysis etc. One can utilize it, for example, to bring IDE-like features to an editor of your choice.
Feature | Status | cxxd-vim frontend |
---|---|---|
Indexer | ✔️ | ✔️ |
Indexer-diagnostics | ✔️ | ✔️ |
Code-completion | ✔️ | ✔️ |
Semantic-syntax-highlighting | ✔️ | ✔️ |
Find-all-references | ✔️ | ✔️ |
Go-to-definition | ✔️ | ✔️ |
Go-to-include | ✔️ | ✔️ |
Type-hints | ✔️ | ✔️ |
Fixits-and-diagnostics | ✔️ | ✔️ |
Clang-tidy integration | ✔️ | ✔️ |
Clang-format integration | ✔️ | ✔️ |
JSON-compilation-database integration | ✔️ | ✔️ |
Plain-text-compilation-database integration | ✔️ | ✔️ |
Arbitrary build targets integration | ✔️ | ✔️ |
Per-repository cxxd custom configuration (JSON) | ✔️ | ✔️ |
Godbolt-like disassembler | ✔️ | ✔️ |
In essence, the main idea behind it is very much alike to what LSP
offers
and its implementations like clangd
.
Platform | Status | Comments |
---|---|---|
Linux | ✔️ | Main development environment of this project. |
Other platforms | ❌ | Not officially tested but should work wherever python3 and libclang is available |
Editor | Link | Description |
---|---|---|
Vim | cxxd-vim |
A PoC Vim plugin integration that I did for this project and which became my daily driver since then. |
Required: Python3
, libclang
(with Python
bindings)
Optional: clang-format
, clang-tidy
Platform | Install |
---|---|
Fedora |
sudo dnf install python3 clang-devel clang-libs clang-tools-extra && pip install --user clang cxxfilt |
Debian |
sudo apt-get install python3 libclang-dev clang-tidy clang-format && pip install --user clang cxxfilt |
To run cxxd
server against the source code one should provide .cxxd_config.json
configuration file and put it into the root of the project repository.
This file can be used to provide project-specific configurations such as:
Category | Type | Value | Description |
---|---|---|---|
configuration |
cxxd at the bare minimum requires either compile_commands.json or compile_flags.txt to be provided with the project. |
||
. | type |
compilation-database , compile-flags , auto-discovery |
Depending on how do you want to run cxxd server against your project select one of those options. While compilation-database is the most recommended way which should be used for real-world projects, compile-flags is provided only for convenience purposes and should be used only for a very simple and small projects. With auto-discovery option, cxxd will try to figure out itself what type of configuration is project using. If no type is provided then cxxd will fallback to implicit auto-discovery-try-harder mode. |
. | compilation-database |
target : { build-target1:build-dir1, ..., build-targetN:build-dirN} |
Real-world projects will have different build target configurations and target field can be used to define a list of relevant build targets and their accompanying build directories. E.g. compilation-database : { target : { 'debug': 'build/debug', 'relwithdebinfo': 'build/relwithdebinfo', 'debug-asan': 'build/debug-asan'}} |
. | compile-flags |
Same as with compilation-database option. |
Same as with compilation-database option. |
. | auto-discovery |
search-paths: [list of directories] |
Cannot be used for defining arbitrary build targets. Provided only as a quickstart convenience method. Use search-paths field to provide a list of directories where cxxd will look for compile_commands.json or compile_flags.txt . If not provided cxxd will use some of the implicitly predefined search paths. |
project-builder |
To make use of previously defined build-targets from configuration::compilation-database or configuration::compile-flags here we can define the actual build commands to be run by cxxd server. |
||
. | target |
target : { build-target1: { cmd: build-target1-specific-build-cmd }, ..., build-targetN: {cmd: build-targetN-specific-build-cmd} |
Mind that build-target names used here must match the build-target names used in configuration::compilation-database or configuration::compile-flags . Example of build-target commands: target : { 'debug': 'cmake . -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja', 'relwithdebinfo': 'cmake . -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja', 'debug-asan': 'cmake . -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja'}} . You can add any amount of arbitrary build target commands here. |
indexer |
Source-code indexing related settings. | ||
. | exclude-dirs |
A list of directories | Used as a hint to cxxd indexer to exclude certain from being indexed. Commonly these can be directories such as build , cmake , external , third-party etc. |
. | extra-file-extensions |
A list of file extensions | Used as a hint to cxxd indexer to also index files with non-standard C or C++ extensions that your project might be using. cxxd will try hard to implicitly identify most of the non-standard C and C++ extensions found in the wild but in case it doesn't, this is a setting for it. |
clang-format |
Here we can customize how we want to use clang-format for given repository. |
||
. | binary |
path-to-specific-clang-format-binary | Sometimes system-wide installed clang-format version will not match the needs of real-world projects. It can be either too old or too recent. This setting allows to set the specific version of clang-format binary provided that the one exists in the given path. E.g. 'binary': '/opt/clang+llvm-8.0.0-x86_64-linux-gnu/bin/clang-format' . |
. | args |
clang-format specific cmd-line args |
Here we can provide a list of any arguments that we want to pass over to clang-format invocation. For example, applying clang-format immediatelly and in-place following the clang-format configuration hosted by our repository can be done with 'args' : { '-i' : true, '--style' : 'file' } . We can use this list to basically pass any argument that given version of clang-format can recognize and tweak it according to the project-specific needs. |
clang-tidy |
Here we can customize how we want to use clang-tidy for given repository. |
||
. | binary |
path-to-specific-clang-tidy-binary | Sometimes system-wide installed clang-tidy version will not match the needs of real-world projects. It can be either too old or too recent. This setting allows to set the specific version of clang-tidy binary provided that the one exists in the given path. E.g. 'binary': '/opt/clang+llvm-8.0.0-x86_64-linux-gnu/bin/clang-tidy' . |
. | args |
clang-tidy specific cmd-line args |
Here we can provide a list of any arguments that we want to pass over to clang-tidy invocation. For example, to enable bugprone, cppcoreguidelines and readability clang-tidy checks we would do 'args' : { '-checks' : "'-*,bugprone-*,cppcoreguidelines-*,readability-*'", "-extra-arg" : "'-Wno-unknown-warning-option'", '-header-filter' : '.*' } . We can use this list to basically pass any argument that given version of clang-tidy can recognize and tweak it according to the project-specific needs. |
clang |
Here we can optionally set some of the clang -specific settings. Should be needed very rarely. |
||
. | library-file |
path-to-specific-libclang-so-library | When updating your system, sometimes a new version of libclang can introduce bugs or changes in behavior which will result with glitches in the usage experience. Same can happen with the python bindings of libclang . Because cxxd does not have a capacity to be tested against every version of libclang and its python bindings, library-file serves the purpose to tell cxxd to use a certain version of libclang . If not provided, cxxd will by default use the system-wide one, which in most cases should be enough. However, if you suddenly start to experience the issues, which you have not before, this should be a first thing to check. And possibly revert the libclang version to an earlier one. E.g. 'library-file': '/usr/lib64/libclang.so.14.0.5' . |
disassembly |
Godbolt-like utility which allows you to examine the disassembly in selected executable target for a given symbol you requested it for at the source code level. | ||
. | objdump |
||
. | binary |
path-to-specific-objdump-binary | Usually system-wide installed objdump will match the needs but if not, this field can be used to pin to particular version of objdump . E.g. 'binary': '/opt/clang+llvm-8.0.0-x86_64-linux-gnu/bin/objdump' |
. | intermix-with-src-code |
true or false | Default is false. If you want to see source-code mixed within the disassembled binary set this field to true. |
. | visualize-jumps |
true or false | Default is true. If you want to disable ASCII art which visualizes jumps within the disassembled binary set this field to false. |
. | syntax |
intel or att |
Default is intel . Syntax used for instructions in a disassembled binary. |
. | nm |
||
. | binary |
path-to-specific-nm-binary | Usually system-wide installed nm will match the needs but if not, this field can be used to pin to particular version of nm . E.g. 'binary': '/opt/clang+llvm-8.0.0-x86_64-linux-gnu/bin/nm' |
. | targets-filter |
A list of extensions or directories | Prior to disassembly, a target binary needs to be selected. This filter can be used to remove targets that are not interesting. E.g. [".so", ".a", "CMake"] |
Compilation database is a requirement for cxxd
. It can come in two different forms: compile_commands.json
(recommended) or compile_flags.txt
(should be used only as a fallback when generating compile_commands.json
is not possible)
TL;DR in CMake projects you would simply add -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
to your CMake build invocation call. Or you could bake this setting into the CMakeLists.txt
with set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
. And you're done.
CMake
/Bazel
/waf
/Qbs
/ninja
/clang
have native and built-in support for generating this type of compilation database. Here's a good summary on how to use each of those. If you don't use any of those build systems, then same page provides another good summary on how to try to generate the compilation database in alternative ways.
If generating the compile_commands.json
is not possible to achieve within your environment, you can hand-code the build flags yourself and put it into the file named compile_flags.txt
. E.g.
-I./lib
-I./include
-DFEATURE_XX
-DFEATURE_YY
-Wall
-Werror
For completness and quickstart sake here's an example of a config file that I used while hacking on the MySQL database engine at Oracle. You can use it as starting point to create your own.
I used out-of-source builds with plenty of different ways to build and test the code. My regular workflow included running both sanitized (ASAN, UBSAN) and non-sanitized debug
or relwithdebinfo
builds. Sometimes I also had to test or backport the changes from MySQL 8.x to MySQL 5.x so I also had the build targets for that purpose. Later on I even tweaked the build target commands to include the way to run the build in distributed fashion through distcc
and icecream
.
clang-format
version had to strictly adhere to specific version declared to be used by MySQL. Occassionally this version would be bumped so I also occassionally bumped the version in the config file.
Some parts of the MySQL used non-standard file extensions for the source code such as *.ic
, .i
or *.tpp
. To make them visible to the cxxd
indexer I have enumerated them in the extra-file-extensions
field. Similarly, I have hinted the cxxd
indexer not to index 3rd-party source code that is found in extra
directory.
{
"configuration" : {
"type" : "compilation-database",
"compilation-database" : {
"target" : {
"debug" : "../build/trunk/debug",
"debug_asan" : "../build/trunk/debug_asan",
"debug_ubsan" : "../build/trunk/debug_ubsan",
"relwithdebinfo" : "../build/trunk/relwithdebinfo",
"relwithdebinfo_asan" : "../build/trunk/relwithdebinfo_asan",
"relwithdebinfo_ubsan" : "../build/trunk/relwithdebinfo_ubsan",
"debug5" : "../build/5.x/debug",
"relwithdebinfo5" : "../build/5.x/relwithdebinfo",
}
}
},
"indexer" : {
"exclude-dirs" : [
"extra"
],
"extra-file-extensions" : [
".ic",
".i",
".tpp"
]
},
"clang-tidy" : {
"args" : {
"-checks" : "'-*,bugprone-*,cert-*,clang-analyzer-*,-clang-analyzer-osx*,misc-*,performance-*,portability-*,readability-*",
"-extra-arg" : "'-Wno-unknown-warning-option'",
"-header-filter" : ".*"
}
},
"clang-format" : {
"binary" : "/opt/clang+llvm-8.0.0-x86_64-linux-gnu/bin/clang-format",
"args" : {
"-i" : true,
"--style" : "file"
}
},
"project-builder" : {
"target" : {
"debug" : {
"cmd" : "cmake ../../../trunk -GNinja -DCMAKE_BUILD_TYPE=Debug -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
"debug_asan" : {
"cmd" : "cmake ../../../trunk -GNinja -DCMAKE_BUILD_TYPE=Debug -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DWITH_ASAN=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
"debug_ubsan" : {
"cmd" : "cmake ../../../trunk -GNinja -DCMAKE_BUILD_TYPE=Debug -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DWITH_UBSAN=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
"relwithdebinfo" : {
"cmd" : "cmake ../../../trunk -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
"relwithdebinfo_asan" : {
"cmd" : "cmake ../../../trunk -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DWITH_ASAN=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
"relwithdebinfo_ubsan" : {
"cmd" : "cmake ../../../trunk -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DWITH_UBSAN=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
"debug5" : {
"cmd" : "cmake ../../../5.x -GNinja -DCMAKE_BUILD_TYPE=Debug -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
"relwithdebinfo5" : {
"cmd" : "cmake ../../../5.x -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_BOOST=ON -DWITH_BOOST=../ -DWITH_UNIT_TESTS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache && ninja"
},
}
},
"disassembly" : {
"objdump" : {
"intermix-with-src-code" : false,
"visualize-jumps" : true,
"syntax" : "intel"
},
"nm" : {
},
"targets-filter" : [".so", ".a", "CMake"]
}
}
Currently there's only a Vim cxxd-vim plugin that I have developed as a PoC and which I have been using as my daily driver since then. On a semi-regular base I try to keep it up to date with the bugfixes and less often with the new features.
To integrate cxxd
with another editor one should read through the architecture overview. Also, file a ticket if you need help.
Fun fact: this project actually started to grow the whole idea and implementation
before LSP (and clangd) has been put into life. And I am basically hacking on cxxd
and cxxd-vim
codebase since then. But I do it only occassionaly and as much as my spare time allows me to.
It turns out that writing a language server implementation on top of the libclang
backend is a mine field. Complex C and C++ compilation process doesn't make this situation any easier. Variety of build systems available and multitude of different ways on how you can build a model of your code due to arbitary number of build targets also makes it a bigger challenge. And only because I implemented both the language server side and the UI frontend side that accompanies it, and everything from the scratch, it happens that I find it easier (and more fun!) to hack through my own codebase and learning something new rather than waiting on other similar tools to provide the fixes, if at all. I tried many of them and they all experience from same or similar issues I stumble upon as well. YMMV of course.
I also implemented a non-LSP compatible protocol which means that I am free to decide and integrate whatever I find interesting for a development workflow. One such example is support for arbitrary build targets which happens to have an actual impact on how the language server backend will understand your code or will not. Browsing or indexing or code-completion context isn't the same across "debug" or "debug-with-some-fancy-feature" or "relwithdebinfo" targets. For that reason, cxxd
is always started against the specific build target.
To see how it looks like in action you may have a look at the screenshots from cxxd-vim.