60

Say we have make files (not cmake/premake/ninja etc) for our project that do work for gcc and clang. We want to generate out from them JSON Compilation Database to feed it into clang-modernize tool. How to do such thing? (is there any parser in clang infrastructure or some script with usage like make CC='cc_args.py gcc' CXX='cc_args.py g++' or some other tool)?

jxramos
  • 7,356
  • 6
  • 57
  • 105
DuckQueen
  • 772
  • 10
  • 62
  • 134

7 Answers7

53

I have no personal experience with it but Bear seems to be targeted to your scenario. (It was linked from the clang-modernize site.)

Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • 2
    Works great even for Linux kernel modules! Thanks much! – St.Antario Dec 13 '19 at 18:18
  • I am using Bear to generate compilation database for VS Code. Though the compile db looks fine, the VS Code is still not smart enough to find symbol definition with the compile DB. It seems there's still a long way to go for the Intellisense in VS Code. After all, VS code is a folder-based tool. Not a project-based tool like Visual Studio. – smwikipedia Dec 22 '22 at 10:50
  • @smwikipedia AFAIK the database isn't used by VS Code directly but rather by an lsp-server it runs, likely `clangd`. So you should look into that. E.g. in my case I have an old large proprietary project at work with lots of targets *(like libs and executables)*. It was Makefile-based until an year ago *(when I moved it to Meson)*. So I've been using Bear + Emacs + clangd as an LSP-server, and it was always working perfectly for me, the "intellisense" in Emacs was always on point. – Hi-Angel May 06 '23 at 11:57
34

Make has a usually-undesired feature of emitting the compiler command lines to its standard output, but in this case you can use it with a shell (and jq) pipeline. With GNU Make on Bash:

make --always-make --dry-run \
 | grep -wE 'gcc|g\+\+' \
 | grep -w '\-c' \
 | jq -nR '[inputs|{directory:".", command:., file: match(" [^ ]+$").string[1:]}]' \
 > compile_commands.json

On Windows, cmd.exe syntax is similar:

  • use ^ instead of \ for line continuation
  • use "-delimited strings only
  • use """ to escape a " within a "-delimited string
  • use ^^ to escape a ^ within a "-delimited string

However, this doesn't handle cases where the makefile recursively invokes itself (or other makefiles) in subdirectories using the -C argument.

Tanz87
  • 1,111
  • 11
  • 10
  • 2
    Had to use `grep -E -w 'gcc|g++'` for the first grep, otherwise I had no results. For anyone using msys/cygwin replace the jq line with `| jq -nR "[inputs|{directory:\"$(cygpath -m $(pwd))\", command:., file: match(\" [^ ]+$\").string[1:]}]" \ ` – Teharez Jun 19 '20 at 09:50
  • @Teharez Thanks, updated the `grep`. With MSYS2 one might also need to use `mingw32-make` instead of `make`. – Tanz87 Jun 19 '20 at 10:59
  • This answer is really good, and if someone use clang like me, try to add gcc|g++|clang in line 2. – GGGin Aug 24 '20 at 08:18
  • @GGGin There are [many different compilers](https://en.cppreference.com/w/cpp/compiler_support) and they should not be mixed in the same build process. – Tanz87 Aug 24 '20 at 09:22
  • 1
    @Tanz87: `grep -wE 'gcc|g++'` should be `grep -wE 'gcc|g\+\+'`. – rtx13 Feb 01 '21 at 16:17
  • 1
    @rtx13 Thanks for spotting! – Tanz87 Feb 01 '21 at 16:20
  • After this command in Wine build my `compile_commands.json` has just `[]`, and I had to do a full rebuild because Makefile just disappeared `make: *** No targets specified and no makefile found. Stop.`. `bear` from the other answer worked better for me. – Hi-Angel Sep 18 '21 at 21:41
  • Such I nice answer, that I had to add to my ~/.bash_functions – DrBeco Jun 25 '23 at 20:50
6

Maybe someone is in my same situation. I have a macOS and Bear was generating an empty compile_commands.json file. They acknowledge this problem in the README and suggest to use scan-build as a workaround. Unfortunately, I was still getting an empty file. Two alternative solutions I found are:

  • compiledb, which worked in my case.
  • a combination of clang++ compiler's -MJ option plus sed, detailed here, that seems simple enough and easy to transform into a make target. This one I have not tested.
3

If you don't have the permission to install Bear/compiledb, or you can't build Bear from source, then a Web app could help.

I built such a simple web app at https://texttoolkit.com/compilation-database-generator, which generates compile_commands.json from the make output.

whatacold
  • 660
  • 7
  • 15
3

Since I don't have enough points to react under answers yet, I wanted to comment under @Tanz87 's answer, who suggests to dry-run make, and uses grep and jq to format the output.

My CMake has detected /usr/bin/c++ as the default compiler (I'm on WSL2, Ubuntu 21.10), and thus the grep -wE 'gcc|g\+\+' command does not work, and just like @Hi-Angel, my compile_commands.json ended up with [].

The fix is to also grep for c++, so the whole command becomes:

make --always-make --dry-run \
 | grep -wE 'gcc|g\+\+|c\+\+' \
 | grep -w '\-c' \
 | jq -nR '[inputs|{directory:".", command:., file: match(" [^ ]+$").string[1:]}]' \
 > compile_commands.json

Edit: That being said, while the solution does seem to generate a compile_commands.json file, running clang-tidy in the build directory prints an error from the clang backend for every file in the compilation database:

error: no such file or directory: '&&' [clang-diagnostic-error]

Every command entry in the compile_commands.json file looks like this:

"cd /build/src && /usr/bin/c++  -I/src/src -std=gnu++2a -o CMakeFiles/project.dir/subdir/subsubdir/sourcefile.cpp.o -c /project/src/subdir/subsubdir/sourcefile.cpp

This project uses CMake to generate the Make build system files. Unfortunately, no solution yet.

Edit: Edit: Removing cd /build/src && from each command in the compile_commands.json fixes that particular error. Using sed:

sed 's|cd.*.&&||g' compile_commands.json

Edit: Edit: Edit: As a oneliner, it requires some extra slashes for the && tokens:

make --always-make --dry-run
 | grep -wE 'gcc|g\+\+|c\+\+'
 | grep -w '\-c'
 | sed 's|cd.*.\&\&||g'
 | jq -nR '[inputs|{directory:".", command:., file: match(" [^ ]+$").string[1:]}]' \
 > compile_commands.json
jmk
  • 111
  • 2
  • 4
1

I put together this compiler wrapper, you may want to massage the if clause which extracts the filename argument. It only detects .c files

I invoke it like this: make ... CC="cc_wrapper $CC"

or: make ... CC="cc_wrapper gcc"

#! /bin/bash

# Writing to a common output file, so buffer for atomic output
if file=($(printf "%s\n" "$@" | grep '\.c$'))
then JSON="$(jq -nR '[inputs|{directory:$pwd, arguments:[$ARGS.positional[]], file:.}]' --arg pwd "$PWD" --args -- "$@" <<< "${file}")"
     # c2rust transpile "$file.compile_commands.json" will still look for a file called compile_commands.json so we have to provide it
     mkdir -p "$file.c2rs"
     echo "$JSON" > "$file.c2rs/compile_commands.json"
fi
exec "$@"
Sam Liddicott
  • 1,265
  • 12
  • 24
1

I have found that for some make projects compiledb is able to intercept make output and generate a valid compile_commands.json file. However, for some projects it generates an empty compile_commands.json file. The solution that always works for me is as below.

make clean;make VERBOSE=y all &> make_output.txt
compiledb --parse make_output.txt

This always generates a valid compile_commands.json file even for make projects where compiledb is not able to directly intercept the make output.
Hope this helps.

user16217248
  • 3,119
  • 19
  • 19
  • 37
CCoder
  • 63
  • 5