docs/requirements/output-json-compilation-database.md
When the user runs Bear wrapping a build command, Bear produces a JSON file
(compile_commands.json by default) that lists every compilation command
invoked during the build. Each entry contains the working directory, the
source file, and the compilation command or arguments.
Bear's output conforms to the Clang JSON Compilation Database
specification, which defines the array-of-command-objects structure and
the directory, file, arguments, command, and output fields:
https://clang.llvm.org/docs/JSONCompilationDatabase.html
Two aspects of that format are specific to how Bear writes it and are
described below: how Bear escapes the command field, and how it spells
the compiler path in arguments[0]. Whether an entry carries the
optional output field, and its value, is owned by
output-compilation-entries.
command field in detailWhen Bear emits the command field instead of arguments, it joins the
argument list into a single string using POSIX shell quoting conventions,
which may produce either single-quoted or double-quoted output depending
on the argument content. Both forms are valid per the specification.
The content therefore has two layers of escaping:
" becomes \" and \ becomes \\ at the JSON level.For example, compiling with -DNAME=\"hello\":
arguments form: [..., "-DNAME=\"hello\"", ...] (no shell escaping,
only JSON encoding of the raw argument)command form: "... '-DNAME=\"hello\"' ..." or
"... \"-DNAME=\\\"hello\\\"\" ..." (shell-quoted, then JSON-encoded)arguments[0])The specification states that arguments[0] should be the executable name
(e.g. clang++), but does not prescribe whether it must be an absolute
path, a relative path, or a bare command name. Bear writes the compiler
exactly as the interception layer observed it; the output stage never
resolves, absolutizes, or otherwise rewrites the path.
What "observed" means depends on the interception mode:
exec call: if the build invoked
gcc, Bear writes gcc; if it invoked /usr/bin/gcc, Bear writes
/usr/bin/gcc.interception-wrapper-mechanism.md),
and the database shows that absolute path.gcc in the
text stays gcc (see interception-shell-text-parsing). No
command ran, so no resolved path exists to report.directory, file, and at least one of command or argumentscommand and arguments fields are mutually exclusive in each entrycommand field that cannot be parsed by POSIX shell-word splitting is
rejected during validationfile or directory fields are rejected during validationWARN level with the reason, and counted in the run summary; it
never aborts processing of subsequent entriesERROR-level summary line so
the empty compilation database is never silentoutput-compilation-entriesarguments (array form)command format is selected, arguments are shell-escaped
following POSIX shell quoting conventionsWhen an entry fails validation:
compile_commands.json.WARN-level log line names the file, directory, and the specific
validation reason (e.g. empty directory, unparsable command).This contract ensures a single malformed entry cannot destroy the usable output produced from the rest of a build.
When every entry that would otherwise have been written is dropped, Bear
emits a single ERROR-level summary line stating so. The compilation
database is still written (as an empty array) so downstream tooling sees a
valid file, but the log makes the empty result impossible to miss.
Validation drops never affect the exit code; the exit-code contract
across modes is owned by cli-exit-codes.
Given a project with a single C source file:
When the user runs
bear -- <compiler> -c source.cthencompile_commands.jsonis created, it contains valid JSON with exactly one entry, the entry hasdirectoryequal to the working directory,fileequal to "source.c", andargumentsstarting with the compiler path.
Given a project with multiple C and C++ source files:
When the user runs
bear -- sh build.shwhere build.sh compiles all files, thencompile_commands.jsoncontains one entry per source file, and each entry has the correct compiler (C or C++) inarguments[0]. Note: exact entry count may vary when a caching compiler wrapper (ccache) is in the path.
Given a build command that produces no compiler invocations:
When the user runs
bear -- true, thencompile_commands.jsoncontains an empty JSON array[].
Given a build that partially fails (some files compile, some do not):
When the user runs
bear -- sh build.sh, thencompile_commands.jsonstill contains entries for all attempted compilations, and Bear's exit code reflects the build failure.
Given a compiler invocation with -DNAME=\"hello\":
When Bear writes the
commandfield, the value is shell-escaped (either single- or double-quoted form is valid), the JSON encoding adds another layer, JSON-decoding followed by shell-word splitting recovers the original argv, and the entry has noargumentsfield (the two forms are mutually exclusive).
Given a compiler invoked as a bare name (e.g. gcc), intercepted in
preload mode:
When Bear writes the entry, then
arguments[0]isgcc(not resolved to an absolute path).
Given a compiler invoked with a full path (e.g. /usr/bin/gcc):
When Bear writes the entry, then
arguments[0]is/usr/bin/gcc.
Given a build that produces a mix of valid entries and one entry that
fails validation (for example, an empty directory field):
When Bear writes the output, then the valid entries appear in
compile_commands.jsonunchanged, aWARNlog line names the dropped entry and the validation reason, the run summary reports one dropped entry, and the process exit code is not affected by the drop.
Given an entry presented for output whose command field contains an
unterminated quote (e.g. cc -c "src.c):
When Bear validates entries while writing the output, then the entry is dropped from
compile_commands.json, and aWARNlog line names the entry and the unparsablecommandas the validation reason.
Given a project with a single C source file:
When the user runs Bear configured to write the database to
build/commands.jsoninstead of the default path, thenbuild/commands.jsonis created and contains the entry, and nocompile_commands.jsonis written in the working directory.
Given a build where every candidate entry fails validation:
When Bear writes the output, then
compile_commands.jsonis written as an empty JSON array[], aWARNlog line is emitted for each dropped entry, and a singleERROR-level summary line reports that every entry was dropped.
directory and file pair
survives -- arguments are ignored by the match, so differing
configurations of the same file collapse; keeping several requires
widening the fields entries are matched on (owned by
output-duplicate-detection).file and directory fields is configurable;
see output-path-format for details.arguments[0] spelling contract responds to issues #240, #678,
and #671.