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.