Developing Mozilla C++ code with clangd and VSCode

Introduction

I’ve long been a fan of smart editors which have a semantic understanding of the code you’re editing, and leverage it to provide semantics-aware features such as accurate code completion (only offering completions for names that are actually in scope), go-to-definition, find references, semantic highlighting, and others.

When I joined Mozilla six years ago, my choice of editor for C++ code was Eclipse CDT, because based on experience and research, this was the most fully-featured option that was cross-platform and open-source. (Depending on who you ask, Visual Studio, XCode, and CLion have, at various times, been described as matching or exceeding Eclipse CDT in terms of editor capabilities, but the first two of these are single-platform tools, and are three all proprietary.)

This assessment was probably accurate at that time, and probably even for much of the intervening time, but in recent years Eclipse CDT has not aged well. The main reason for this is that Eclipse CDT has its own C++ parser. (For brevity, I’m using “parsing” here as an umbrella term for lexing, preprocessing, parsing, semantic analysis, and all other tasks that need to be performed to build a semantic model of code from source.) C++ is a very complex language to parse, and thus a C++ parser requires a lot of effort to write and maintain. In the early days of CDT, there was a lot of investment, mostly from commercial vendors that packaged CDT-based IDEs, in building and maintaining CDT’s parser, but over time, the level of investment has faded. Meanwhile, the C++ language has been gaining new features at an increasing rate (and the Mozilla codebase adopting them — we’re on the verge of switching to C++17), and CDT’s parser just hasn’t been able to keep up.

Trends in editor tooling have also evolved since CDT’s heyday. Two major themes, in particular, have emerged:

  • Building tooling on top of compiler internals. Since C++ parsers are so expensive to write and maintain, people don’t tend to write new ones these days. Rather, new tooling tends to be built on top of the internals of C++ compilers. The emergence of clang as a modular C++ compiler that was written with tooling uses in mind, has been instrumental to this shift.
  • Decoupling language intelligence from editor front-ends. People’s tastes in editors vary of lot, with some swearing by command-line editors like vim or emacs, and others preferring IDEs like Eclipse or Visual Studio. However, the semantic capabilities that people want (e.g. go-to-definition) are largely the same. So, it makes sense to decouple these, and separate the “language intelligence” that powers semantic features from the editors themselves. Microsoft has been spearheading an effort in this area, called the Language Server Protocol (LSP), which defines an interface between editor front-ends (“language clients”) and components which provide language intelligence (“language servers”).

In accordance with these trends, the new hotness in C++ editor tooling is leveraging the clang compiler’s front-end, and more specifically LSP-based C++ language servers. A number of clang-based C++ language server projects have sprung up over the past couple of years, but in recent months, clangd (short for “clang daemon”) seems to have emerged as the C++ community’s choice. (Among other advantages, clangd lives in-tree in the LLVM codebase, so it gets to use clang’s rich but unstable C++ API without having to constantly deal with API breakages.)

In light of these developments, I’ve been looking at switching my development workflows from using Eclipse CDT, to an editor that uses clangd. To this end, I’ve been following the development of clangd, contributing to it, and trying it out, and I’m happy to report that it has reached a state where I’m productively using as a replacement for CDT. I wouldn’t quite say it has reached feature parity with CDT yet — but I think the respects in which it’s better (such as full C++17 support, and much faster code completion) clearly outweigh the remaining feature gaps.

In the remainder of this article, I will describe how to set up a Mozilla C++ development environment using clangd. As my client, I use Visual Studio Code (VSCode), which is the most mature LSP client out there (and Microsoft’s reference LSP client), but it should be possible to use clangd with any other editor that has LSP client capabilities — including vim, emacs, and Sublime Text (with some caveats, e.g. semantic highlighting is currently a clangd extension rather than an LSP feature proper, so it’s only available in the vscode-clangd client).

Setting up clangd + VSCode for Mozilla development

Install clangd

As clangd is still under active development and bugs are being fixed and improvements made on a daily basis, I recommend using the latest nightly version. For Debian-based Linux distributions, the easiest way to get these is via apt.llvm.org, using the “development branch” packages. As of LLVM 10, clangd now has its own dedicated package (named clangd, possibly with a version suffix like clangd-10); in earlier versions it was in the clang-tools package.

If you’re not inclined to use a nightly build, the next best option is the latest release, which at the time of this writing is clangd-9.

Important note: using clangd nightly does not mean you have to build your code with a corresponding clang nightly. You can continue building your code with any older version of clang, or even with gcc.

After installation, clangd does not require any further setup beyond ensuring that the clangd binary is in your PATH (which with a .deb package should happen by default).

Install VSCode

VSCode can be installed following instructions on its website; it, too, has Linux packages available (both .deb and .rpm). Note that, unlike Visual Studio, VSCode is both cross-platform and free/open-source software.

In addition to VSCode itself, you’ll want to install the vscode-clangd plugin. This plugin tells VSCode to use clangd as the language server for C++, and also implements some LSP extensions that clangd supports, like semantic highlighting.

Note: when you first open a .cpp file in VSCode, it will likely recommend installing Microsoft’s proprietary vscode-cpptools plugin. Do not install this if you plan to use vscode-clangd (or disable it if it’s already installed); if both enabled, the two plugins will step on each other’s toes.

Project setup

The last step is to tell clangd about Mozilla’s project configuration, so it can parse our C++ code properly (with the proper include paths, defines, and other flags). Thankfully, this is fairly straightforward, due to the fact that clangd’s primary means of consuming project configuration info is via a compilation database, and our build system has built-in support for generating one of those.

These are the steps I followed:

  1. If bug 1583999 isn’t fixed yet, apply my patch for it (attached to the bug) locally. This disables unified builds for the purposes of generating a compilation database (only; the actual build will still be unified), because clangd has trouble understanding unified builds.
  2. In your srcdir, run ./mach build-backend -b CompileDB. This will generate a compilation database (compile_commands.json file) in the objdir.
  3. Create .vscode/settings.json in your srcdir, with the following contents:
    {
      "clangd.arguments": [
        "--compile-commands-dir",
        "<path-to-objdir>"
      ],
    }  
        

    This tells clang where to find the compilation database for this workspace.

You’re now ready to launch VSCode, either from the command line via code <srcdir>, or by opening the srcdir as the project folder from the VSCode menu.

At this point, clangd will begin indexing the workspace. Indexing all of mozilla-central can take an hour or more (you can see its progress if you open the Output view and select “Clangd Language Server” from the dropdown). However — importantly — TU-local features like completion, go-to-definition, and semantic highlighting do not use the index, and are available from the start (within a few seconds of opening a .cpp file, which is how long it takes for clangd to build an AST for it). Only cross-TU features like “find references” require the index, so those will become available when indexing is complete. (This is a notable improvement over Eclipse CDT, which required the project to be indexed for almost all semantic features.)

One thing to note is that because we are disabling unified builds for the purpose of generating the compilation database, and because we no longer test non-unified builds in CI, clangd may occasionally show errors that only occur in non-unified builds. These are typically caused by vioaltions of the “include what you use” principle; if you come across them, I suggest taking the opportunity to fix them šŸ™‚

If you’re looking to improve your C++ editor experience, I encourage you to give this a try! You can report any issues you encounter in the clangd issue tracker.

1 thought on “Developing Mozilla C++ code with clangd and VSCode

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s