C/C++ code intelligence#

pw_ide: Code editor and IDE support for Pigweed projects

Pigweed projects have a few characteristics that make it challenging for some IDEs and language servers to support C/C++ code intelligence out of the box:

  • Pigweed projects generally don’t use the default host toolchain.

  • Pigweed projects usually use multiple toolchains for separate targets.

  • Pigweed projects rely on the build system to define the relationship between facades and backends for each target.

These challenges are common to embedded projects in general.

We’ve found that the best solution is to use the clangd language server or alternative language servers that use the same compilation database format and comply with the language server protocol. Usually, your build system generates the compilation database, and we supplement that with Pigweed tools to produce target-specific compilation databases that work well with language servers.

Supporting clangd for embedded projects#

There are three main challenges that often prevent clangd from working out-of-the-box with embedded projects:

  1. Embedded projects cross-compile using alternative toolchains, rather than using the system toolchain. clangd doesn’t know about those toolchains by default.

  2. Embedded projects (particularly Pigweed projects) often have multiple targets that use multiple toolchains. Most build systems that generate compilation databases put all compile commands in a single database, meaning a single file can have multiple, conflicting compile commands. clangd will typically use the first one it finds, which may not be the one you want.

  3. Pigweed projects have build steps that use languages other than C/C++. These steps are not relevant to clangd but some build systems will include them in the compilation database anyway.

To deal with these challenges, pw_ide processes the compilation database(s) you provide, yielding one or more compilation databases that are valid, consistent, and specific to a particular target and toolchain combination. This enables code intelligence and navigation features that accurately reflect a specific build.

After processing a compilation database, pw_ide knows what targets are available and provides tools for selecting which target the language server should use. Then clangd’s configuration is changed to use the compilation database associated with that target.

Implementation details#

Bazel doesn’t have native support for generating compile commands, so it’s common to rely on outside tools. In the Pigweed Visual Studio Code extension, we use the Bazel Compile Commands Extractor.

Invoking GN with the --export-compile-commands flag (e.g. gn gen out --export-compile-commands) will output a compilation database in the out directory along with all of the other GN build outputs (in other words, it produces the same output as gn gen out but additionally produces the compilation database). You can remove the need to explicitly provide the flag by adding this to your .gn file: export_compile_commands = [ ":*" ].

The database that GN produces will have all compile commands for all targets, which is a problem for the reasons described above. It will also include invocations of certain Pigweed Python scripts that are not valid C/C++ compile commands and cause clangd to fault. So these files need to be processed to:

  • Filter out invalid compile commands

  • Separate compile commands into separate databases for each target

  • Resolve relative paths so clangd can find tools in the Pigweed environment

The pw_ide CLI does all of this when you run the sync command.

Enabling the CMAKE_EXPORT_COMPILE_COMMANDS option ensures that compilation databases are produced along with the rest of the CMake build. This can be done either in your project’s CMakeLists.txt (i.e. set(CMAKE_EXPORT_COMPILE_COMMANDS ON)), or by setting the flag when invoking CMake (i.e. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON).

CMake will generate separate compile_commands.json files that are specific to each target in the target’s build output directory. These files are already in the format we need them to be in, so the only thing Pigweed needs to provide is a mechanism for discovering those targets and pointing clangd at the appropriate file.

Target groups#

Running code intelligence on the compilation database of a single target is the most consistent approach, but it’s not always the most useful. For example, application code and test code are usually built in separate targets, but it’s a nuisance to switch code intelligence targets as you move back and forth between application code and test code.

We address this through the use of “target groups”, which in which a compilation database is build by intentionally combining the compile commands from several related targets. Continuing the previous example, you might have a target group that contains both the application code and test code to eliminate the nuisance of repeatedly switching between those targets. The compile commands for the application code would be higher in the list than those for test code, so in a conflict (e.g., a test that uses a different backend from the application code), you will get code intelligence for the application code. This is usually an acceptable trade-off.