compilation database - chunhualiao/public-docs GitHub Wiki
Below are lightweight, Bear‑free ways to get a compile_commands.json
from a Makefile‑based C/C++ project. I’ve ordered them by “friction” (fewest moving parts first), and included the exact commands you can paste.
0) If your project already uses CMake under the hood
Some “Makefile projects” are actually driven by CMake (they run make
, but the build files were generated by CMake). If that’s you, this is by far the easiest:
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build -j
# The database will be at: build/compile_commands.json
# (Optionally symlink it into the source tree)
ln -sf build/compile_commands.json .
CMake has first‑class support for this via CMAKE_EXPORT_COMPILE_COMMANDS
. (CMake)
compiledb
(tiny Python tool; no kernel hacks, no LD_PRELOAD)
1) Why: Single small dependency (Python). Works by parsing a Make dry‑run or an actual build log, so it’s usually the least painful option.
Install (recommended via pipx):
pipx install compiledb
# or: python3 -m pip install --user compiledb
Generate without building (best for big trees):
cd /path/to/your/project
compiledb -n make -j8 V=1 VERBOSE=1 Q=
# writes ./compile_commands.json
If your makefile is “quiet” by default, the V=1 VERBOSE=1 Q=
knobs coax it to print real compile lines so compiledb
can see them.
If you need generated headers or code to exist, run the real build through compiledb
:
compiledb make -j8 V=1
compiledb
is designed for GNU Make, supports parsing from stdin (make -Bnwk | compiledb
), and defaults to the preferred “arguments” format. (PyPI)
intercept-build
(from LLVM’s scan‑build; also lightweight)
2) Why: Very common, maintained with LLVM. It wraps your build and records compiler invocations into a compilation database.
Install (no distro hunting needed):
pipx install scan-build
# this gives you: intercept-build, scan-build, intercept-cc, intercept-c++
Generate the database:
cd /path/to/your/project
# Force wrapper mode so it works even if the makefile overrides CC/CXX
intercept-build --override-compiler \
--cdb=compile_commands.json \
make CC=intercept-cc CXX=intercept-c++ -j8
Notes:
- If your build chooses compilers via env vars or nonstandard names, you can point to the real compiler with
--use-cc
/--use-c++
. - If you end up with an empty
[]
, try the--override-compiler
form above or do a clean rebuild so compile steps actually run. (LLVM Git Repositories, PyPI, GitHub)
-MJ
) — zero extra tools if you already use Clang
3) Pure Clang approach (Clang can emit JSON fragments for each compile. You then wrap/merge them into a proper compile_commands.json
.
A. Quick and simple (serial build):
cd /path/to/your/project
rm -f ccdb.raw
make -j1 CC=clang CXX=clang++ \
CFLAGS+=' -MJ ccdb.raw ' \
CXXFLAGS+=' -MJ ccdb.raw '
# Merge the stream of JSON objects into an array:
jq -s . ccdb.raw > compile_commands.json
B. Safer under parallel builds (per‑file fragments): If you can tweak the compile rule (or add a tiny wrapper), write each TU to its own file and merge:
# Example wrapper (put in project root as ./clang-ccdb++)
cat > clang-ccdb++ <<'EOF'
#!/usr/bin/env bash
# Drop JSON next to the object file if -o is present; else use a temp
outjson=".ccdb/$$.json"
mkdir -p .ccdb
for ((i=1;i<=$#;i++)); do
if [ "${!i}" == "-o" && $((i+1)) -le $# ](/chunhualiao/public-docs/wiki/-"${!i}"-==-"-o"-&&-$((i+1))--le-$#-); then
ofile="${!((i+1))}"
base="$(basename "$ofile")"
outjson=".ccdb/${base}.json"
fi
done
exec clang++ -MJ "$outjson" "$@"
EOF
chmod +x clang-ccdb++
# Use the wrapper for one build
make CXX="$PWD/clang-ccdb++" -j8
jq -s . .ccdb/*.json > compile_commands.json
Clang’s docs define the JSON Compilation Database format and describe using -MJ
to produce fragments that you “concatenate … between [
and ]
.” jq -s .
does exactly that. (Clang)
compile_flags.txt
(only for simple projects)
4) As a last resort: Some clang‑based tools (e.g., clangd
) also accept a project‑wide compile_flags.txt
with one flag per line, used for every file. It’s not as accurate as a compilation database, but for very small projects it can be enough:
-std=c++20
-Iinclude
-DMYDEF=1
Place it at your project root. (Clang)
Which should you choose?
Situation | Best pick |
---|---|
You can’t install much & Make prints commands (or can be made verbose) | compiledb -n make |
Your Makefile actually compiles files (not just “no‑op” dry run), or you want the most robust capture | intercept-build |
You already use Clang and want zero extra tools | Clang -MJ (merge with jq ) |
Your project is CMake‑driven | CMake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON |
Practical tips
- Quiet makefiles: add
V=1 VERBOSE=1 Q=
so tools see real commands (shown above). - Where the file goes: most tools look for
compile_commands.json
in the build directory; symlink or copy it to the source root if your editor expects it there. The conventional filename/location is specified by Clang’s docs. (Clang) - Multi‑config or multi‑target workspaces: you can keep multiple databases (e.g.,
build-debug/compile_commands.json
,build-rel/…
) and point your tool at the one you need, or merge them withjq -s 'add'
. (Stack Overflow, richrose.dev)
TL;DR
- Start with
compiledb
(pipx install compiledb; compiledb -n make V=1
). - If that doesn’t capture your build reliably, switch to
intercept-build
(pipx install scan-build; intercept-build --override-compiler make CC=intercept-cc CXX=intercept-c++
). - If you already use Clang, add
-MJ
and merge fragments withjq -s .
.
All three avoid Bear and keep dependencies to an absolute minimum. (PyPI, LLVM Git Repositories, Clang)
If you’d like, tell me how your Makefile sets CC/CXX
and whether it’s “quiet”; I can tailor a one‑liner that works on your tree.