C/C++编译数据库的生成

2,658 阅读3分钟

编译数据库是一个json格式,它提供了编译源文件时的命令、参数、源文件、工作目录等信息,可以基于编译数据库对C/C++代码进行分析。一般的生成文件名为compile_commands.json

这里包含对Compilation database — Sarcasm notebook的整理。

主流的支持基于编译数据库分析的工具有:clang-checkclang-docclang-include-fixerclang-renameclang-tidyclangd

如何获得项目的编译数据库

使用cmake构建项目

原来是cmake的地方替换成cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1

生成的编译数据库形如:

{ "directory": "/home/user/llvm/build",
"arguments": ["/usr/bin/clang++", "-Irelative", "-DSOMEDEF=With spaces, quotes and \\-es.", "-c", "-o", "file.o", "file.cc"],
"file": "file.cc" },

{ "directory": "/home/user/llvm/build",
"command": "/usr/bin/clang++ -Irelative -DSOMEDEF=\"With spaces, quotes and \\-es.\" -c -o file.o file.cc",
"file": "file2.cc" },

]

注意到这里有argumentscommand两种输出形式,一般认为arguments结果要好。

使用xmake构建项目

xmake提供了两种方式来生成。

  • 调用xmake project -k compile_commands生成。
  • 在xmake.lua添加规则add_rules("plugin.compile_commands.autoupdate")(一般情况在顶层添加该规则)。

使用bazel构建项目

使用ninja构建项目

使用如下格式生成。

ninja -t compdb > compile_commands.json

使用clang编译单个文件

使用类似 clang -MJ c_struct.o.json c_struct.c -o c_struct.o的选项生成。生成的格式形如下。注意如下几个细节:

  • 生成文件的末尾的,,这并不是标准的json;同时缺乏通常的编译数据库生成的[]。因此,基于这种方案生成的编译数据库文件需要手动调整(这里提供一个自动化脚本)。
  • argumentsclang的命令被替换成非软链接的绝对路径,且添加上了一些选项如-xc--target=x86_64-pc-linux-gnu。这样的编译数据库,是使用了clang编译器而不是driver。
  • 除了arguments还有output项,且/tmp/c_struct-d6903c.oc_struct.o并不一样。
{ "directory": "/root", "file": "c_struct.c", "output": "/tmp/c_struct-3faa5e.o", "arguments": ["/usr/lib/llvm-14/bin/clang", "-xc", "c_struct.c", "--target=x86_64-pc-linux-gnu"]},

使用bear进行wrap

使用bear build这个回复中提到不建议使用scan-build中的intercept-build

优势

  • 可以搞定任何构建命令的编译数据库生成问题。
  • 生成的编译数据库使用arguments而不是command,避免了转义的问题(但很多对编译数据库的分析工具可能仅支持command项)。

不足

  • 由于bear仅监控本次实际构建的命令,因此如果是项目的增量式构建则只会输出部分编译数据库结果,但全量构建可能会非常耗时。经常会出现,"全版本动辄近一个小时,很影响效率,大部分时间都浪费在等待编译结束"。
  • 仅在类Unix(Linux/Mac/BSD)下可用。

使用compiledb对make进行wrap(推荐)

compiledb有所有bear的优势,且弥补了bear的不足;性能方面也有优势。改进之处包括:

  1. 可以跳过时间构建而只生成compile_commands.json,如compiledb -n make

  2. 可以基于build-log来生成编译数据库。

    make -Bnwk > build-log.txt
    compiledb --parse build-log.txt # 或 compiledb < build-log.txt
    # 使用管道的方式也行
    make -Bnwk | compiledb -o-
    
  3. 使用command风格的编译数据库:

    compiledb --command-style make # 根据我的实验,在已经构建完成之后使用该命令,仍然能够得到编译数据库
    

不足:

  • 仅能够对make指令进行wrap。
    # 可编译
    > gcc testnum.c main.c
    # bear可wrap
    > bear -- gcc testnum.c main.c
    #compiledb不可wrap
    > compiledb  gcc testnum.c main.c
    Usage: compiledb [OPTIONS] COMMAND [ARGS]...
    Try 'compiledb -h' for help.
    
    Error: No such command 'gcc'.
    

TODO:

  • 补全citnamesintercept命令行的使用(bear相关)。
  • 补全交叉编译时的问题。
  • 补全编译数据库供基于clang的分析工具时的局限性。