← 返回日报
精读 预计 1 分钟

Project-Specific clangd Configuration with a Temporary Shell

摘要

作者分享了一个解决 clangd 在多项目共享代码库下编译数据库冲突问题的方案:通过脚本为每个构建目标生成独立的 compile commands.json,并创建一个指向该数据库的 clangd 包装器,放入临时目录并置于 PATH 最前,然后启动交互式 shell。从该 shell 启动编辑器(如 Helix)时,编辑器调用的 clangd 会自动使用正确的编译数据库,多个项目可同时打开互不干扰。作者还指出该机制不限于 clangd,可用于其他工具的项目特定配置。

荐读理由

正文给出了可复制的脚本思路:用临时目录 + PATH 前置 wrapper 为每个项目隔离 clangd 配置,解决多项目共享代码库时编译数据库冲突的实际痛点。该模式可迁移到其他工具,属于可直接落地的工程技巧。

原文

Blog Logo

Felix' Blog

Experience Projects About SVG image of RSS feed icon

Blog Logo

Felix' Blog

Experience Projects About

Project-Specific clangd Configuration with a Temporary Shell

2026-07-31

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:

device-env my-target

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.

Lobsters · 1 赞 · 0 评 讨论 → 阅读原文 →

这条对你有帮助吗?