Boost C++ Linting With Clangd: A Practical Guide

by Kenji Nakamura 49 views

Hey everyone! I've been working on a script to streamline clangd linting setup on Linux, and I wanted to share it with you all. For those who aren't familiar, clangd is an incredibly useful Language Server Protocol (LSP) implementation that provides features like code completion, diagnostics, and formatting for C++ in editors like Vim, Emacs, and others. It's a game-changer for code quality and productivity.

The Script: A Deep Dive

This script is currently a bash script, but I'm open to rewriting it in Python if there's enough interest. It's designed to automate the process of setting up clangd for a specific project structure. Let's break down what it does:

  1. Copy compile_commands.json: The script starts by copying the compile_commands.json file from the ./build directory. This file is crucial for clangd as it contains information about how your project's files are compiled, including include paths and compiler flags. Without it, clangd wouldn't know how to properly parse your code.
  2. Copy Docker Headers: Next, the script copies the /ext/include directory from a Docker image to the ./build directory. This step is important if your project relies on headers that are only available within a Docker container, ensuring clangd has access to all the necessary headers for accurate analysis. This ensures that clangd has access to all the necessary headers, even those that might be specific to a Docker environment. This is particularly helpful when working on projects that have dependencies managed within containers.
  3. Reset Include Paths in compile_commands.json: The script then modifies the compile_commands.json file to adjust the include paths. This is often necessary because the paths within the Docker environment might not match the paths on your host machine. By resetting these paths, the script ensures that clangd can correctly locate the header files on your system. This step is crucial for making sure clangd understands the project structure in your local development environment.
  4. Set Include Paths in .clangd: Finally, the script creates or modifies a .clangd file in the project root. This file allows you to specify additional include paths that might be missing from the compile_commands.json file. This is a great way to add project-specific include paths that clangd might not automatically detect, ensuring it has a complete picture of your project's dependencies. The .clangd file acts as a configuration file for clangd, allowing you to fine-tune its behavior.

Here's the script:

#!/bin/bash
set -e
cd "$(dirname \"$0\")" || exit 1

if [ \"$1\" != \"tr1\" ] && [ \"$1\" != \"tr2\" ]; then 
    echo \"You did not specify tr1 or tr2\"
    exit 1
fi

cd build || { echo \"build tr1 or tr2 first\"; exit 1; }
cd \"$1\" || { echo \"build $1 first\"; exit 1; }
cd linux || exit 1

cp compile_commands.json ../../../
cd ../..

if [ ! -d ext/include ]; then
    echo \"Copying /ext/include from Docker image...\"
    mkdir -p ext
    docker create --name tmpcopy rrdash/trx-linux:latest
    docker cp tmpcopy:/ext/include \"$(pwd)/ext/include\"
    docker rm tmpcopy
fi

cd ..

# You have to change /home/noisecode3/TRX for now..

python - <<'PYCODE'
import json
import os

with open(\"compile_commands.json\", \"r\") as f:
    data = json.load(f)

for entry in data:
    if entry[\"directory\"].startswith(\"/app/build/\"):
        entry[\"directory\"] = entry[\"directory\"].replace(
            \"/app/build/\", \"/home/noisecode3/TRX/build/\", 1
        )
    entry[\"command\"] = entry[\"command\"].replace(
        \"-I/ext\", \"-I/home/noisecode3/TRX/build/ext\"
    )
    entry[\"command\"] = entry[\"command\"].replace(
        \"../../../src\", \"/home/noisecode3/TRX/src\"
    )
    entry[\"file\"] = entry[\"file\"].replace(
        \"../../../src\", \"/home/noisecode3/TRX/src\"
    )

with open(\"compile_commands.json\", \"w\") as f:
    json.dump(data, f, indent=2)
PYCODE


FLAGS=\"CompileFlags:
  Add: [-xc, -I$(pwd)/src/tr1/subprojects/libtrx, -I$(pwd)/src/tr1/subprojects/libtrx/include/libtrx, -I$(pwd)/build/ext/include, -I$(pwd)/build/ext/include/SDL2] \""
echo \"$FLAGS\" > .clangd

Key Improvements and Considerations

The script currently looks for dependencies like uthash on the host machine, rather than relying on the Docker headers. This could be improved by incorporating the Docker headers more thoroughly. I'm also considering making the script more robust and user-friendly. Would a rewrite in Python be beneficial? Let me know your thoughts!

One important thing to note is that you'll need to adjust the paths in the Python script to match your specific project structure. Currently, it includes hardcoded paths like /home/noisecode3/TRX, which you'll need to update.

Warning Suppression: A Necessary Evil?

After getting clangd up and running, I've noticed a few warnings that seem to be quite prevalent in our codebase. These include:

  • `W #pragma once â–  No copyright message found. You should have a line: