Generate compile_commands.json for any C or C++ build
Bear gives you working code navigation, autocomplete, and diagnostics on
any C or C++ project, whatever the build system. Prefix your build
command with bear -- and Bear produces the compile_commands.json
file that Clang tooling (clangd, clang-tidy, and friends) needs to
understand your code:
bear -- make
Bear captures the compiler invocations while your build runs and writes the JSON compilation database for you. No changes to your build system are required.
When to use Bear
Bear is the tool of choice when the build system cannot produce a compilation database for you:
- Make, autotools, or custom script builds.
- A build you cannot or do not want to modify: a third-party project, a codebase you are just exploring, a CI job.
- Unusual toolchains: embedded, HPC, CUDA, cross-compilers, and compiler launchers such as ccache, distcc, and icecc.
- A build you cannot run at all: Bear can read
make -ndry-run output or a saved build log instead of running the build.
If your project uses CMake or Meson, those tools can export a compilation database directly, and Bazel has it through Hedron’s bazel-compile-commands-extractor. Use that when it is available; Generate compile_commands.json for a CMake project covers those exports and the cases where they are not enough.
Where to go next
- Getting started with Bear - the first successful run, start to finish.
- Install Bear - packages and building from source.
- Recipes - one page per task.
- Troubleshooting - when the database is empty or incomplete.
- How Bear works - the mechanism behind the results.
Looking something up rather than reading? Command-line options and Configure Bear are generated from Bear itself, so they match the version you have; Supported compilers lists every executable Bear recognizes.
Install Bear
Install Bear with your platform’s package manager:
# Debian / Ubuntu
sudo apt install bear
# Fedora
sudo dnf install bear
# Arch Linux
sudo pacman -S bear
# macOS (Homebrew)
brew install bear
# FreeBSD
pkg install bear
Then confirm it is on your PATH:
bear --version
Compare that version against the latest release.
Distribution packages often lag several releases behind, so a problem
you hit may already be fixed upstream; build the current release from
source before reporting a bug. bear --help, or Command-line
options, lists every flag once you have it
installed.
For the full list of distributions that carry a Bear package, see the
Repology page. Repology tracks the version each distribution
ships; if yours lags behind, or does not package Bear at all, build it
from source instead. Bear is not published on crates.io, so cargo install bear will not find it; cargo build --release plus the
install script below is the source-build path.
Build from source
Follow INSTALL.md in the repository for prerequisites (the
Rust toolchain and a C compiler) and the full steps. The short version:
cargo build --release
./scripts/install.sh
scripts/install.sh installs to /usr/local when run as root (for
example under sudo), or $HOME/.local otherwise - a plain, unelevated
run already gives you a working, no-sudo install. Override either
default explicitly with PREFIX:
sudo PREFIX=/usr ./scripts/install.sh
DESTDIR for packaging, separately from PREFIX
scripts/install.sh accepts two different path variables, and they are
not interchangeable. PREFIX is the final, on-system path; the bear
entry script it installs embeds that path literally, so PREFIX must
match where the package actually runs, not where it is being built.
DESTDIR, when set, is a staging root prepended to every install path
on top of PREFIX, for building a package without touching the real
system; it must be an absolute path. Building a distribution package
sets both together:
DESTDIR=/tmp/pkgroot PREFIX=/usr ./scripts/install.sh
INTERCEPT_LIBDIR must match between build and install
On systems that use lib64 or another non-default library directory
name, set INTERCEPT_LIBDIR for both the build and the install step,
so bear-driver finds the preload library where it actually ends up:
INTERCEPT_LIBDIR=lib64 cargo build --release
INTERCEPT_LIBDIR=lib64 ./scripts/install.sh
Let the two commands disagree and Bear still installs without
complaint, but preload interception silently fails at runtime: the
dynamic linker cannot find the library at the path bear-driver
expects, so it skips loading it and lets the build run anyway. Nothing
about that failure is loud - the build succeeds, Bear exits 0, and
the only symptom is a compile_commands.json that came out empty or
suspiciously short, because a missing preload library is not one of the
failures Bear itself reports (see Exit
status). Bear on
Linux
has the matching commands for the common lib64 case.
Platform-specific notes
A distribution package or a source build both give you a working
bear, but a few platforms have their own constraints on how Bear
intercepts a build:
Next steps
Once bear --version works, follow
Getting started with Bear for the first real run,
or jump straight to a recipe for your build system.
Getting started with Bear
By the end of this page you will have built Lua 5.4 under Bear, looked at
the compile_commands.json it wrote, and used that file to run clangd
and clang-tidy against the Lua source, the same way an editor or a CI
lint job would.
Install Bear
Install Bear with your platform’s package manager; see
Install Bear for the commands. Then confirm it is on
your PATH:
bear --version
Get a real project
Lua 5.4 is a good first target: a plain Makefile, no configure step, no
dependencies beyond a C compiler and make. Download and extract it:
curl -fsSL -O https://www.lua.org/ftp/lua-5.4.8.tar.gz
tar xzf lua-5.4.8.tar.gz
cd lua-5.4.8
lua.org’s ftp directory does not change existing releases, so this
download always matches this checksum:
$ sha256sum lua-5.4.8.tar.gz
4f18ddae154e793e46eeab727c59ef1c0c0c2b744e7b94219710d76f530629ae lua-5.4.8.tar.gz
Build it under Bear
bear -- make
Bear runs the build exactly as make would on its own, forwarding its
output, and adds one thing: a compile_commands.json file in the
current directory, one entry per compiled source file. Lua’s Makefile
recurses into src, so this produces 34 entries, one for each .c file
that went into lua and luac.
Look at the result
cat compile_commands.json
Each entry looks like this:
{
"file": "lapi.c",
"arguments": [
"/usr/bin/gcc",
"-std=gnu99",
"-O2",
"-Wall",
"-Wextra",
"-DLUA_COMPAT_5_3",
"-DLUA_USE_LINUX",
"-c",
"-o",
"lapi.o",
"lapi.c"
],
"directory": "/home/you/lua-5.4.8/src",
"output": "lapi.o"
}
directory is where the compiler ran, file is the source it compiled
(relative to directory), arguments is the exact command as an array,
and output is the object file it produced. Your own output will show
your own resolved compiler path in arguments[0]; on a distribution
that routes compilers through ccache, that path may be a ccache
symlink instead of the compiler itself. There is one entry per compiled
source and none for the headers Lua’s sources include, such as
lapi.h, because Bear only records commands the build actually ran,
and the build never invokes the compiler on a header directly. For what
Bear does and does not capture, and why, see
How Bear works.
Use the database: clangd
compile_commands.json exists for tools like clangd to read, not to be
read by people. Point clangd at one Lua source file and it logs the
exact command it pulled from the database:
$ clangd --check=src/lapi.c
I[...] Compile command from CDB is: [/home/you/lua-5.4.8/src] /usr/bin/gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_3 -DLUA_USE_LINUX -c -o lapi.o -resource-dir=... -- /home/you/lua-5.4.8/src/lapi.c
That is the whole hand-off: an editor with clangd support opened on this directory gets working “go to definition”, completion, and diagnostics with no other setup. See Set up clangd for a project without CMake for the editor side.
Use the database: clang-tidy
clang-tidy reads the same file to know how to parse each source it lints. Run it against one Lua source file, restricted to a single check so the output stays short:
$ clang-tidy -p . -checks='-*,clang-analyzer-deadcode.DeadStores' src/lfunc.c
1 warning generated.
lfunc.c:196:42: warning: Although the value stored to 'upl' is used in the enclosing expression, the value is never actually read from 'upl' [clang-analyzer-deadcode.DeadStores]
196 | while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) {
| ^
That is a real finding in Lua’s own source, produced by clang-tidy, not
by Bear: Bear’s job ends at writing compile_commands.json, and every
tool built on Clang’s tooling API, from editors to linters to
refactoring tools, takes it from there.
Next steps
- Generate compile_commands.json for a Makefile project for variations on what you just did: autotools, incremental and parallel builds, recursive Makefiles.
- Set up clangd for a project without CMake if the editor side needs more than the default lookup.
- Recover compile_commands.json from a build log if you cannot run the build yourself, only read its log.
- Bear produces an empty
compile_commands.jsonif a real project’s database comes out empty, and Troubleshooting if it comes out wrong. - How Bear works for the interception mechanism behind this page.
Build an autotools project under Bear
By the end of this page you will have built libtasn1 4.20.0 twice: once the ordinary way, once under Bear, and compared what each run leaves behind. This page assumes Bear is already installed; see Getting started with Bear for a first run against a plain Makefile project, or Install Bear for platform packages.
Get a real project
libtasn1 is a small GNU library built with automake and autoconf, a
common shape for the C and C++ projects Bear targets. Its release
tarball ships a generated configure script, so building it needs
nothing beyond a C compiler and make, no autoconf or automake
installed. Download and extract it:
curl -fsSL -O https://ftp.gnu.org/gnu/libtasn1/libtasn1-4.20.0.tar.gz
tar xzf libtasn1-4.20.0.tar.gz
cd libtasn1-4.20.0
GNU’s release tarballs do not change after release, so this download always matches this checksum:
$ sha256sum libtasn1-4.20.0.tar.gz
92e0e3bd4c02d4aeee76036b2ddd83f0c732ba4cda5cb71d583272b23587a76c libtasn1-4.20.0.tar.gz
Build it the ordinary way
./configure
make
This builds libtasn1.so under lib/.libs/, the same as it would for
anyone packaging or installing this library. Look for a compilation
database and there is none:
$ ls compile_commands.json
ls: cannot access 'compile_commands.json': No such file or directory
Nothing here was written to produce one; configure and make have no
notion of a compilation database. That is the gap Bear fills.
Build it under Bear
Start from a clean tree and repeat the same two commands, this time
running make under Bear:
make distclean
./configure
bear -- make
./configure is exactly the command from the previous section, run the
same way, outside Bear. Only make runs under Bear. Preload
interception, the default on Linux, watches every process the build
spawns, so it does not matter that configure ran unsupervised earlier:
Bear only needs to be watching for the commands it must record, and
those are the compiler invocations make issues. “Build it again, in
wrapper mode” below builds this same project a second time under the
other interception method, where the configure step does need to run
under Bear.
Look at the result
$ grep -c '"file":' compile_commands.json
34
libtasn1’s Makefile.am recurses into six subdirectories, and each one
contributes its own entries: lib (9), lib/gl (3), src (4),
src/gl (14), examples (3), fuzz (1). If you get fewer, check
whether make stopped early: this release also builds its Texinfo
manual, which needs makeinfo from the texinfo package, and a failure
there ends the recursion before the last directories are reached. That
is worth seeing once, because it is the general case with interception:
Bear records what the build got to, so a build that stops early gives
you a database that stops with it. A recursive automake build
like this compiles in several directories at once, and gnulib’s bundled
replacement sources under the two gl directories are compiled with
their own flags, separate from the library’s.
Here is one entry from lib:
{
"file": "ASN1.c",
"arguments": [
"/usr/bin/gcc",
"-DHAVE_CONFIG_H",
"-I.",
"-I..",
"-I./gl",
"-I./includes",
"-DASN1_BUILDING",
"-fanalyzer",
"-Wall",
"-Wextra",
"-Wshadow",
"-Wwrite-strings",
"...",
"-g",
"-O2",
"-c",
"ASN1.c",
"-fPIC",
"-o",
".libs/ASN1.o"
],
"directory": "/home/you/libtasn1-4.20.0/lib",
"output": ".libs/ASN1.o"
}
(the real entry lists several dozen more -W flags; they are elided
above with ...)
And one from src/gl:
{
"file": "cloexec.c",
"arguments": [
"/usr/bin/gcc",
"-DHAVE_CONFIG_H",
"-I.",
"-I../..",
"-Wno-cast-qual",
"-Wno-conversion",
"-Wno-sign-compare",
"-Wno-unused-function",
"-Wno-unused-parameter",
"-g",
"-O2",
"-c",
"cloexec.c",
"-fPIC",
"-o",
".libs/libsgl_la-cloexec.o"
],
"directory": "/home/you/libtasn1-4.20.0/src/gl",
"output": ".libs/libsgl_la-cloexec.o"
}
lib turns on a wall of analyzer and style warnings for libtasn1’s own
code; src/gl turns most of them back off, because that directory holds
gnulib’s imported replacement sources, not code the project wants held
to its own warning level. One database, one directory per entry,
describes the whole recursive build: a tool reading it gets the flags
that actually compiled each file, instead of one guessed command applied
everywhere.
Use the database: clangd
$ clangd --check=lib/ASN1.c
I[...] Compile command from CDB is: [/home/you/libtasn1-4.20.0/lib] /usr/bin/gcc -DHAVE_CONFIG_H -I. -I.. -I./gl -I./includes -DASN1_BUILDING -fanalyzer -Wall -Wextra ... -g -O2 -c -fPIC -o .libs/ASN1.o -resource-dir=... -- /home/you/libtasn1-4.20.0/lib/ASN1.c
clangd pulled the same flags straight out of compile_commands.json,
directory and all. See Set up clangd for a project without
CMake for the editor side of this hand-off.
Build it again, in wrapper mode
Preload interception, used above, watches every process the build
spawns, so it never mattered that ./configure ran outside Bear: the
commands Bear needed to see were make’s. Wrapper mode works
differently: it puts wrapper executables on PATH in place of the real
compilers, so a step that discovers and records the compiler has to see
the wrapper while it does so, or the build later invokes the real
compiler directly and Bear observes nothing. For libtasn1, that step is
./configure. See How Bear works for the mechanism
behind both interception methods.
That makes this project’s configure step the one place on this site
where running under Bear is required, not just harmless. Write a
bear.yml next to the source tree:
schema: "4.2"
intercept:
mode: wrapper
Bear finds bear.yml in the current working directory on its own, so no
flag is needed; naming it explicitly with --config bear.yml works the
same way. This same file selects wrapper mode as the interception method
on every platform, including Linux and the BSDs, where preload is
otherwise the default. Configure Bear covers the
rest of the file’s keys and the other places Bear looks for it.
From a clean tree, run configure and make as two separate Bear
invocations, in order:
make distclean
bear --config bear.yml -- ./configure
bear --config bear.yml -- make
The first command writes a database of its own: six entries, all of them
configure’s own throwaway compiler probes (conftest.c and similar,
plus a libtool probe under a temporary directory), not libtasn1’s
sources. The second command writes the same 34 entries, across the same
six directories, as the preload build above, with no conftest entry
among them. Bear overwrites the output file on every run by default, so
the second command’s database simply replaces the first’s; the probes
are not merged in and do not need to be filtered out. Do not add
--append to either command here, since that flag places new entries
alongside the old ones instead of replacing them, which would let the
probes survive into the final database.
Next steps
- Generate compile_commands.json for a Makefile
project for the autotools
variations this page skipped: the general
conftest.cprobe caveat, incremental and parallel builds. - Recover compile_commands.json from a build log if you cannot run the build yourself, only read its log.
- How Bear works for the interception mechanism behind every build on this page.
Recover compile_commands.json from a build log
By the end of this page you will have turned a plain text build log,
with no live build behind it, into a working compile_commands.json,
using bear parse-sh instead of running anything.
This is for the build you cannot run again: a CI job that already finished and left its log in an artifact, a machine you no longer have access to, or a build that takes an hour when you only need the compiler flags out of it. Getting started with Bear covers the other path, running a build under Bear as it happens; this page covers reconstructing a database from text alone, after the fact.
Get a build to reconstruct
Download Lua 5.4.8, a small, dependency-free C project, and extract it:
curl -LO https://www.lua.org/ftp/lua-5.4.8.tar.gz
tar xzf lua-5.4.8.tar.gz
cd lua-5.4.8
Capture a dry run
Ask make to print the commands it would run, without running them,
and save that text to a file:
make -nw > build.log
-n is the dry run: make prints each recipe line instead of
executing it. -w makes make also print Entering directory and
Leaving directory lines around every directory it works in. Lua’s
top-level Makefile hands the real build off to a make invocation in
src/; that inner make already announces its own directory by
default, and -w additionally names the top-level one, so the log
names every directory the build touched, not just the ones recursion
would have announced anyway.
Turn the log into a database
$ bear parse-sh -i build.log
bear: warning: line 4: skipped (command substitution)
bear: warning: line 5: skipped (command substitution)
bear: warning: parse-sh: 40 command(s) parsed, 2 line(s) skipped
Those two warnings point at Lua’s own platform-detection recipe, which
runs echo Guessing `uname` and make `uname` to pick a
default target: the backticks are shell command substitution, outside
the subset parse-sh understands, so it skips both lines and says why.
Neither line is a compiler invocation, so nothing is missing from the
result; a skip only costs you an entry when the skipped line was one.
The run still exits 0, because most of the log did parse.
compile_commands.json now holds one entry per source file make
would have compiled:
$ grep -c '"file"' compile_commands.json
34
and each entry looks like this:
{
"directory": "/home/you/lua-5.4.8/src",
"file": "lapi.c",
"arguments": [
"gcc",
"-std=gnu99",
"-O2",
"-Wall",
"-Wextra",
"-DLUA_COMPAT_5_3",
"-DLUA_USE_LINUX",
"-c",
"-o",
"lapi.o",
"lapi.c"
],
"output": "lapi.o"
}
directory is src, not the checkout root: parse-sh followed the
cd src in the top-level recipe and the Entering directory lines
-w added, the same way it would for a real recursive build.
The same log, read somewhere else
The scenario this page is really for is a log you did not just
produce: someone hands you build.log from a build that ran elsewhere,
or ran an hour ago on a machine that is gone now. Copy it somewhere
that has never seen the Lua checkout, and parse it from there:
$ mkdir ~/handed-a-log
$ cp lua-5.4.8/build.log ~/handed-a-log/
$ cd ~/handed-a-log
$ bear parse-sh -i build.log
$ grep -m1 '"directory"' compile_commands.json
"directory": "/home/you/lua-5.4.8/src",
Same 34 entries, same directory, even though ~/handed-a-log has no
lua-5.4.8 anywhere near it. parse-sh gets directory from the cd
and Entering directory text inside the log itself, never from where
you happen to run it; that is what makes a saved log portable. Capture
with -nw as a habit: it costs nothing and guarantees the log names
its own build root, including for a Makefile whose top level compiles
directly instead of only delegating, which the
Makefile recipe covers in
more detail.
Limits of reconstructing from text
parse-sh reconstructs commands from what a build system chose to
print; it never observes anything running. That makes it lower fidelity
than intercepting a real build, in specific, permanent ways:
-
A dry run can omit commands entirely. Recursive
makedoes not always propagate-nto sub-makes, and a command that compiles a not-yet-generated source never gets printed in the first place.The sharpest case of this is a build that produces a tool and then consults that tool to decide what to compile, for example
SRCS := $(shell ./tool)in a makefile, or an equivalent generated-flags step.make -nonly prints the recipe that builds the tool; it never runs it, so$(shell ./tool)has nothing to run and the compiles that depend on its output never reach the log at all. On a project shaped this way,bear -- makerecords every compile, the tool’s own included;make -npiped intobear parse-shrecords only the tool’s compile - silently, with no warning, and exit status 0. A generated makefile that isincluded, by contrast, is not affected: GNU make remakes an included makefile for real even under-n, so a source list coming from an included.mkfile survives the dry run. It is specifically the$(shell ...)-at-parse-time shape that goes missing. Nothing signals the gap, so notice it by comparing the entry count against what you expect for the project; when you find one, run the actual build under Bear (bear -- make) if it can be run at all. -
A bare executable name in the log, like
gccabove, resolves against the environment andPATHyou runparse-shin, not the original build’s. If the build used a different compiler onPATH, the database will point at yours instead. -
Only a documented subset of POSIX shell is understood: word splitting and quoting, the
;,&&,||,&, and|separators, comments, redirections,cd, brace groups, and make’s directory markers. Subshells, command substitution (what tripped the two lines above), parameter expansion, globs in the executable position, here-documents, and shell keywords such asiforforare all outside it and get skipped, loudly, rather than guessed at. Seebear parse-shin thebear(1)man page for the exact list.
Prefer bear -- make (see Getting started with
Bear and the Makefile
recipe) whenever you can
actually run the build: it observes the real exec() calls, so it
cannot miss a command or resolve a compiler name wrong. Reach for
parse-sh only when running the build again is not an option.
Related pages
-
Getting started with Bear for the tutorial that builds a project under Bear directly, instead of from a log.
-
Generate compile_commands.json for a Makefile project for recursive builds, incremental builds, and the rest of the Make-specific detail.
-
Bear produces an empty compile_commands.json if a database, from either path, comes out shorter than expected.
-
Recipes for the rest of the task pages.
Recipes
One page per task. Each recipe answers a single question and opens with the command that answers it.
- Generate compile_commands.json for a Makefile project
- the main recipe: plain Make, autotools, incremental and parallel builds, recursive Makefiles, and recovering a database without running the build.
- Generate compile_commands.json for a CMake project - when CMake’s own export is enough, and when it is not.
- Bear produces an empty compile_commands.json
- what to check when the file comes out empty or short.
- Set up clangd for a project without CMake - point your editor at the database once Bear has written it.
- clangd has no compile command for a header - when to let clangd infer one, and when to have Bear synthesize it.
- Use Bear with ccache, distcc, or icecc - keep a compiler launcher in the build while Bear still records the real compiler invocation.
- Run Bear inside a Docker container - capture a build that runs inside a container.
Toolchains
- Generate compile_commands.json when cross-compiling - a target-prefixed GCC or Clang.
- Generate compile_commands.json for an STM32 or arm-none-eabi project - the bare-metal Arm GCC toolchain.
- Use Bear with a vendor embedded toolchain - QNX, Microchip XC8 / MPLAB X, and Texas Instruments.
- Generate compile_commands.json for an Emscripten project -
compiling to WebAssembly with
emcc/em++. - Generate compile_commands.json for a CUDA project -
nvccalongside a host compiler. - Use Bear with Intel oneAPI compilers - the oneAPI and Classic Intel C/C++/Fortran drivers.
- Use Bear with Cray and NVIDIA HPC compilers - HPE Cray’s and NVIDIA’s HPC SDK drivers.
- Generate compile_commands.json for an MPI project - the
mpicc-style wrappers.
Related pages: Getting started with Bear for the first run, Troubleshooting for a database that came out wrong, and How Bear works for the mechanism.
Generate compile_commands.json for a Makefile project
Run this from the directory holding the Makefile:
bear -- make
Bear runs make exactly as it would run on its own, watches every
command it executes, and writes compile_commands.json next to it, one
entry per compiled source file. Nothing about the Makefile changes.
Make has no built-in way to export a compilation database (unlike CMake
or Meson, which do; see the home page if that is your
build system instead), so intercepting the real build is the standard
way to get one for a Make project. For the full walkthrough of a first
run, including what the output looks like, see Getting started with
Bear; this page covers everything beyond that
first run.
Autotools: ./configure and make
Run configure on its own, then build under Bear:
./configure
bear -- make
This is enough whenever preload is the active interception method:
configure only needs to run so it can write a Makefile, and make is
the command Bear needs to watch.
In wrapper mode a build step that discovers the compiler on its own has to run under Bear too, or it never sees the wrapper. See Configure Bear for which mode is the default on your platform and how to force one or the other. Practically, the wrapper-mode requirement means running the configure step under Bear as well, and discarding its output:
bear -- ./configure
make clean
bear -- make
Do not combine ./configure and make into a single Bear run (for
example bear -- sh -c './configure && make', or a Makefile target that
re-runs configure itself): configure’s throwaway toolchain probes
(conftest.c and similar) end up recorded alongside the real build. Run
configure and make as two separate Bear invocations instead, as
above; the second run’s output overwrites the first’s, so nothing from
the probe phase survives. See
Troubleshooting for
the mechanics, and for excluding probe files by pattern instead, if you
would rather keep one combined run.
Incremental builds
Make only rebuilds what is out of date, and Bear only records commands
the build actually executes, so a make with nothing to do records
nothing: running bear -- make a second time in a row against an
unchanged tree overwrites compile_commands.json with an empty [].
Build from clean when you want a complete database:
make clean
bear -- make
To fold in just the parts you rebuild, add --append. It places new
entries before the existing ones in the output file, so accumulate the
database as you touch different parts of the project instead of
rebuilding everything at once:
bear --append -- make main.o
bear --append -- make greet.o
After the first call the database holds one entry (main.c); after the
second it holds two, with the newest entry first. On the first call
there is nothing to append to, so Bear warns and ignores the option. If
a file is later rebuilt with different flags, its newest entry wins by
default; see Configure Bear
for the matching rule, and Bear produces an empty
compile_commands.json if entries still
seem to go missing.
Parallel builds
bear -- make -j4 needs nothing special: every compiler process, however
many run concurrently, reports back to Bear on its own. A parallel build
produces the same entries as the serial one.
Recursive Makefiles
A real build under Bear needs no extra flags for make -C or $(MAKE)
recursion: each intercepted command carries its own working directory,
so entries come out correctly scoped to the subdirectory they were
compiled in, however deep the recursion goes. In a project whose
top-level Makefile recurses into a libgreet/ subdirectory, the
database holds main.c with directory set to the project root and
greet.c with directory set to libgreet, with no
-w/--print-directory needed.
The -w flag matters only for the dry-run path below, not for a real
build.
Bear itself, not make, decides where compile_commands.json is
written: it always lands in the directory Bear was started from, even
when the Makefile it drives is elsewhere. Running bear -- make -C libgreet from inside libgreet/ writes the file there; running the
same command from the project root writes it at the root instead.
A build you cannot re-run
When you cannot run the build again, for example to recover a database
from a CI log after the fact, feed a dry run into bear parse-sh
instead of running anything:
make -n | bear parse-sh
For a recursive build add -w, so the top-level make also prints the
Entering directory markers that sub-makes already print by default;
parse-sh uses them to resolve each command to the right directory:
make -nw | bear parse-sh
Parsing a plain make -n for a recursive project from a directory other
than the build root puts the top-level source’s directory at the
parsing directory rather than the build root; make -nw from the same
place resolves it correctly. Without -w, the
top-level make prints no directory marker of its own, so parse-sh
falls back to its own working directory (or --directory, if given) for
commands issued before any marker appears.
This path is reconstruction from text, not observation, and is lower
fidelity than a real build: a dry run can omit commands entirely
(recursive make does not always propagate -n to sub-makes, and
commands behind not-yet-generated sources never print), the environment
and PATH used to resolve bare executable names is the parsing one, not
the real build’s, and only a documented subset of POSIX shell syntax is
understood (subshells, command substitution, and here-documents are not,
among others; an unparsed line is skipped and reported on standard error
with its line number). Prefer bear -- make whenever you can actually
run the build; reach for parse-sh only when you cannot. See bear parse-sh in the bear(1) man page for the full list of what
it understands.
Where the output goes, and how to change it
By default Bear writes compile_commands.json in the directory it was
started from. Use -o/--output (see Command-line
options) to write it
somewhere else:
bear -o build/compile_commands.json -- make
- (standard output) is not accepted for -o in combined mode, since
standard output belongs to the build; see the bear(1)
man page for the split intercept/semantic modes, which do
accept it.
Related pages
-
Bear produces an empty compile_commands.json when even a clean build gives you nothing.
-
Set up clangd for a project without CMake for pointing your editor at the file this page produces.
-
Use Bear with ccache, distcc, or icecc if your Makefile invokes the compiler through a launcher.
-
Run Bear inside a Docker container if the build itself runs in a container.
-
How Bear works for the interception mechanism behind everything on this page.
-
Command-line options and Configure Bear for the flags and keys named above.
-
Recipes for the rest of the task pages.
Generate compile_commands.json for a CMake project
CMake writes compile_commands.json itself; turn that on before reaching
for Bear:
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build
CMake places the file in the build directory, the one passed to -B
(build above), not the source tree. Setting the same variable through a
CMakePresets.json cacheVariables entry, instead of the -D flag on
the command line, has the same effect. Point your editor at the file
this produces with Set up clangd for a project without
CMake, which covers --compile-commands-dir and the
.clangd CompilationDatabase key.
This is the better answer for the ordinary CMake project, and the rest of this page is about the cases where it is not enough.
Generators that ignore the variable
CMake’s own documentation for CMAKE_EXPORT_COMPILE_COMMANDS states:
“This option is implemented only by Makefile Generators and Ninja
Generators. It is ignored on other generators.” The Visual Studio and
Xcode generators fall into “other generators”: setting the variable with
either of those has no effect, and no compile_commands.json appears no
matter how the project builds. Reach for Bear instead, pointed at
whatever actually invokes the compiler (see Command-line
options for the -- split between
Bear’s own flags and the build command):
bear -- cmake --build build
This works regardless of which generator produced the build files; see How Bear works for why watching the build succeeds where the generator’s own export does not.
When the export does not match what actually ran
CMake’s export records the command it intends to run, generated at configure time. Three situations make that different from what the build actually executes:
- a compiler launcher in front of the compiler
(
CMAKE_C_COMPILER_LAUNCHER,CMAKE_CXX_COMPILER_LAUNCHER, or accache/distccsetup baked into the toolchain file); - a wrapper script standing in for the compiler;
- a custom command or custom target that compiles something outside CMake’s own compile rules.
Interception records what the build actually ran instead of what CMake planned:
bear -- cmake --build build
See Use Bear with ccache, distcc, or icecc for the launcher case in detail.
A superbuild, or CMake as one step of a larger build
When CMake is only part of the build - a superbuild that configures and
builds several subprojects, or a top-level Makefile that drives cmake
alongside other steps - CMake’s own export only ever covers the CMake
part. Run the whole thing under Bear instead, and every compiler
invocation ends up in the same database regardless of which step
produced it:
bear -- make
or, when the top-level driver is itself CMake:
bear -- cmake --build .
If the project does not use CMake
CMake is not the only build system that writes compile_commands.json
itself. Check whether yours already does before reaching for Bear:
- Ninja:
ninja -t compdb > compile_commands.json - Qbs:
qbs generate --generator clangdb - Waf: load the
clang_compilation_databasetool fromwscript, then runwaf clangdb - Bazel: no native flag, but Hedron’s Compile Commands Extractor does the same job as a third-party target
- Clang itself: pass
-MJ <fragment>.jsonto every invocation and concatenate the fragments into a single JSON array
Use your build system’s own export when it has one. Reach for Bear when it does not, or when what it exports does not match what actually ran.
clangd cannot find the file CMake wrote
compile_commands.json living in the build directory rather than the
project root is the usual reason clangd does not pick it up
automatically. See Set up clangd for a project without
CMake for how clangd searches and for pointing it at
the exact directory, and Bear produces an empty
compile_commands.json if the file exists
but is missing entries rather than missing entirely.
Related: Generate compile_commands.json for a Makefile project for the equivalent Bear-only workflow when there is no CMake export to fall back on, and the Recipes index.
Bear produces an empty compile_commands.json
The most common cause is that the build compiled nothing: an incremental build with everything already up to date runs no compiler at all, and Bear only records commands the build actually executes. Rebuild from clean:
make clean
bear -- make
If a clean rebuild still leaves [], work through the other causes
below, most common first. Each one shows the command that confirms it
and the command that fixes it.
The build had nothing to compile
An incremental build that finds every target up to date runs no compiler, so there is nothing for Bear to intercept. This is not a bug: Bear observes execution, it does not read your build files to guess what “should” happen.
Confirm: check whether the build itself said it did nothing, for example make’s
make: Nothing to be done for 'all'.
Fix: clean first, or build into an empty directory, as shown above.
When you only rebuilt part of a larger project and want to keep the rest
of a previous run’s entries instead of doing a full clean, use
--append (see Command-line options
for its exact semantics, and Troubleshooting
for the duplicate-handling rules that apply when you do).
The compiler is not recognized
Bear decides whether a command is a compilation by matching the executable’s filename against a table of known names; a name outside that table produces no entry, even though the compiler ran and built your code successfully.
Confirm: check whether your compiler’s name is one Bear knows, on Supported compilers, or ask the installed build directly:
bear semantic --print-compilers
A custom or renamed executable will not appear here. The Diagnostics section below shows a more precise way to confirm the exact command that went unrecognized.
Fix: add a compilers: hint mapping the executable’s path to the
family it behaves like:
schema: "4.2"
compilers:
- path: /usr/local/bin/my-custom-cc
as: gcc
A Makefile rule invoking ./my-totally-custom-cc, itself a plain copy
of gcc, produces [] on its own, and a working entry once the hint
above names its path. The
accepted as: values are listed on Supported
compilers.
Preload interception is blocked or unavailable
Bear has two interception methods, chosen once per invocation: preload
injects a library into every process the build starts; wrapper
substitutes the known compilers on PATH instead. See Configure
Bear for which one is the
default on your platform and how to force the other, and How Bear
works for the mechanism. A few
situations keep preload from seeing a compilation at all:
A statically linked build tool. Preload works by hooking library calls inside the process that makes them; a statically linked executable never loads the dynamic linker, so no hook is ever installed in it, and any compiler it execs directly is invisible to Bear. This is a fundamental limit of the approach, not a bug.
Confirm with:
file "$(command -v your-build-tool)"
A report of “statically linked” (rather than “dynamically linked”)
means preload cannot see what that tool executes. A statically linked
launcher that directly execs a compiler produces [] under the
default (preload) mode on Linux, and a correct entry once wrapper mode
is forced:
schema: "4.2"
intercept:
mode: wrapper
macOS with System Integrity Protection enabled. SIP strips the
preload environment variable from protected executables before it can
take effect, so preload cannot run there; it is wrapper mode’s reason
for being the macOS default in the first place. See Bear on
macOS for the detail. Forcing preload mode
where it is unavailable (SIP-protected macOS, or Windows, which has no
preload library at all) is a startup error naming wrapper mode as the
alternative - Bear does not silently fall back, and the build does not
run, so this case shows up as an explicit error rather than an empty
compile_commands.json.
Running against a container from the outside. bear -- docker exec ... on the host does not work: the daemon runs the command inside the
container’s own process tree, which the host-side Bear process never
observes, so the database comes out empty with no other symptom. Bear
must run inside the container as part of the build. See Run Bear
inside a Docker container for the correct invocation.
In every one of these cases, wrapper mode is the general fallback, with one condition of its own: the build has to actually pick the wrapper up, covered below.
The preload library failed to load
A missing or broken libexec.so install is not a failure the dynamic
linker treats as fatal: it drops a preload target it cannot open, prints
a warning on standard error, and lets the build run anyway. Only the
top-level command Bear launched is observed directly; everything that
command itself spawns runs unwatched, so a build whose compiler is
invoked through make or a shell script comes back with an empty
database, and Bear still exits 0 because the build succeeded. See
Exit status for why success and an
empty database are not mutually exclusive.
Confirm: look for this line in the build’s own standard error, not Bear’s own log output:
ERROR: ld.so: object '.../libexec.so' from LD_PRELOAD cannot be preloaded: ignored.
Fix: see Troubleshooting for the install-path check.
Wrapper mode: the build never picks up the wrapper
Wrapper mode works by putting reporting executables ahead of the real
compilers on PATH. A build step that discovers the compiler on its
own, before the wrapper directory is in place, bakes in the real
compiler’s path directly, and every later invocation goes straight to
the real compiler with no wrapper in between: a ./configure script run
outside Bear, or a CMake configure step that resolves cc to an
absolute path, are the common versions of this.
Confirm: look for the real compiler’s absolute path already baked
into a generated build file (config.mk, CMakeCache.txt, or similar)
from before the build ran under Bear.
Fix: run the configure step under Bear too, discarding its output, then run the real build under Bear:
bear --config bear.yml -- ./configure
bear --config bear.yml -- make
A configure.sh that resolves cc to an absolute path and writes it
into a makefile fragment produces [] when only the subsequent make
runs under Bear in wrapper mode. Running configure.sh under Bear as
well, with the same config and discarding that run’s own output, makes
the following build’s database come out populated.
This matches the two-step CMake example in the bear(1) man
page’s EXAMPLES section.
The build failed before it reached a compiler
Bear records the compiler invocations that actually happen; if the build fails before starting any of them - a missing include in a Makefile, a dependency that does not exist, a configure step that aborts - there is nothing to record.
Note the distinction: a compile that fails after the compiler was invoked (a syntax error in the source) still produces an entry, because Bear records the attempted command, not its outcome. An empty database from a failed build specifically means the failure happened before any compiler ran at all.
Confirm: check the build’s own error output and Bear’s exit code (see Exit status); a non-zero exit with no compiler messages at all points here.
Fix: fix whatever stops the build before it reaches a compile step, then rebuild under Bear.
Source filtering removed everything
A sources: rule in the configuration file that is broader than
intended can exclude every compiled file, leaving a [] even though the
build compiled normally. (A duplicates: rule cannot do this by
itself: it always keeps the first occurrence of a group of duplicates,
so it can shrink a database but never empty it out on its own; see
Troubleshooting
for that case.)
Confirm: temporarily remove the sources: section from your
configuration and rebuild; if entries appear, a rule there is the cause.
Fix: narrow the rule. For example, excluding every .c file:
schema: "4.2"
sources:
files:
- pattern: "*.c"
action: exclude
produces [] for a project with only .c sources, by design. See
Configure Bear for the full
sources: syntax.
Diagnostics
Before anything else, re-run with logging turned on:
RUST_LOG=info bear -- <build command>
Near the end of the output is a summary that tells you which side of the pipeline lost your entries:
Output pipeline:
semantic events: 5
current entries: 2
previous entries: 0
filtered entries by duplicate: 0
filtered entries by source: 0
synthesized header entries: 0
dropped entries (invalid): 0
total entries written: 1
semantic eventsnear the minimum (as low as 1, just the build command itself) means the build never executed a compiler - the “nothing to compile” or “build failed early” cases above.semantic eventsclearly greater than zero butcurrent entries: 0means commands ran but none were recognized as a compiler - the “compiler not recognized” case.filtered entries by sourceorfiltered entries by duplicategreater than zero, withtotal entries writtenat zero, means recognized entries were filtered out by your configuration.
Set RUST_LOG=debug instead for more detail: every intercepted command
is tagged Recognized(...) or NotRecognized(...), which pins down
exactly which invocation Bear saw and how it classified it. Without
RUST_LOG set, Bear prints only warnings and errors; see the
bear(1) man page’s ENVIRONMENT section for the exact levels.
Related: Troubleshooting for a database that is short or wrong rather than empty, Supported compilers for the recognized names, How Bear works for the interception and analysis mechanism, the Recipes index, and the platform notes for Linux, macOS, Windows, and BSD.
Set up clangd for a project without CMake
bear -- make
Run your build under Bear once to produce compile_commands.json, and
clangd picks it up automatically the next time you open a source file in
your editor - no CMake, no manual flag lists. The rest of this page is
about getting that file where clangd looks for it, and confirming that
it actually found it.
Where the file has to be
clangd searches for compile_commands.json starting from the file you
are editing: the file’s own directory, a build/ subdirectory of it,
then the parent directory, that parent’s build/ subdirectory, and so
on up the tree (see clangd’s project setup
documentation). Running bear -- make from your
project root places the file exactly where this search finds it for any
source file under that root, which is why the plain invocation above is
usually all you need.
Two situations fall outside that search:
- the database was generated somewhere other than the project root, for
example a separate build directory named with
--output(bear --output build/compile_commands.json, orbear intercept’s own--output); - you are editing a source tree that clangd’s search does not reach from the file’s own location.
Pointing clangd at a database in a different place
Pass --compile-commands-dir on clangd’s own command line (consult your
editor’s clangd-integration settings for where to add this):
clangd --compile-commands-dir=/path/to/build
or, to keep it in the project instead of your editor configuration, add
a .clangd file at the project root:
CompileFlags:
CompilationDatabase: build
The CompilationDatabase key takes a directory (absolute, or relative
to the .clangd file’s own location) to search instead of walking
upward from the edited file. See clangd’s configuration
documentation for the complete set of .clangd keys;
this page only covers the one that matters for finding the database.
With compile_commands.json in a build/ directory
and sources in a sibling src/ directory, both clangd --compile-commands-dir=build and a .clangd file with
CompilationDatabase: build at the project root make clangd load the
database for a file under src/.
Confirming clangd actually loaded it
Ask clangd to parse a single file outside of your editor, and look at the lines it logs while loading:
$ clangd --check=path/to/file.c
Loaded compilation database from /path/to/build/compile_commands.json
Compile command from CDB is: [...] /usr/bin/cc -o main ...
Compile command from CDB is: means clangd found and used an entry for
that exact file. There are two other outcomes worth telling apart.
Compile command inferred from greet.c is: [...] -x c-header ... -- /path/to/greet.h
clangd found no entry for this file but borrowed one from a neighbour in the same directory. This is the normal, working result for a header, since Bear records the sources a build compiles and headers are not compiled on their own. Take it as success unless the flags it inferred are wrong for that file.
Generic fallback command is: [...] clang ... -- /path/to/file.c
clangd loaded some database but found nothing to work from, not even a
neighbour - a sign the file was filtered out of compile_commands.json,
or was never compiled (see Bear produces an empty
compile_commands.json if the database
itself is empty). If it never mentions loading a database at all, it
did not find one on its search path or at the configured location; see
Where the file has to be above.
Related: Bear produces an empty compile_commands.json if the file has no entries at all, Generate compile_commands.json for a Makefile project, and the Recipes index.
clangd has no compile command for a header
clangd already guesses a compile command for a header with no entry of its own, by borrowing the flags of a compiled source file nearby, and for many projects that guess is good enough as it stands. When it is not - the borrowed flags are wrong for this header, or there is no source nearby to borrow from - turn on header-entry synthesis in Bear’s configuration file:
schema: "4.2"
headers:
enabled: true
strategy: siblings
This makes Bear write a real entry for the header itself, cloned from a
compiled source’s arguments with the source path swapped in for the
header’s and the output flag removed, instead of leaving clangd to infer
one. The full set of headers: keys and their defaults is on Configure
Bear.
Telling the situations apart
Run clangd against the header directly and read the log line it prints while loading the database:
clangd --check=path/to/header.h
Compile command from CDB is: ...- the database already has a real entry for this header. This is not the problem this page is about.Compile command inferred from some_source.c is: ...- clangd found no entry but borrowed one from a neighboring source in the same directory. This is often fine as it stands; look at whether the borrowed flags actually apply to the header (same defines, same target) before changing anything.Generic fallback command is: ...- clangd found no entry and no neighbor to borrow from at all. This is the case synthesis exists for.
Set up clangd for a project without CMake documents these same three log lines with the fuller context of a working database; this page only adds the header-specific reading of them.
Synthesis is not always the difference between broken and working
In a split include/ + src/ project (include/greet.h used by
src/greet.c, which lives in a different directory), running with header
synthesis off still leaves clangd able to work on include/greet.h: it
infers a command from src/greet.c and logs Compile command inferred from src/greet.c is: ... -x c-header .... Turning on the
dependency-files strategy replaces that with a real entry, and the log
line changes to Compile command from CDB is: ... - but the header was
already usable before that change. Reach for synthesis when one of these
is actually true, not by default:
- the flags a neighboring source would donate are wrong for this header (a different target, different defines, a header shared across parts of the build that compile differently);
- no compiled source is in reach to donate flags from at all (the
Generic fallback commandcase above); - a consumer needs a real database entry rather than clangd’s own
inference - running
clang-tidydirectly against the header, or another tool that readscompile_commands.jsonwithout clangd’s fallback logic.
Choosing a strategy
Two strategies are available, and they reach different headers:
siblingsclones flags from a compiled source in the same directory as the header. It needs nothing from the build, but it reaches only headers that share a directory with a compiled source: in a splitinclude/+src/layout it picks up a header living beside.cfiles insrc/, but not one living alone ininclude/, because that directory has no compiled source to clone from. The flags it clones are approximate, borrowed from whichever compiled source happens to sit next to the header, not necessarily the one that actually includes it.dependency-filesreads the make-style.dfiles the build already left on disk (from-MMD,-MD, or an equivalent flag) and synthesizes an entry for every header named as a prerequisite, wherever it lives. This is how it reaches a header ininclude/from a.dfile written while compiling a source insrc/. It needs the build to have actually emitted those files: look for*.dfiles next to the object files after a build, or check the compiler flags for-MMD/-MD, before choosing this strategy. A build that never passes one of those flags leaves nothing fordependency-filesto read, and synthesis silently reaches no headers.
Start with siblings when the project’s headers all sit next to the
sources that use them; reach for dependency-files when they do not, or
when siblings’s approximate flags are visibly wrong, and the build
already produces .d files.
A third strategy is conceivable: post-process an existing compilation database and determine, for each translation unit, which header files it actually used. That would be more accurate than cloning a neighbor’s flags, and would not require the build to have emitted dependency files. Bear does not implement this strategy yet; external tools exist that do this kind of post-processing.
Related: Bear produces an empty compile_commands.json for a database missing more than just header entries, Generate compile_commands.json for a Makefile project, and the Recipes index.
Use Bear with ccache, distcc, or icecc
In the common case Bear needs no configuration for a launcher in front of a compiler. Run the build exactly as you already do, launcher and all:
bear -- ccache gcc -c main.c -o main.o
produces this entry in compile_commands.json:
[
{
"file": "main.c",
"arguments": ["gcc", "-c", "main.c", "-o", "main.o"],
"directory": "/path/to/project",
"output": "main.o"
}
]
The ccache token is gone: arguments holds the real compiler’s argv,
taken verbatim from whatever followed the launcher’s name, not a
resolved or canonicalized path. distcc and icecc drop out the same way;
the database never contains an entry whose command is the launcher
itself:
bear -- ccache gcc -c main.c -o main.o # -> "gcc", ...
bear -- ccache cc -c main.c -o main.o # -> "cc", ...
bear -- ccache /usr/bin/gcc -c main.c -o main.o # -> "/usr/bin/gcc", ...
In each case arguments starts with exactly the token shown in the
comment. Bear does not chase the name further; if the build wrote a bare
cc, that is what ends up in the database.
The ccache masquerade case
Some distributions (Fedora, Arch, Gentoo) put a directory of symlinks
ahead of the real compilers on PATH, so a plain cc or gcc resolves
to ccache without the build ever typing the word ccache. This is
different from the case above: there is no launcher token for Bear to
drop, because the build’s command line never named one. On a host where
/usr/lib64/ccache/cc is a symlink to ccache:
bear -- cc -c main.c -o main.o
records "cc" as the compiler, exactly as invoked, not resolved past the
symlink and not rewritten to gcc. Bear classifies the ambiguous cc
name by probing --version (ccache passes that probe through to the real
compiler), so the entry parses with the right flag family, but the
recorded name stays what the build actually ran. This matches the
contract in Supported compilers for
ambiguous names.
Preload mode versus wrapper mode
Both interception modes handle an explicit launcher the same way: a
build command of ccache gcc -c main.c -o main.o produces the identical
single entry (compiler gcc, ccache dropped) whether
intercept.mode is
preload or wrapper.
Wrapper mode has one behavior preload mode does not need: when a build
discovers its compiler through PATH or a CC variable and that lookup
lands on a ccache masquerade directory (not an explicit ccache token),
Bear resolves past the masquerade at discovery time and records the real
compiler’s absolute path. With CC=gcc and a masquerade directory first
on PATH, Bear logs
resolve: masquerade wrapper at /usr/lib64/ccache/gcc; re-resolving 'gcc' past /usr/lib64/ccache
and the entry records the real compiler the masquerade pointed at, never
the masquerade symlink and never Bear’s own .bear/ wrapper. See
“The recursion hazard” below for why this step exists.
distcc specifics
distcc’s own options that appear before the compiler name (-j,
--jobs, -v, --verbose, -i, --show-hosts, --scan-avail,
--show-principal) are recognized and skipped while Bear looks for the
compiler, so they never get mistaken for it and never leak into the
recorded arguments. distcc -j4 clang -c main.c therefore records
clang -c main.c, not -j4 clang -c main.c.
icecc specifics
icecc follows the same contract as ccache, distcc, and sccache: the
icecc token is dropped and the real compiler’s arguments are recorded
as invoked. icecream’s icerun is a different tool (it runs arbitrary
commands on the cluster, not compilations) and is not recognized as a
launcher.
The recursion hazard, and how Bear avoids it
Wrapper mode works by prepending its own directory of compiler-named
executables to PATH. A masquerade wrapper does the same thing for its
own purpose. If Bear recorded the masquerade wrapper’s path as “the
compiler” instead of resolving past it, the two directories would look
each other up indefinitely: Bear’s wrapper would call the masquerade
symlink, which would search PATH for the real compiler, find Bear’s
own wrapper first, and call back into it. Bear avoids this by resolving
its lookup PATH past any masquerade directory before recording what a
bare compiler name points to, so the real compiler is what gets wrapped
and recorded, never the masquerade link or Bear’s own wrapper. Detection
is a filesystem check (does the resolved path’s target look like a known
launcher binary), not a subprocess probe, and it only changes what Bear
resolves, not the build’s own child environment: a build that actually
wants ccache’s caching behavior for other invocations still gets it.
Setting CCACHE_COMPILER or CCACHE_PATH, or stripping the masquerade
directory from the build’s own PATH, were all considered and rejected,
because each changes the build’s behaviour rather than only Bear’s
lookup; the reasoning is recorded with the source, under
docs/rationale/.
What does not work
- A launcher wrapping another launcher (
ccache distcc gcc -c main.c) produces no entry. Bear does not chase a chain of launchers; the first launcher’s argument has to be a real compiler. - A launcher whose argument is not a recognized compiler (
ccache make all), or a bare launcher invocation with no argument at all, produces no entry. - A launcher under a name Bear does not recognize is not treated as a launcher at all: its own invocation produces no entry (it is not a compiler), though a real compiler it execs directly may still be recorded on its own. Add an override (below) to fix the launcher’s own entry instead of relying on that side effect.
A launcher at a nonstandard name or path
If the build wraps the compiler with a launcher under a custom path or a renamed binary, teach Bear the mapping in the configuration file, the same way you would hint an unrecognized compiler:
schema: "4.2"
compilers:
- path: /opt/build-tools/my-ccache
as: ccache
With this override, my-ccache gcc -c main.c -o main.o records the same
single entry, compiler gcc, as a plain ccache invocation would. as
accepts each launcher’s own name for the equivalent case with those
tools, and wrapper as the generic spelling for any of them; see
Configure Bear for the
compilers: key in full, and Supported
compilers for the accepted
names, generated from Bear’s own definitions.
Related: Supported compilers for the recognized launcher and compiler names, Troubleshooting for a database that comes out wrong, and the Recipes index for other tasks.
Run Bear inside a Docker container
Run Bear inside the container, as part of the build it observes, the same way you would on a bare host:
RUN bear -- make -j4
or against an already-running container:
docker exec my-container sh -c "bear -- make -j4"
This is the working answer for the common case: a normal, dynamically linked build toolchain, building for the container’s own architecture. The rest of this page covers getting Bear into the image, the cases where the mechanism needs help, and the one problem that is specific to containers: the paths Bear records belong to the container’s filesystem, not the host’s.
Installing Bear in the image
Add Bear to the image the same way you would install it anywhere else:
your distribution’s package if it carries a current-enough one, or a
source build otherwise. Install Bear covers both;
a source build inside a Dockerfile follows the same three commands as
INSTALL.md:
# Install the Rust toolchain, a C compiler, and lld or mold first
# (see INSTALL.md for the exact prerequisites for your base image).
COPY . /src
WORKDIR /src
RUN cargo build --release && ./scripts/install.sh
Build Bear in the same image (or one with the same libc) it will run in. The preload library is a compiled shared object, so a copy built elsewhere only works if that build’s libc is no newer than the image’s, the same ABI constraint Troubleshooting describes for cross-compilation.
Why the build has to run inside the container
bear -- docker exec ... from the host does not work: docker exec
hands the command to the Docker daemon, which runs it in the container’s
own process tree, one the host-side Bear process never sees, so it never
observes the compiler invocations. This is explained in full in
Bear on Linux, WSL2, and Docker; this page
takes it as the starting point, not something to reprove.
What the container needs for preload interception
Preload mode works inside
a container with no extra privileges: LD_PRELOAD is a plain
dynamic-linker feature, not a ptrace-based interception, so it needs no
--privileged flag and no added capabilities. It does need:
- a dynamic linker in the image. A
scratchor fully static final image has none, so a build that runs in such an image cannot use preload mode there; build in a normal base image instead, or use wrapper mode (below). - the container’s libc to match what Bear’s preload library was built against, per the ABI note above.
- a build tool that is itself dynamically linked. A statically linked
build tool never loads the dynamic linker, so its own
exec()calls are invisible to preload the same way they are on a bare host; this is not a Docker-specific limitation. - working loopback networking, for the TCP connection intercepted
processes use to report back to
bear-driver. Bear binds this on127.0.0.1(or[::1]), and the driver and the processes it observes are in the same network namespace, so the connection stays inside the container whichever--networkmode started it.
When to fall back to wrapper mode
If the build tool inside the container is statically linked (a Go build using cgo is the common case; see How Bear works), set wrapper mode in the configuration file the build reads:
schema: "4.2"
intercept:
mode: wrapper
As on any platform, a “configure” step that discovers compilers before the real build runs must itself run under Bear so it discovers the wrapper; see Bear produces an empty compile_commands.json.
The path-mapping problem
By default Bear records directory and file exactly as the build used
them, with no transformation
(format.paths). Inside
a container that path is almost always absolute under the container’s
own filesystem root, for example /src/main.c. clangd, clang-tidy, and
similar tools normally run on the host, where /src does not exist; a
compile_commands.json copied out of the container as-is points at
files the host-side tool cannot find.
Three ways to deal with it, in order of how little they change:
-
Bind-mount the project at the same absolute path inside the container as on the host, so the recorded paths are already correct on both sides:
docker run -v "$(pwd):$(pwd)" -w "$(pwd)" myimage bear -- makeThis needs no post-processing and is the simplest fix when the host and container can agree on a path.
-
Rewrite the path prefix after copying the database out, when the layouts cannot match (a different OS, or a host path the container cannot mount at the same location): substitute the container’s mount point for the host’s project root in
directoryandfilewithsedorjqbefore pointing clangd at the file. -
Run the editor and language server inside the container too (a dev-container setup), so nothing outside the container ever reads the database, and the mismatch does not arise.
Related: Bear on Linux, WSL2, and Docker for the platform-level notes this page builds on, Troubleshooting for a database that comes out wrong, and the Recipes index for other tasks.
Generate compile_commands.json when cross-compiling
Run the cross build under Bear exactly as you would run it directly:
bear -- make
Bear records the cross compiler the same way it records a native one: the
executable path the build actually invoked, and every argument verbatim,
including a cross-compilation target prefix in the name
(arm-none-eabi-gcc, aarch64-linux-gnu-g++) and flags such as
--sysroot or a target-specific -I. Bear does not interpret or rewrite
argument values (only @file response-file expansion and a couple of
environment-variable foldings are optional exceptions, see
format.arguments in Configure Bear),
so a value like --sysroot=/opt/arm-sdk/sysroot lands in the entry’s
arguments exactly as the build passed it. Nothing about the build
changes; what is worth knowing is which names get recognized as
cross-compiled, and one limitation of the default interception method
that shows up specifically in cross builds.
Prefixed and version-suffixed names
GCC, Clang, NVIDIA’s nvcc, Flang, and Vala’s valac are recognized
under a cross-compilation target prefix, a version suffix, or both
together; every such spelling is parsed with its base name’s own flag
rules. This support varies by family - it is not a blanket rule applied
to every entry - so Cross-compiler prefixes and version
suffixes
is the source of truth for which families it covers and how the pattern
works; this page only covers the cross-build-specific failure below.
When preload cannot reach the compiler
Preload is the default interception method on Linux and the BSDs, and it
works for a cross build the same way it works for a native one, with one
exception: the preload library is built for the host’s ELF class. Many
vendor cross-toolchains still ship a 32-bit compiler driver binary even
on a 64-bit host (common for older embedded SDKs distributed as prebuilt
binaries); loading a 64-bit preload library into that 32-bit process
fails with a “wrong ELF class” error from the dynamic linker. The build
still completes, but that invocation is not intercepted, so its source
file is silently missing from compile_commands.json.
A related but different failure is a glibc symbol-version mismatch, which Troubleshooting covers in full, including the commands to compare glibc versions and the fix (link a Bear build against a glibc no newer than the SDK’s).
Either failure means preload cannot inject into that particular compiler process. Switch to wrapper mode instead, since it substitutes the compiler executable rather than injecting into it, so it does not care about the target binary’s ELF class or its glibc:
schema: "4.2"
intercept:
mode: wrapper
Wrapper mode needs the build to discover the wrapper as the compiler, so a “configure” step that detects compilers on its own has to run under Bear too; see Configure Bear for how to set the mode and How Bear works for what each method can and cannot reach.
Related pages
- Generate compile_commands.json for an STM32 or arm-none-eabi project for the Arm bare-metal case.
- Use Bear with a vendor embedded toolchain for QNX, Microchip XC8, and Texas Instruments.
- Supported compilers for the full name
table and the
compilers:override. - Generate compile_commands.json for a Makefile project for the general Make workflow this page builds on.
- Recipes for the rest of the task pages.
Intercept a 32-bit build on a 64-bit host
A build that runs 32-bit compilers or 32-bit build tools on a 64-bit
host records nothing from them: the build still exits 0, no error
appears, and compile_commands.json just comes out short or empty. The
preload library a normal Bear install ships is built for the host’s
64-bit ELF class, and the dynamic linker cannot inject a 64-bit shared
library into a 32-bit process (see How Bear
works
for what preload does and why the ELF class has to match). Exit
status covers why a missing
preload library never turns into a non-zero exit: the dynamic linker
treats it as something to warn about and skip, not to fail on, so
nothing about the run tells you a source file went unrecorded.
On glibc Linux this is fixable: build a second, 32-bit preload library,
install it alongside the normal 64-bit one, and point bear-driver at
both with a single INTERCEPT_LIBDIR setting. INTERCEPT_LIBDIR names
the directory bear-driver looks in for the preload library, relative
to its own bin/, and on glibc it accepts the literal string $LIB as
that name. The dynamic linker itself expands $LIB at load time, per
process: lib64 when loading into a 64-bit process, lib when loading
into a 32-bit one. One install then serves both kinds of process,
provided both libraries exist on disk.
Get a 32-bit toolchain and Rust target
# Fedora
sudo dnf install glibc-devel.i686 libgcc.i686 libatomic.i686
# Debian / Ubuntu
sudo apt install gcc-multilib
rustup target add i686-unknown-linux-gnu
For any other distribution, install whatever it calls its 32-bit development packages; the two lines above are only confirmed for Fedora.
Work around the missing cross driver
The preload library’s build script compiles a small C shim with
cc-rs, which looks for a i686-linux-gnu-gcc cross driver. Fedora (and
any distribution that provides multilib through gcc -m32 rather than a
separate cross-driver binary) has no such executable, so the build fails
with:
error occurred in cc-rs: failed to find tool "i686-linux-gnu-gcc"
There is no supported fix for this yet; the workaround is a two-line
shim on PATH that execs gcc -m32, pointed to by
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER:
cat >/tmp/i686-linux-gnu-gcc <<'EOF'
#!/bin/sh
exec gcc -m32 "$@"
EOF
chmod +x /tmp/i686-linux-gnu-gcc
export PATH="/tmp:${PATH}"
export CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER=/tmp/i686-linux-gnu-gcc
Build the 32-bit preload library
INTERCEPT_LIBDIR='$LIB' cargo build -p intercept-preload \
--target i686-unknown-linux-gnu
This produces target/i686-unknown-linux-gnu/debug/libexec.so, a 32-bit
libexec.so distinct from the normal 64-bit one.
Build and install the 64-bit Bear, then place both libraries
Build and install the regular 64-bit Bear with the same $LIB setting:
INTERCEPT_LIBDIR='$LIB' cargo build
SRCDIR=target/debug PREFIX=<prefix> INTERCEPT_LIBDIR='$LIB' ./scripts/install.sh
(See Install Bear for what PREFIX, DESTDIR,
and INTERCEPT_LIBDIR each control; the $LIB value here is what makes
this recipe work and is on top of what that page documents.)
install.sh does not understand $LIB as a dynamic-linker token: it
creates a directory literally named $LIB under <prefix>/libexec/bear/
and installs the 64-bit library there. Finishing the layout is a manual
step, and there is currently no supported way to have the build or
install scripts do it for you:
cd <prefix>/libexec/bear
mkdir -p lib lib64
mv 'target/i686-unknown-linux-gnu/debug/libexec.so' lib/libexec.so
mv '$LIB/libexec.so' lib64/libexec.so
rmdir '$LIB'
The result differs from the ordinary layout in exactly one place: the
single $INTERCEPT_LIBDIR directory becomes two, one per ELF class, and
no directory named $LIB is left behind.
$PREFIX/
|-- bin/
| `-- bear (shell script)
|-- libexec/
| `-- bear/
| |-- bin/
| | |-- bear-driver
| | `-- bear-wrapper
| |-- lib/
| | `-- libexec.so (ELF 32-bit)
| `-- lib64/
| `-- libexec.so (ELF 64-bit)
`-- share/ (unchanged)
Check the two libraries really are different ELF classes, and that the
32-bit one is the one under lib:
$ file <prefix>/libexec/bear/lib/libexec.so <prefix>/libexec/bear/lib64/libexec.so
libexec/bear/lib/libexec.so: ELF 32-bit LSB shared object
libexec/bear/lib64/libexec.so: ELF 64-bit LSB shared object
Swapping them produces the same silent, short database as having no
32-bit library at all: a 32-bit process looks only in lib, finds an
object of the wrong class, and skips it. That output is worth including
in any report about a multilib install. The rest of the tree, and what
PREFIX and DESTDIR mean, is in Install Bear.
Confirm it worked
Run the build under bear -- <build> as usual. If it drives any 32-bit
compiler or a 32-bit build tool that execs a compiler on its own behalf,
compare the entry count in compile_commands.json with and without
lib/libexec.so in place: removing the 32-bit library drops exactly the
entries that 32-bit process would have contributed, while the build
itself still exits 0. With the library missing, the dynamic linker also
prints the warning covered in Troubleshooting: LD_PRELOAD
errors - cannot be preloaded (cannot open shared object file): ignored. - which is the tell that this
is the missing-32-bit-library case rather than some other silent gap.
Limitations
- This works only on glibc Linux. musl, macOS, and the BSDs have no
$LIB-style token: they need a concrete library directory name instead, so one install cannot serve two ELF classes the same way. - The
cc-rsshim above is a workaround for a missing cross driver, not a fix; a distribution that shipsi686-linux-gnu-gccdirectly does not need it. scripts/install.shhas no option to produce thelib/lib64split itself. Repeat the manual step after every reinstall.
This is a different problem from a vendor cross-toolchain whose compiler driver is itself a 32-bit binary; that case, and wrapper mode as its fix, is covered in Generate compile_commands.json when cross-compiling.
Related pages
- Generate compile_commands.json when cross-compiling for the general cross-prefix and wrong-ELF-class story.
- Install Bear for
PREFIX,DESTDIR, andINTERCEPT_LIBDIR. - Exit status for why Bear exits
0even when a preload library never loaded. - Troubleshooting for
LD_PRELOADerrors and other silent interception failures. - Recipes for the rest of the task pages.
Generate compile_commands.json for an STM32 or arm-none-eabi project
Run the project’s Makefile under Bear:
bear -- make
Cross-prefixed GNU Arm Embedded GCC (arm-none-eabi-gcc,
arm-none-eabi-g++) and Arm Compiler 6 (armclang, armclang++, the
compiler behind Keil MDK) are the two toolchains Bear recognizes for a
bare-metal Arm target; see Supported
compilers for the full name
table. arm-none-eabi-gcc is recognized through GCC’s general
cross-compilation prefix support, covered in Generate
compile_commands.json when cross-compiling;
armclang has its own family instead.
Where STM32CubeIDE’s Makefile actually is
STM32CubeIDE does not build from a Makefile at the project root: it
generates one per build configuration, under a subdirectory named for
that configuration - Debug/Makefile, Release/Makefile, or a custom
configuration’s own name - and that generated file is what the IDE’s
“Build” button actually runs. Once a configuration has been built at
least once from the IDE, run Bear against that Makefile directly:
cd Debug
bear -- make
If the IDE already finished building that configuration, make there
has nothing left to do and Bear records nothing; force a full rebuild the
same way Generate compile_commands.json for a Makefile
project recommends
for any other up-to-date build tree, for example bear -- make clean all. A plain CubeMX-generated Makefile project (no IDE) writes its
Makefile at the project root instead of a per-configuration subdirectory,
but the same rule applies: run Bear against the Makefile that actually
compiles your sources, from clean if it is already up to date.
When preload cannot inject into the compiler
Vendor Arm toolchains, including some STM32CubeIDE and Keil MDK
installations, still ship a 32-bit compiler driver binary even when
installed on a 64-bit host. Preload, the default interception method on
Linux, is built for the host’s own ELF class, so injecting it into a
32-bit process fails with a “wrong ELF class” error from the dynamic
linker: the build still completes, but that invocation goes
unintercepted and its source file is silently missing from
compile_commands.json. Generate compile_commands.json when
cross-compiling
covers this failure and its glibc-mismatch cousin in full, including the
intercept.mode: wrapper fix, which does not care about the target
binary’s ELF class.
What clangd needs for a bare-metal entry
The entry Bear records names the cross compiler itself
(arm-none-eabi-gcc or armclang) with the project’s flags intact:
-mcpu, -mthumb, --specs, -I for the vendor HAL and CMSIS headers,
and any -D for the target MCU. Without
--query-driver pointed at
that same cross compiler, clangd parses the entry with its own host
Clang, and host Clang rejects flags that only GCC’s embedded target
support understands: -mcpu=cortex-m4, -mthumb, and
--specs=nano.specs each produce an “unknown argument” diagnostic
instead of the target and header search path they set on the real
compiler.
--query-driver=/path/to/arm-none-eabi-gcc fixes this by asking the real
driver for its target and built-in include paths instead of guessing from
host Clang’s own, the same mechanism used for a wrapper’s baked-in paths
on Generate compile_commands.json for an MPI
project. Without
that option, strip the GCC-specific flags in .clangd instead so clangd
stops rejecting the command line outright:
CompileFlags:
Remove: [-mcpu=*, -mthumb, --specs=*]
See Set up clangd for a project without CMake for pointing your editor at the database once it exists.
Related pages
- Generate compile_commands.json when cross-compiling for cross-prefix recognition and the preload/wrapper trade-off in general.
- Use Bear with a vendor embedded toolchain for QNX, Microchip XC8, and Texas Instruments’ Arm toolchain.
- Set up clangd for a project without CMake.
- Supported compilers for the full recognized-name table.
- Recipes for the rest of the task pages.
Use Bear with a vendor embedded toolchain
Run the project’s Makefile under Bear exactly as you already build it:
bear -- make
This covers three vendor toolchains: QNX Momentics, Microchip’s MPLAB X
(XC8), and Texas Instruments’ Clang-based Arm compiler. Each builds
through a Makefile (MPLAB X generates one), and Bear needs nothing
toolchain-specific once that Makefile runs under it. What differs between
them is which executable name gets recognized as what; the full name
table, as: ids, and prefix/suffix rules for every family are on
Supported compilers and are not
repeated here.
QNX
Bear recognizes qcc and q++, QNX Neutrino’s compiler drivers, under
the qnx family, parsed with GCC’s flag rules (QNX 8 ships a GCC
12.2-based toolchain). This is a fixed pair of names: unlike GCC or
Clang, qcc/q++ are not recognized under a cross-compilation prefix or
a version suffix.
A QNX SDP installation also carries a set of ntoARCH-prefixed GCC
binaries underneath the driver (ntoaarch64-gcc, ntox86_64-gcc, and
similar, one per target architecture). These are recognized too, but as
gcc, not qnx: GCC’s cross-compilation prefix pattern matches any name
ending in -gcc, and QNX’s target names happen to fit that shape. A
Makefile that calls one of these directly, rather than through qcc,
still gets an entry, parsed with GCC’s own flag rules instead of QNX’s.
QNX’s -V flag picks the target/compiler variant on the command line
(-Vgcc_ntoaarch64le); the qnx family models it as a single token that
is never split and never swallows a following source file, matching both
the attached form and the bare -V (which lists available variants).
Microchip XC8 / MPLAB X
Bear recognizes both xc8-cc (the GCC-styled driver name used from XC8
v2.30 onward) and the legacy xc8 name, and records both under the
gcc family, not an XC8-specific id: XC8’s command-line syntax is
GCC-styled, so Bear parses it with GCC’s own flag rules.
XC16 and XC32 need no entry of their own: their driver names, xc16-gcc
and xc32-gcc, already match the general <prefix>-gcc cross-compilation
pattern GCC is recognized under (see Generate compile_commands.json when
cross-compiling). XC8 needed its own table row
because xc8-cc and xc8 do not follow that prefix pattern.
MPLAB X generates a Makefile for every build configuration and uses it as
the project’s real build entry point, the same way its own “Build” button
does internally. Run bear -- make from the project directory that holds
that generated Makefile.
Texas Instruments
Bear recognizes tiarmclang, TI’s Clang-based compiler for Arm targets,
under the clang family (not a TI-specific id, and not armclang, which
names Arm Ltd.’s own Compiler 6 instead).
TI’s earlier, non-Clang Code Generation Tools - the classic drivers for
C2000, C6000, MSP430, and the pre-Clang Arm compiler (armcl, cl2000,
cl430, cl6x) - are not in Bear’s recognition table at all. An
invocation of one of these produces no entry: not a wrong one, none.
A compilers: entry
pointing at the executable’s path can force one of these to be parsed
with, for example, as: gcc, but that only helps to the extent its flag
dialect actually matches GCC’s. It does not for the option that matters
most for the recorded entry: these tools spell an output file
--output_file=main.obj, not GCC’s -o main.obj, so the output field
GCC’s rules populate from -o comes out empty, and every other flag the
two dialects spell differently is misparsed the same way. The other
option is filing an issue against the project; see what is not
recognized.
Related pages
- Generate compile_commands.json when cross-compiling for cross-compilation prefix and version-suffix recognition in general.
- Generate compile_commands.json for an STM32 or arm-none-eabi project for the Arm bare-metal GCC / Arm Compiler 6 case.
- Supported compilers for the
full recognized-name table and the
compilers:override. - Generate compile_commands.json for a Makefile project for the general Make workflow these toolchains build on.
- Recipes for the rest of the task pages.
Generate compile_commands.json for an Emscripten project
Run the build under Bear, with Bear as the outermost command:
bear -- emmake make
Bear captures every process the build tree starts, however many wrapper
scripts sit in between, so it does not matter that emmake itself sets
CC/CXX to emcc/em++ before handing off to make: those exec calls
happen inside the tree Bear is already watching. Bear has to be the
outermost command for that to hold, so bear -- emmake make is the order
to use.
emcc, em++, and their .py-suffixed spellings are recognized under
the clang family, not a separate emscripten id, since Emscripten’s
driver is a Clang wrapper whose command line follows Clang’s flag syntax
closely enough for Bear’s Clang rules to parse it correctly. There is no
emscripten as value to write in a compilers: override; use clang
if a nonstandard emcc path needs a hint. See Supported
compilers for the full
recognized-name table.
CMake projects: emcmake
For a CMake-based Emscripten project, emcmake only needs to run once, to
generate a build tree configured for emcc/em++; the actual build is
what Bear needs to watch:
emcmake cmake -B build
bear -- cmake --build build
This is the same two-step shape as the general CMake case in the
bear(1) man page’s EXAMPLES: the generation step does not
compile your project’s sources, so it does not need to run under Bear.
In wrapper mode (the default on macOS and Windows, or forced with
intercept.mode: wrapper), a step that discovers the compiler on its own
has to run under Bear too, so run the generation step under Bear as well
and discard its output, the same way Generate compile_commands.json for
a Makefile project
handles ./configure:
bear -- emcmake cmake -B build
bear -- cmake --build build
clangd and an emcc entry
clangd reads the recorded compiler as emcc, a binary it does not run
and does not know the way it knows a plain clang. When it cannot infer
a driver’s built-in include paths and default target from the recorded
command alone, point it at the real driver directly with
--query-driver, the same mechanism the bear(1) man
page documents for MPI compiler wrappers:
clangd --query-driver=/path/to/emcc
See Set up clangd for a project without CMake for getting the database itself into a place clangd finds it.
Related pages
-
Supported compilers for the full recognized-name table and the
compilers:override for a path Bear gets wrong. -
Generate compile_commands.json for a Makefile project for the Autotools-style two-step pattern this page reuses for
emcmake. -
Set up clangd for a project without CMake for pointing your editor at the database this page produces.
-
Recipes for the rest of the task pages.
Generate compile_commands.json for a CUDA project
Run the build under Bear exactly as you already run it:
bear -- make
Bear recognizes nvcc, NVIDIA’s CUDA compiler driver, under the cuda
family (as: cuda), including a cross-compilation target prefix and a
version suffix (aarch64-linux-gnu-nvcc, nvcc-12.0). The recorded entry
looks like any other compiler entry, with nvcc-specific flags
(--gpu-architecture, -gencode, -Xcompiler, and similar) parsed and
kept in arguments verbatim:
[
{
"file": "main.cu",
"arguments": ["nvcc", "-c", "main.cu", "-o", "main.o"],
"directory": "/path/to/project",
"output": "main.o"
}
]
See Supported compilers for the full recognized-name table and how prefix/suffix recognition works in general.
clangd and clang-tidy do not speak the same nvcc dialect
nvcc’s command-line flags are its own, not Clang’s CUDA mode: options
such as -gencode, --generate-code, -Xcompiler, -Xptxas, and
--extended-lambda have no equivalent that Clang-based tooling accepts,
so an entry recorded straight from an nvcc invocation can make clangd
or clang-tidy reject the command line outright when they try to parse a
.cu file with it. Bear itself has no option to filter or rewrite
individual flags in a recorded entry; format.arguments in the
bear(1) man page only covers @file expansion and a couple
of environment-variable foldings, and sources only decides which files
get an entry, not what their recorded arguments contain.
The fix is on the consumer side. A .clangd file can strip the
unsupported flags before clangd parses the entry:
CompileFlags:
Remove: [-gencode*, --generate-code*, -Xcompiler*, -Xptxas*, --extended-lambda]
If you would rather not have clangd touch .cu files at all, suppress
its diagnostics on them the same way the bear(1) man
page recommends for the valac entries of a mixed C/Vala
project:
If:
PathMatch: .*\.cu
Diagnostics:
Suppress: '*'
Or drop CUDA sources from the database entirely with a sources rule in
bear.yml (see CONFIGURATION in the man page):
sources:
files:
- pattern: "*.cu"
action: exclude
nvcc’s host-compiler sub-invocations
nvcc splits a .cu file into a device-code path and a host-code path,
and for the host side it generates an intermediate file and compiles that
with the host compiler (gcc, clang, or MSVC’s cl, depending on
platform) as a separate process. Bear intercepts that process the same
way it intercepts nvcc itself, and because the host compiler’s
intermediate file has a different name than the original .cu source,
this sub-invocation does not collide with nvcc’s own entry under Bear’s
default duplicate matching (directory and file): both land in
compile_commands.json as separate entries, one for the .cu file and
one for the generated intermediate file the host compiler actually saw.
The intermediate file’s entry is rarely useful to an editor, since the
file it names does not exist once the build finishes; exclude it the same
way as .cu files above, with a pattern matching the intermediate
filename your local nvcc generates (for example "*.cudafe1.cpp"), or
scope a sources rule to directories instead if the host-compiler pass
runs from its own temporary directory.
Related pages
-
Supported compilers for the full recognized-name table and the
compilers:override for a path Bear gets wrong. -
Generate compile_commands.json for a Makefile project for the general Make workflow this page builds on.
-
Set up clangd for a project without CMake for pointing your editor at the database this page produces.
-
Recipes for the rest of the task pages.
Use Bear with Intel oneAPI compilers
Source the oneAPI environment script for the current shell, then run the build under Bear exactly as you would for any other compiler:
source /opt/intel/oneapi/setvars.sh
bear -- make
setvars.sh is what puts icx, icpx, ifx, and the rest of the oneAPI
drivers on PATH. Bear itself does nothing toolchain-specific at that
point: it watches whatever executable the build actually runs, the same
as for GCC or Clang. Skipping the source step is the most common
reason a build “under Bear” still records the system compiler: the shell
running make never had the oneAPI drivers on PATH to begin with, so
bear -- make and a plain make invoke exactly the same programs.
Which family each driver is recognized as
icx/icpx (current oneAPI) and icc/icpc (the older Classic
drivers) share one family id, intel_cc, and one Intel-specific flag
table (-qopenmp, -ipo, -fp-model, and the rest). The Fortran
drivers, ifort and ifx, are a separate family, intel_fortran. See
Supported compilers for the
full recognized-name table; bear semantic --print-compilers prints the
same mapping for the version you have installed.
Intel’s MPI wrappers (mpiicc, mpiifx, and the rest) reuse these same
two families rather than a generic MPI wrapper id. See Generate
compile_commands.json for an MPI project for that mapping and
why the split exists.
If a driver is not recognized
A build that calls the compiler through a renamed copy or a nonstandard
path (a custom install, a CI image that symlinks icx to something else)
needs a compilers: hint:
schema: "4.2"
compilers:
- path: /opt/custom/bin/icx-wrapper
as: intel_cc
Use as: intel_fortran for a renamed Fortran driver. See as and
ignore hints
for the accepted values, and the compilers section of Configure
Bear for the key itself.
Related pages
- Generate compile_commands.json for an MPI project for the Intel MPI wrappers specifically.
- Supported compilers for the complete recognized-name table and the ambiguous-name rules.
- Configure Bear for the
compilers:section. - Recipes for the rest of the task pages.
Use Bear with Cray and NVIDIA HPC compilers
Load the programming environment module for the current shell, then run the build under Bear:
module load PrgEnv-cray
bear -- make
On an HPE Cray system the toolchain is selected by which module is
loaded, not by which package is installed: module load rewrites PATH
(and other environment variables) for the shell that runs it. Once the
right compiler is on PATH, Bear needs nothing else; it watches
whatever the build executes, the same as anywhere else. The same applies
to an NVIDIA HPC SDK module (module load nvhpc) on a cluster that
provides one.
Recognized drivers
Cray’s own compiler drivers - craycc, crayCC, craycxx (cray_cc,
built on Clang, with a few Cray-specific extensions such as -fcray-*)
and crayftn/ftn (cray_fortran) - and the NVIDIA HPC SDK’s nvc,
nvc++, nvfortran (nvidia_hpc) are all recognized directly. The
legacy PGI names, pgcc, pgc++, pgfortran, map to the same
nvidia_hpc family and its flag table (-Mvect, -acc, -gpu, and the
rest), since the PGI compiler became the NVIDIA HPC SDK. See Supported
compilers for the full name
table.
cc, CC, and ftn: the PrgEnv wrapper names
cc and CC are ambiguous on a Cray system - either can front CCE, GCC,
or another vendor’s compiler depending on the loaded programming
environment module - so neither is in Bear’s static table; Bear
classifies them by probing --version once and caching the result, the
mechanism Supported
compilers
documents in general. The probe answers gcc or clang only, never
cray_cc, so add a compilers: entry when you need the family pinned
exactly, or when the probe cannot identify the compiler at all.
ftn, by contrast, is recognized directly as Cray Fortran (as: cray_fortran) regardless of which programming environment module is
loaded. If a loaded module makes ftn front a different Fortran
compiler on your system, override it the same way you would any
misclassified compiler:
schema: "4.2"
compilers:
- path: /opt/cray/pe/craype/default/bin/ftn
as: intel_fortran
Related pages
- Supported compilers for the full recognized-name table and the ambiguous-name probe.
- Generate compile_commands.json for an MPI project if the
build also calls
mpicc/mpifort-style wrappers directly instead of thecc/CC/ftndrivers. - Configure Bear for the
compilers:section. - Recipes for the rest of the task pages.
Generate compile_commands.json for an MPI project
Run the build under Bear exactly as you would without MPI:
bear -- make
A build that compiles through mpicc, mpicxx, mpifort, or one of the
Intel MPI wrappers needs no special configuration: Bear recognizes all of
them and records the wrapper invocation itself. See MPI compiler
wrappers
and Supported compilers for the
full name table and the wrapper-is-recorded-as-invoked contract in
general. This page covers what that contract means in practice: what
actually lands in the recorded entry, and the one place the database can
differ from what a reader expects.
Open MPI/MPICH vs. Intel
Open MPI’s and MPICH’s wrappers share one family id, mpi, and parse
with the GCC flag table. Intel’s wrappers (mpiicc, mpiifort, and the
rest) are recognized too, but as extra names on the intel_cc and
intel_fortran families instead of the generic mpi id: mpiicc is
icc/icx with MPI’s paths baked in, and Intel’s flag table has arity
rules (for example -debug takes a following argument) that the GCC
table would mis-parse. See Use Bear with Intel oneAPI
compilers for those two families in general.
What actually lands in the entry
Given
mpicc -c main.c -o main.o
where mpicc is a script that execs gcc with -I/opt/mpi/include -L/opt/mpi/lib prepended, the entry Bear writes is
[
{
"file": "main.c",
"arguments": ["mpicc", "-c", "main.c", "-o", "main.o"],
"directory": "/path/to/project",
"output": "main.o"
}
]
arguments holds the command exactly as the build wrote it: no gcc,
and none of the wrapper’s baked-in -I/-L flags, since Bear does not
run the wrapper to learn them. Clang tooling that needs those flags to
resolve MPI headers should point at the wrapper directly; clangd does
this with --query-driver. See Set up clangd for a project without
CMake for pointing an editor at the database this page
produces.
The compiler the wrapper execs is usually not a second entry
In preload mode Bear does intercept the compiler the wrapper execs
internally, not just the wrapper itself, but that rarely shows up as a
second entry in compile_commands.json: the default duplicate filter
(directory and file) treats the two invocations as the same
compilation and keeps whichever comes first, the wrapper’s. That
lower-level invocation surfaces as its own entry only if you widen
duplicates.match_on to include arguments (see Configure
Bear); ordinarily that is
not useful, since it names an intermediate compiler call rather than the
one your build system actually asked for.
MPICH’s compiler-override options, -cc=, -cxx=, and -fc=, are also
accepted without the = (-cc gcc); either form is recognized as a
driver option that takes a value, so it never gets misread as compiling a
source file named gcc.
Related pages
- Use Bear with Intel oneAPI compilers for the
intel_cc/intel_fortranfamilies the Intel MPI wrappers reuse. - Use Bear with ccache, distcc, or icecc for the launcher case this page contrasts with.
- Supported compilers for the full recognized-name table.
- Recipes for the rest of the task pages.
Troubleshooting
What to do when Bear runs but the result is not what you expected: an
empty or short compile_commands.json, entries with the wrong flags, or
a build that behaves differently under Bear than without it. If the
database is completely empty, start with Bear produces an empty
compile_commands.json, which
works through the causes one by one; this page collects the rest.
Enable debug logging
Before investigating anything else, re-run the build with debug logging on standard error:
RUST_LOG=debug bear -- <build command>
Without RUST_LOG set, Bear prints only warnings and errors. Setting it
to debug (or info/warn/error for less detail) switches every
helper process (bear-driver, bear-wrapper, the preload library) to a
verbose, timestamped format detailed enough to see which executables
were intercepted, how each was classified, and why an entry was or was
not written. Include this output when reporting a problem.
The output is missing entries
Same causes as an empty database, but partial - a compiler ran and was
recognized, yet its entry did not survive to the file. The most common
cause is --append (see Command-line
options): each Bear run
overwrites the output by default, so building incrementally without it
discards the previous run’s entries. Accumulate results across runs
instead:
bear --append -- make module_a
bear --append -- make module_b
New entries are placed before the existing ones, so a later rebuild’s
entry for a given file takes precedence over the stale one (see the
duplicates section below).
A file you know is compiled twice shows up once
If a project compiles the same source file more than once - once for a
static library and once for a shared one, for example - and the
database has only one entry for it, this is not --append: it is the
default duplicate match dropping the second invocation. Building zlib
1.3.1 shows the effect directly. ./configure && bear -- make produces
17 entries, one per source file, even though zlib compiles every one of
its 17 sources twice in the same directory: once for the static build,
and once with -fPIC -DPIC for the shared one. The two invocations
recorded for adler32.c:
gcc -O3 -D_LARGEFILE64_SOURCE=1 -DHAVE_HIDDEN -c -o adler32.o adler32.c
gcc -O3 -fPIC -D_LARGEFILE64_SOURCE=1 -DHAVE_HIDDEN -DPIC -c -o objs/adler32.o adler32.c
Bear’s default duplicates.match_on compares only directory and
file (see Configure Bear),
and both invocations share both, so only the first is kept. Add
arguments to the default match, keeping directory and file in it:
schema: "4.2"
duplicates:
match_on:
- directory
- file
- arguments
Rebuilding zlib with this configuration produces all 34 entries, two
per source file. Keep directory in the list: a project with a util.c
in two subdirectories, compiled with the same flags in each, would
otherwise have one of them treated as a duplicate of the other and
dropped. This is a trade-off, not a strict improvement: one
entry per file per directory is what most Clang tooling wants, and
matching on arguments too gives you every invocation at the cost of a
larger database in which clangd (and most other consumers) still picks
only one entry per file.
The output has duplicate entries
Two entries are duplicates only when every field listed in
duplicates.match_on matches; of a set of duplicates, Bear keeps the
first one in the file - the build’s first invocation within a single
run, or (per the ordering described above) the newest one across
--append runs.
The default match is directory and file, so one entry survives per
source file per directory whatever its arguments were. If that is
dropping invocations you wanted to keep, see A file you know is
compiled twice shows up once
above for the fix and its trade-off. Configure
Bear enumerates the fields
match_on accepts.
The output has extra entries
There are two different causes, and they need different fixes.
--append across runs carries forward entries from earlier
invocations, including ones for files that no longer exist. Drop
--append and rebuild from clean to get a database with only the
current build’s entries:
make clean
bear -- make -j4
Without --append each Bear run overwrites the previous output, so
bear -- ./configure followed by bear -- make leaves nothing behind
from the configure run: the build run replaces the file wholesale.
A configure step inside a single Bear run is the other cause, and
--append has nothing to do with it. bear -- sh -c './configure && make', or a Makefile that re-runs configure itself, puts the
configure phase’s throwaway compiles (conftest.c and friends) in the
same output as the real build. Split the two, so the build run’s output
is the one that survives:
bear -- ./configure
make clean
bear -- make -j4
If the configure step has to run under Bear for the build to be
intercepted at all - wrapper mode, where the configure step must
discover the wrapper as its compiler - keep the two runs separate as
above rather than chaining them with --append, or exclude the probe
files with a sources rule.
The build behaves differently with Bear
Usually harmless: extra lines in the build’s own output, such as a
loader warning about libexec.so, or additional environment variables
Bear sets for its own bookkeeping. These do not change what gets
compiled.
Worth reporting as a bug if the build’s actual result changes: a different binary, a step that fails only under Bear, or output written to an unexpected place. Two places to check first, since they are the things Bear adds to the build environment:
- the
.bear/wrapper directory in wrapper mode, in case the build system has its own use for a directory of that name in the working directory; - environment variables Bear sets for interception
(
LD_PRELOAD/DYLD_INSERT_LIBRARIESin preload mode, a modifiedPATHin wrapper mode), in case the build inspects or forwards its environment in a way that is sensitive to them.
Report these with RUST_LOG=debug output attached.
LD_PRELOAD errors
ERROR: ld.so: object '.../libexec.so' from LD_PRELOAD cannot be preloaded: ignored.
This means the dynamic linker could not find or load libexec.so at
the path bear-driver expects, relative to its own location
(../$INTERCEPT_LIBDIR/libexec.so, lib by default). Check that Bear
was built and installed with the same INTERCEPT_LIBDIR value; see
Bear on Linux for the build/install commands.
The warning does not stop the build or fail Bear: see Bear produces an
empty compile_commands.json
for why this is a silent cause of a database missing exactly the entries
a subprocess would have reported.
glibc version errors in cross-compilation
.../libexec.so: version `GLIBC_2.33' not found (required by .../libexec.so)
This is a different failure from the one above: the linker finds
libexec.so, but the library needs a newer glibc symbol version than
the C library available to the process it was injected into. It shows
up when the build runs compilers from a cross-compilation SDK whose
sysroot ships an older glibc than the host Bear was built on - the
preload library must be ABI-compatible with the libc the intercepted
compiler process loads from the SDK sysroot, not only with the host’s.
The intercepted invocation fails outright, so that command is silently
missing from the database rather than reported as a warning.
Compare the highest glibc version Bear’s preload library requires against the highest the SDK’s libc provides:
# Highest glibc version Bear's preload library requires
strings <prefix>/libexec/bear/lib/libexec.so | grep -oE 'GLIBC_[0-9.]+' | sort -V | tail -1
# Highest glibc version the SDK toolchain's libc provides
strings /path/to/sdk/sysroot/lib/libc.so.6 | grep -oE 'GLIBC_[0-9.]+' | sort -V | tail -1
objdump reports the same requirement more precisely, if it is
available:
objdump -T <prefix>/libexec/bear/lib/libexec.so | grep -oE 'GLIBC_[0-9.]+' | sort -uV | tail
If the version Bear’s library requires is newer than what the SDK’s libc provides, build (or obtain) a Bear linked against a glibc no newer than the SDK’s, and use that build for the cross-compilation. Building on a host whose own glibc is no newer than the SDK’s avoids the mismatch entirely.
Getting help
- Check
bear --versionagainst the latest release; distribution packages are often old, see Install Bear. - Run with
RUST_LOG=debugand read the output. - Search existing issues.
- Check How Bear works if the behaviour itself is the surprise.
- Open a new issue with the debug log and your platform (OS, Bear
version from
bear --version, and build system).
Related: Command-line options and Configure Bear for the flags and keys named above, Bear produces an empty compile_commands.json for that case, the Recipes index for task-shaped pages, and the platform notes for Linux, macOS, Windows, and BSD.
Command-line options
bear runs in four modes, selected by the subcommand or its absence:
combined (bear, no subcommand, the default), bear intercept, bear semantic, and bear parse-sh. Options given before -- belong to Bear;
everything after -- is the build command, passed through untouched.
- Combined runs the build, captures it, and writes the compilation database in one step. It is the default and the recommended way to run Bear.
bear interceptruns the build and only captures it, writing the raw event stream to a file for later analysis.bear semanticreads an event stream and writes the compilation database, without running a build.bear parse-shwrites the compilation database from shell command text, such as a build’s dry-run output or a saved build log, without running anything.
See How Bear works for the mechanism
behind capture and analysis, Configure Bear for every
key the -c/--config file accepts, and Exit status
for what Bear’s own exit code means in each mode.
Global usage
bear [OPTIONS] -- <BUILD_COMMAND>...
bear [OPTIONS] <COMMAND>
Bear is a tool that generates a compilation database for clang tooling.
Subcommands
| Command | Description |
|---|---|
intercept | intercepts command execution |
semantic | detect semantics of command executions |
parse-sh | produces the compilation database from build-system dry-run text (e.g. make -n output) |
help | Print this message or the help of the given subcommand(s) |
Arguments
| Argument | Description |
|---|---|
<BUILD_COMMAND>... | Build command |
Options
| Flag | Description | Default |
|---|---|---|
-c, --config <FILE> | Path of the config file | - |
-o, --output <FILE> | Path of the result file | compile_commands.json |
-a, --append | Append result to an existing output file | - |
-h, --help | Print help | - |
-V, --version | Print version | - |
bear intercept
intercepts command execution
bear intercept --output <FILE> -- <BUILD_COMMAND>...
Arguments
| Argument | Description |
|---|---|
<BUILD_COMMAND>... | Build command |
Options
| Flag | Description | Default |
|---|---|---|
-o, --output <FILE> | Path of the event file to write | - |
-h, --help | Print help | - |
bear semantic
detect semantics of command executions
bear semantic [OPTIONS]
Options
| Flag | Description | Default |
|---|---|---|
-i, --input <FILE> | Path of the event file to read | - |
-o, --output <FILE> | Path of the result file | compile_commands.json |
-a, --append | Append result to an existing output file | - |
--print-compilers | Print the compilers Bear recognizes and exit | - |
-h, --help | Print help | - |
Reads the event stream that bear intercept (or a third-party producer) writes and produces the compilation database, e.g.:
bear semantic --input events.json
bear parse-sh
produces the compilation database from build-system dry-run text (e.g. make -n output)
bear parse-sh [OPTIONS]
Options
| Flag | Description | Default |
|---|---|---|
-i, --input <FILE> | Path of the shell text to parse | - |
-o, --output <FILE> | Path of the result file | compile_commands.json |
-a, --append | Append result to an existing output file | - |
-C, --directory <DIR> | Initial working directory for the parsed commands | - |
-h, --help | Print help | - |
Parses the text a build system prints in dry-run mode and writes the compilation database, without running a build, e.g.:
make -n | bear parse-sh
Configure Bear
Bear reads an optional bear.yml. Every key is optional except schema,
which is required once you write a file at all; every section below
states its default, so a key you do not write keeps that value. The
canonical source for every key, accepted value, and default is the
bear(1) man page CONFIGURATION section; this page mirrors
it, organized one section at a time, with the shape a user would
actually write next to its default.
Where the file is found
Without --config, Bear searches for bear.yml in the current working
directory first, then the platform’s per-user configuration directory
(the XDG locations on Linux and the BSDs, %LOCALAPPDATA% /
%APPDATA% on Windows). The first file found is loaded; the rest are
not consulted. The exhaustive, ordered list of paths is the man page’s
FILES section. Pass -c/--config FILE to load a specific file
instead of searching.
schema
schema: "4.2"
Names the configuration format version this file is written for. It is the one key with no default: a file that omits it, or names a version this Bear release does not support, is rejected rather than partially applied.
intercept
intercept:
mode: wrapper
intercept.mode:preloadorwrapper. Default:preloadon Linux and the BSDs,wrapperon macOS and Windows.
compilers
compilers:
- path: /usr/bin/cc
as: gcc
- path: /usr/local/bin/gcc
ignore: true
Default: [] (no overrides; every compiler is recognized automatically).
compilers.path: Path of the compiler executable. Required. Apathon its own, with no other key, is already enough to make an otherwise unrecognized executable be treated as a compiler.compilers.as: Compiler type hint. Optional. The accepted values are the family ids listed on Supported compilers, which is generated from Bear’s own compiler definitions, pluswrapperfor a compiler launcher (see Use Bear with ccache, distcc, or icecc).bear semantic --print-compilersprints the same set for the version you have installed. Whenasis omitted, Bear guesses the family from the executable’s filename using its normal recognition patterns, falling back togccwhen no pattern matches; an MSVC-style or otherwise unusual compiler whose name matches nothing is then parsed with GCC’s flag rules unlessasis set explicitly.compilers.ignore: Exclude this executable’s invocations from the database. Optional. Default:false.
sources
sources:
directories:
- path: /project/tests
action: exclude
files:
- pattern: "moc_*.cpp"
action: exclude
Default: {} (both lists empty; nothing is filtered out).
sources.directories.path,sources.directories.action: list of directory rules;actionisincludeorexclude.sources.files.pattern,sources.files.action: list of filename-glob rules;actionisincludeorexclude.
duplicates
duplicates:
match_on:
- file
- arguments
duplicates.match_on: list of entry fields to compare:file,arguments,directory,command,output. Two entries are duplicates when all configured fields match; the first occurrence is kept. Default:[directory, file].
format
format:
paths:
directory: canonical
file: canonical
entries:
use_array_format: true
include_output_field: true
arguments:
from_response_files: false
from_environment: true
format.paths
format.paths.directory,format.paths.file:as-is,canonical,relative, orabsolute. Default:as-isfor both.
format.entries
format.entries.use_array_format: write theargumentsarray instead of thecommandstring. Default:true.format.entries.include_output_field: include theoutputfield. Default:true.
format.arguments
format.arguments.from_response_files: expand@fileresponse-file references into their tokenized contents. Default:false.format.arguments.from_environment: fold compiler environment variables (CPATH,C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH, MSVC’sCL/_CL_) into the recorded arguments. Default:true.
headers
headers:
enabled: true
strategy: dependency-files
headers.enabled: turn header-entry synthesis on. Default:false.headers.strategy:siblingsordependency-files. Default:siblings.
Checking what is actually in effect
Bear logs its fully resolved configuration as YAML whenever RUST_LOG
is set to info or a more verbose level:
RUST_LOG=info bear -- true
This is the same line whether the values came from a bear.yml or from
built-in defaults, so it is the way to check what a given file actually
changed on this machine, rather than working it out from the sections
above.
See also: How Bear works for the interception and semantic-analysis mechanism this configuration shapes, Supported compilers for compiler recognition, and the Recipes for task-oriented uses of these sections (for example excluding generated sources).
Supported compilers
Bear decides whether an intercepted command is a compilation by
matching the name of the executable the build ran against a table of
known compiler names. A name it does not know is not a compiler, and
Bear does not run the program to guess otherwise. The one exception is a
short list of names that are genuinely ambiguous across platforms, and
Ambiguous names below says which and why. Once a
name matches, the command line is
parsed with that family’s own flag rules, since GCC, MSVC, and Swift
(to pick three) do not agree on how to spell an include path or an
output file. This page explains how that matching works and lists the
names it currently covers; the configuration keys that let you extend
or correct it are in the bear(1) man page and explained in
Configure Bear.
Cross-compiler prefixes and version suffixes
A handful of core families are recognized under more than their plain
name. GCC and Clang (and, following the same pattern, CUDA’s nvcc,
Flang, and Vala’s valac) are also recognized with a
cross-compilation target prefix (arm-linux-gnueabihf-gcc,
aarch64-linux-gnu-clang), a version suffix (gcc-12, clang-15), or
both together (arm-linux-gnueabi-gcc-12). Every such spelling is
parsed with its base name’s flag rules. Support for this varies by
family: it is not a blanket rule applied to every entry in the table
below.
Compiler launchers
ccache, sccache, distcc, and icecc wrap a real compiler invocation
rather than compiling anything themselves. Bear recognizes each
launcher by name, finds the real compiler among its arguments, and
records the compilation as if the launcher were not there: ccache gcc -c main.c is recorded as a gcc invocation, not a ccache one. A
launcher invocation whose argument is not a recognized compiler
(ccache make all), or that wraps another launcher (ccache distcc gcc -c main.c), produces no entry; Bear does not chase a chain of
launchers. distcc’s own options (-j, --jobs, -v, and similar) are
skipped while looking for the compiler, so they never get mistaken for
one.
MPI compiler wrappers
mpicc, mpicxx, mpifort, and the vendor-specific MPI wrappers
(Intel’s mpiicc, mpiifort, and similar) are recorded as invoked,
not expanded to the compiler they wrap. Clang tooling that needs a
wrapper’s baked-in include paths can point at the wrapper directly, for
example with clangd’s --query-driver. An information-only invocation
(mpicc -showme, -show, -compile_info, -link_info) produces no
entry, and a wrapper option that carries a value (MPICH’s -cc=gcc)
never swallows a following source file.
Ambiguous names
cc, c++, and the HPE Cray PrgEnv wrapper CC are not in the name
table at all, because the same basename is a different compiler
depending on the platform or the loaded environment module (GCC on
most Linux distributions, Clang on the BSDs and macOS, whatever the
loaded programming environment selects on a Cray system). Bear
classifies these by running the executable once with --version and
caching the result; a compilers: entry in the configuration (see
below) skips the probe and forces a classification when its output
does not match a known signature.
as and ignore hints
A compilers: entry in the configuration names a path and either an
as value (the family to parse it with) or ignore: true (drop its
invocations entirely). Reach for it when a compiler sits at a path or
under a name the table below does not cover, or when a generic name is
misclassified. The generic names cc and c++ are the usual reason to
add an entry, since not every custom build of GCC or Clang answers
--version in a way the probe recognizes.
An as value is a family identifier, not a display name: it is the
short, lower-case id given as each family’s “Configuration as: value”
below, spelled verbatim. There are no aliases, so as: gcc is accepted while
as: GCC is not, and several families share one id (icx and icc
are both intel_cc). The accepted ids are the ones in the family tables
below, which are generated from the compiler definitions, plus wrapper
for a compiler launcher. This page does not repeat them here as a list:
the tables are the copy that cannot drift. An unaccepted value is
rejected when the configuration loads, with an error listing every value
that would have worked, and bear semantic --print-compilers prints the
same mapping for the version you have installed.
Recognized families
Internal jobs that a driver spawns for itself are recognized only so Bear can filter them back out: they are never user-facing invocations and never produce a database entry. The tables below label each one as internal, separately from the family’s real, user-facing names.
The compiler launchers share one kind rather than a compiler family, so
wrapper, or any launcher’s own name from the launcher table below,
selects it.
Compiler families
armclang
Configuration as: value: armclang.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
armclang, armclang++ | Arm Compiler 6 | armclang-12 | not recognized | developer.arm.com |
clang
Configuration as: value: clang.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
clang, clang++ | Clang | clang-12 | arm-linux-gnueabihf-clang | clang.llvm.org |
amdclang, amdclang++, hipcc | AMD ROCm Clang/HIP | not recognized | not recognized | rocm.docs.amd.com |
emcc, em++, emcc.py, em++.py | Emscripten | not recognized | not recognized | emscripten.org |
tiarmclang | Texas Instruments Arm Clang | not recognized | not recognized | ti.com |
An invocation is also ignored when its arguments include -cc1: that is an internal frontend or codegen call, not a user-facing compile.
clang_cl
Configuration as: value: clang_cl.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
clang-cl | Clang (MSVC mode) | clang-cl-12 | not recognized | clang.llvm.org |
cray_cc
Configuration as: value: cray_cc.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
craycc, crayCC, craycxx | Cray C/C++ | craycc-12 | not recognized | support.hpe.com |
cray_fortran
Configuration as: value: cray_fortran.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
crayftn, ftn | Cray Fortran | crayftn-12 | not recognized | support.hpe.com |
cuda
Configuration as: value: cuda.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
nvcc | NVIDIA CUDA | nvcc-12 | arm-linux-gnueabihf-nvcc | docs.nvidia.com |
fasm
Configuration as: value: fasm.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
fasm | flat assembler | not recognized | not recognized | flatassembler.net |
flang
Configuration as: value: flang.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
flang, flang-new | Flang | flang-12 | arm-linux-gnueabihf-flang | flang.llvm.org |
amdflang | AMD ROCm Flang | not recognized | not recognized | rocm.docs.amd.com |
gcc
Configuration as: value: gcc.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
gcc, g++, gfortran, egfortran, f95 | GCC | gcc-12 | arm-linux-gnueabihf-gcc | gcc.gnu.org |
xc8-cc, xc8 | Microchip XC8 | not recognized | not recognized | microchip.com |
Internal, not user-facing: cc1, cc1plus, cc1obj, cc1objplus, f951, collect2, lto1. Bear recognizes these only so it can filter them back out; they never produce a database entry.
ibm_xl
Configuration as: value: ibm_xl.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
ibm-clang, ibm-clang++ | IBM Open XL C/C++ | ibm-clang-12 | not recognized | ibm.com |
xlclang, xlclang++ | IBM XL C/C++ | xlclang-12 | not recognized | ibm.com |
intel_cc
Configuration as: value: intel_cc.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
icx, icpx | Intel oneAPI C/C++ | icx-12 | not recognized | intel.com |
icc, icpc | Intel C++ Compiler Classic | icc-12 | not recognized | intel.com |
mpiicc, mpiicpc, mpiicx, mpiicpx | Intel MPI C/C++ wrapper | not recognized | not recognized | intel.com |
intel_fortran
Configuration as: value: intel_fortran.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
ifort, ifx | Intel Fortran | ifort-12 | not recognized | intel.com |
mpiifort, mpiifx | Intel Fortran MPI wrapper | not recognized | not recognized | intel.com |
mpi
Configuration as: value: mpi.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
mpicc, mpicxx, mpic++, mpiCC, mpifort, mpif77, mpif90 | MPI compiler wrapper | mpicc-12 | not recognized | open-mpi.org, mpich.org |
msvc
Configuration as: value: msvc.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
cl | Microsoft Visual C++ | not recognized | not recognized | learn.microsoft.com |
Internal, not user-facing: c1, c1xx, c2. Bear recognizes these only so it can filter them back out; they never produce a database entry.
nasm
Configuration as: value: nasm.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
nasm, yasm | NASM / YASM assembler | not recognized | not recognized | nasm.us, github.com |
nvidia_hpc
Configuration as: value: nvidia_hpc.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
nvc, nvc++, nvfortran | NVIDIA HPC SDK | nvc-12 | not recognized | docs.nvidia.com |
pgcc, pgc++, pgfortran | PGI (legacy NVIDIA HPC) | pgcc-12 | not recognized | docs.nvidia.com |
qnx
Configuration as: value: qnx.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
qcc, q++ | QNX qcc | not recognized | not recognized | qnx.com |
swift
Configuration as: value: swift.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
swiftc | Swift | not recognized | not recognized | swift.org |
Internal, not user-facing: swift-frontend. Bear recognizes these only so it can filter them back out; they never produce a database entry.
An invocation is also ignored when its arguments include -frontend: that is an internal frontend or codegen call, not a user-facing compile.
vala
Configuration as: value: vala.
| Executable names | Recognized as | Version suffix | Cross-compilation prefix | Documentation |
|---|---|---|---|---|
valac | Vala | valac-12 | arm-linux-gnueabihf-valac | vala.dev |
Compiler launchers
| Executable name | Recognized as | Configuration as: value | Documentation |
|---|---|---|---|
ccache | Compiler cache | ccache | ccache.dev |
distcc | Distributed compiler | distcc | distcc.org |
icecc | Distributed compiler | icecc | github.com |
sccache | Compiler cache | sccache | github.com |
This list is kept in step with Bear’s own compiler definitions, and grows by request as users bring toolchains that are not covered yet; it is not a fixed set.
What is not recognized
A few names are excluded on purpose, not by oversight:
as(the GNU assembler): GCC and Clang already spawn it on a temporary file for every ordinary compile, so recognizing it would add a throwaway entry per compilation, keyed on a name that differs every run.mpirun,mpiexec: these launch programs, they do not compile anything.swift: this is the Swift subcommand dispatcher (swift build,swift run); the actual compiler invocation isswiftc.amdgpu-arch: reports target GPU architectures; despite the shared prefix, it is not a compiler driver.ml,ml64(MASM): not currently supported.
None of these can be recovered with a compilers: override, because
as only redirects a path to one of the families already in the
tables above; it cannot teach Bear a new one. If your build uses a
toolchain that genuinely is not covered anywhere in this page, that is
a gap in Bear’s compiler definitions, not a configuration problem: it
is worth filing as an issue against the project.
See also: Configure Bear for the compilers:
section in full, and How Bear works for where
recognition fits between interception and writing the database.
Exit status
Modes that run a build report the build’s own outcome; modes that only transform data report whether Bear understood its input.
| Mode | Exit status |
|---|---|
bear -- <build> (combined) | The build’s exit code, byte for byte: 0 on success, the same non-zero code otherwise. |
bear intercept -- <build> | Same as combined: the build’s exit code. |
bear semantic | 0 when at least one input event was understood, or the input was empty. Non-zero when the input was non-empty and every event was rejected. |
bear parse-sh | 0 when at least one line parsed as a shell command, even when none of them named a recognized compiler, or when the input was empty. Non-zero when the input was non-empty and every line was skipped. |
bear semantic --print-compilers | Always 0. |
bear --help (or a subcommand’s --help) | Always 0. |
| A malformed invocation (unknown flag, missing build command) | Non-zero (2), reported before anything runs. |
See command-line options for what each mode does.
Notes
A build killed by a signal never exits 0, but the signal number is not
encoded. bear -- sh -c 'kill -TERM $$' exits 1, and so does a build
killed with SIGKILL. Bear does not follow the 128 + signal shell
convention: a caller that only inspects Bear’s own exit code cannot tell a
signalled build from an ordinary failing one. The signal itself still
reaches the build unchanged.
Empty input is not an error. An empty semantic or parse-sh run
writes an empty database, exits 0, and prints a notice on standard
error so an accidentally empty pipeline does not pass unnoticed. The
same holds for parse-sh input that parses cleanly but names no
compiler invocation, such as a lone mv command: no line is skipped, no
diagnostic is printed, and the empty database is a success.
Bear can fail on its own, independent of the build or the input. A configuration file that fails to parse, an invalid combination of flags (for example directing the compilation database to standard output while also running a build), or a named input file that does not exist are all reported before any build runs or any input is read, and all exit non-zero. An output path that cannot be written (a directory sitting where the database file should go) is also non-zero, and this one can surface after the build already succeeded: the build’s own outcome does not change, but Bear’s exit code still reports non-zero because it could not deliver its result.
A missing preload library is not one of these failures, and the
consequence is worth knowing: the dynamic linker ignores a preload target
it cannot open, prints its own warning on standard error, and lets the
build run. The build’s own commands are still observed, but the processes
it spawns are not, so a build whose compiler runs under make yields an
empty database and an exit status of 0. Success here means the build
succeeded, never that the database is complete. See Bear produces an
empty compile_commands.json.
For what to do about a non-zero exit, see troubleshooting.
How Bear works
Producing a compilation database is two separate problems: capturing
which commands a build runs, and recognizing which of those commands
are compilations worth recording. Bear keeps the two apart. A capture
stage produces a stream of execution events (program, arguments,
working directory, environment); a semantic analysis stage turns the
events it recognizes as compiler invocations into compilation-database
entries. Combined mode runs both in one process; bear intercept and
bear semantic run them as two separate steps over a file in between,
and bear parse-sh replaces capture entirely by reconstructing events
from text instead of running anything. This page explains the
mechanism; for the commands themselves see Getting started with
Bear and the Recipes.
Two ways to capture a real build
Interception observes the build as it runs. Bear has two mechanisms for it, and uses exactly one per invocation, chosen before the build starts. Which one is the default on your system is on its platform page - Linux, macOS, Windows, BSD - and Configure Bear explains how to force the other one.
Preload injects a small shared library into every process the build
starts, using the dynamic linker’s library-preloading facility
(LD_PRELOAD on Linux and the BSDs, DYLD_INSERT_LIBRARIES on macOS).
The library intercepts the exec family of calls (and posix_spawn,
popen, system) before they reach the kernel, reports the call, then
lets it proceed unchanged. Because it hooks the mechanism every process
uses to start another one, it sees every compiler invocation regardless
of how the build found it, and the build itself is unaware anything is
watching. It cannot see into a statically linked executable, because
such a binary never goes through the dynamic linker in the first place.
It cannot run at all on Windows, which has no preloading facility to
hook and for which the library is not even built, nor on a macOS host
with System Integrity Protection enabled, since SIP strips
DYLD_INSERT_LIBRARIES from protected executables before it can take
effect.
Wrapper takes the opposite approach: instead of watching every
process, it puts a reporting executable where the compiler is expected
to be. Bear creates a .bear/ directory holding one wrapper for each
compiler it recognizes on PATH at startup, puts that directory first
on PATH, and points
compiler-selection variables (CC, CXX, and the like) at it. When the
build launches what it thinks is the compiler, it launches the wrapper
instead; the wrapper reports the call, then execs the real compiler
found at its resolved, absolute path so the build sees no difference.
This works on every platform, including Windows, but only for compilers
the build actually reaches through PATH or a variable Bear resolved
at startup: a build step that discovers the compiler on its own (a
./configure probing for cc) needs to run under Bear too, so the
wrapper is in place by the time the real build starts.
Neither mechanism is a fallback for the other. Where preload is unavailable, Bear does not quietly switch to wrapper mode; forcing preload there is a startup error that names wrapper mode as the alternative, and the build does not run.
From an intercepted call to the driver
Both mechanisms report through the same channel: the process running
the build (bear-driver, from the bear-driver binary) opens a TCP
listener on the loopback interface, and every preload library instance
or wrapper process connects to it and writes one length-prefixed JSON
event per execution. This keeps the reporting side dependency-free and
lets interception and semantic analysis live in separate processes when
running as bear intercept and bear semantic, with the event stream
as the file in between. The shared pieces of this runtime (the
execution type, the wire encoding, environment-variable filtering) live
in the intercept crate; the driver-side half that supervises the
build, runs the TCP collector, and prepares the wrapper directory or
preload environment lives in intercept-supervisor; the injected
library itself is intercept-preload, and the wrapper executable’s own
code is bear-wrapper.
Reconstructing events without running anything
bear parse-sh takes a third path that skips capture entirely: instead
of observing real execution, it parses shell command text, typically a
build system’s dry-run output (make -n) or a saved build log, and
reconstructs the executions that text describes. It understands a
documented subset of sh syntax and make’s recursive-directory
markers, and feeds what it reconstructs through the same semantic
analysis as a real build. This is inherently lower fidelity: a dry run
can omit commands a real build would issue, and the environment used to
resolve bare executable names is the parsing environment, not the
build’s. It exists for builds that cannot be run at all, such as
recovering a database from a CI log after the fact; interception
remains the higher-fidelity default whenever the build can run.
Semantic analysis
Whatever produced the event stream, from here the processing is the same. Each event’s executable name is checked against Bear’s compiler definitions, including cross-compiler prefixes, version suffixes, and compiler launchers such as ccache and distcc that carry the real compiler in their own arguments; see Supported compilers for how that recognition works. An event that names no recognized compiler produces nothing.
A recognized invocation is then turned into zero, one, or several entries, depending on the compiler and what the invocation asked it to do:
- most compilers treat each source file as its own translation unit, so an invocation naming several sources yields one entry per source, each stripped down to look like a command that compiled only that file;
- a compiler that always compiles a whole target as one unit (Vala’s
valac) collapses to a single entry keyed on the first source, with every source kept in its arguments; - a compiler whose sources depend on each other within one invocation
(Swift’s
swiftcin whole-module mode) produces one entry per source, but every entry keeps the complete invocation’s arguments; - an invocation that only links, or only asks for information
(
--version, preprocessing,--help), produces no entry at all.
From there, the entries pass through whatever the configuration requests: dropping generated sources by directory or filename, collapsing duplicate entries, resolving or leaving paths as they are, choosing between an arguments array and a command string, and optionally synthesizing entries for header files. Configure Bear explains each of these.
Output
The result is written as a JSON array of entries conforming to the Clang JSON Compilation Database specification, the format clangd, clang-tidy, and similar tools read to know how each source file is compiled.
See also: Getting started with Bear for the first run, the Recipes for specific tasks, and Troubleshooting for a database that came out empty or wrong.
Bear on Linux, WSL2, and Docker
Preload is the default interception method on Linux (see how Bear
works for the mechanism, and
Configure Bear for the
setting). It cannot see into a statically linked build tool; force
intercept.mode: wrapper
instead.
WSL2
If .wslconfig sets networkingMode=mirrored, the loopback connection
intercepted processes use to report back to bear-driver breaks,
producing an empty or short compile_commands.json with no other
error. Remove the setting and restart WSL2 (wsl --shutdown).
Docker
Bear must run inside the container, as part of the build it observes;
bear -- docker exec ... from the host does not work. See Run Bear
inside a Docker container.
Installing the preload library to a non-standard lib directory
INTERCEPT_LIBDIR=lib64 cargo build --release
INTERCEPT_LIBDIR=lib64 ./scripts/install.sh
A mismatch here fails silently: preload never loads and the database comes out empty or short. See Installing Bear for why.
Related: Troubleshooting for output that comes out wrong, and the Recipes index for other tasks.
Bear on macOS
Wrapper is the default interception method on macOS: Apple-signed Xcode
compilers block preload, because System Integrity Protection strips
DYLD_INSERT_LIBRARIES before it can take effect (see how Bear
works). Preload can be forced with
intercept.mode: preload,
but only works with SIP disabled.
Building through Xcode or xcodebuild
Xcode passes many flags through an @file response file, and Bear’s
default recording keeps that argument literal. Turn on
format.arguments.from_response_files
so an entry built through xcodebuild carries the actual flags.
Homebrew-installed compilers
Homebrew’s GCC formula installs only versioned binaries (gcc-14, not
gcc); Bear recognizes these by filename automatically (see Supported
compilers).
Related: how Bear works for the preload/wrapper mechanism, Troubleshooting for output that comes out wrong, and the Recipes index for other tasks.
Bear on Windows
Wrapper is the only interception method on Windows: there is no
LD_PRELOAD/DYLD_INSERT_LIBRARIES equivalent, so forcing preload is a
startup error before anything runs (see how Bear
works and Configure
Bear).
MSYS2 and MinGW64 environments
Bear builds under MSYS2’s Unix-like environments (MINGW64, UCRT64,
CLANG64, CLANGARM64). $MINGW_PREFIX points at whichever one you
launched (/mingw64, /ucrt64, /clang64, and so on), the natural
PREFIX for installing into it:
DESTDIR="$pkgdir" PREFIX="$MINGW_PREFIX" ./scripts/install.sh
Only bear-driver and bear-wrapper are installed in this
configuration; there is no preload library to package.
Related: how Bear works for the wrapper mechanism, Troubleshooting for output that comes out wrong, and the Recipes index for other tasks.
Bear on BSD
Bear runs on FreeBSD, OpenBSD, NetBSD, and DragonFly BSD with the same defaults as Linux: preload is the default interception method (see how Bear works and Configure Bear), and it cannot see into a statically linked build tool. What differs is packaging and the default compiler and linker names.
cc and c++ mean Clang here
On FreeBSD, OpenBSD, NetBSD, and DragonFly BSD (and on macOS), the base
system’s cc/c++ is Clang, not GCC as on most Linux distributions.
Bear resolves this by probing --version; no configuration is needed
for the common case. See Supported
compilers to
override when the probe cannot classify a compiler.
Installing
FreeBSD packages Bear (pkg install bear). On OpenBSD, NetBSD, and
DragonFly BSD, build from source per
INSTALL.md;
check your ports/pkgsrc tree first.
Related: how Bear works for the preload mechanism, Troubleshooting for output that comes out wrong, and the Recipes index for other tasks.