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)


1) compiledb (tiny Python tool; no kernel hacks, no LD_PRELOAD)

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)


2) intercept-build (from LLVM’s scan‑build; also lightweight)

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)

3) Pure Clang approach (-MJ) — zero extra tools if you already use Clang

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)


4) As a last resort: compile_flags.txt (only for simple projects)

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 with jq -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 with jq -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.