Project-Specific clangd Configuration with a Temporary Shell

Lobsters Hottest Tools

Summary

A developer shares a script that creates a temporary shell with a clangd wrapper pointing to a project-specific compilation database, allowing multiple firmware projects in one repository to use separate clangd configurations simultaneously.

<p><a href="https://lobste.rs/s/j04gsk/project_specific_clangd_configuration">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 07/31/26, 02:48 PM

# Felix' Blog - Project-Specific clangd Configuration with a Temporary Shell Source: [https://felix-knorr.net/posts/2026-07-31-lsp-config.html](https://felix-knorr.net/posts/2026-07-31-lsp-config.html) I've been writing C and some C\+\+ in terminal\-based editors for the past five years\. By default, clangd searches the current file's parent directories for a`compile\_commands\.json`\. This becomes annoying when a repository contains several independently built firmware projects that share code and have a common repository root\. Each project needs its own compilation database\. Replacing a single repository\-level`compile\_commands\.json`whenever I switch projects already sucks, but running separate editor instances for different projects breaks the whole thing, because both clangd instances would need the same file to contain different data\. I recently came up with a solution that I like enough \(and found non\-obvious enough\) to justify a short post\. The script below takes a build target as its argument and creates a temporary, target\-specific development environment\. It: 1. Generates the target's`compile\_commands\.json`in a temporary directory\. 2. Creates a`clangd`wrapper in the same directory\. 3. Configures that wrapper to use the generated compilation database and also applies other settings we need\. 4. Starts an interactive Bash session with the temporary directory prepended to`PATH`\. 5. Adds the selected target to the shell prompt as a reminder\. Because the wrapper is named`clangd`and appears first in`PATH`, editors started from this shell launch the correctly configured clangd without needing any project\-specific editor configuration\. The`bashrc`prepends the directory after sourcing my normal`~/\.bashrc`, which also prevents tools such as direnv from accidentally putting another clangd ahead of it during shell initialization\. ``` #!/usr/bin/env bash # Start a shell whose clangd uses the compilation database for one Bob target. set -euo pipefail if (($# != 1)); then echo "usage: $(basename "$0") <bob-target>" >&2 exit 2 fi target=$1 script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P) repo_root=$(cd "$script_dir/.." && pwd -P) firmware_dir=$repo_root/firmware clangd=$repo_root/dev-tools/clangd if [[ ! -d $firmware_dir || ! -x $clangd ]]; then echo "device-env must be located in the repository's dev-tools directory" >&2 exit 1 fi if ! command -v uv >/dev/null; then echo "uv is not on PATH; install it before running device-env" >&2 exit 1 fi tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/device-env.XXXXXX") trap 'rm -rf "$tmpdir"' EXIT ( cd "$firmware_dir" uv run bob ninja-tool -f compdb "$target" ) >"$tmpdir/compile_commands.json" cat >"$tmpdir/clangd" <<EOF #!/usr/bin/env bash exec "$clangd" \\ "--compile-commands-dir=$tmpdir" \\ "--query-driver=**" \\ "\$@" EOF chmod +x "$tmpdir/clangd" cat >"$tmpdir/bashrc" <<'EOF' if [[ -f ~/.bashrc ]]; then source ~/.bashrc fi export PATH="$DEVICE_ENV_TMPDIR:$PATH" PS1="[denv:${DEVICE_ENV_TARGET}] ${PS1}" EOF export DEVICE_ENV_TARGET=$target export DEVICE_ENV_TMPDIR=$tmpdir bash --noprofile --rcfile "$tmpdir/bashrc" -i ``` I can now start a project\-specific shell with: and launch Helix, or any other editor that invokes`clangd`through`PATH`from there\. Each shell gets its own compilation database and clangd process, so multiple projects can be open simultaneously without interfering with one another\. The mechanism is not specific to clangd btw\. A temporary directory containing wrapper executables can be a useful way to provide project\-specific behavior to tools that are normally configured globally or by the editor launching them\.

Similar Articles

Building a C compiler with a team of parallel Claudes

Anthropic Engineering

Anthropic researcher demonstrates using a team of 16 parallel Claude instances to autonomously build a C compiler in Rust capable of compiling the Linux kernel. The article details the architecture, cost, and lessons learned from this multi-agent autonomous coding experiment.

Development shells with Nix: four quick examples

Michael Stapelberg

A tutorial demonstrating four ways to set up development shells using Nix, including interactive one-offs, config files, and hermetic Nix Flakes, using GoCV and OpenCV as an example.