JSON 编译数据库格式规范

本文档描述了一种格式,用于指定如何独立于构建系统重放单个编译。

背景

基于 C++ 抽象语法树的工具需要完整的关于如何解析翻译单元的信息。通常,此信息隐式存在于构建系统中,但将工具作为构建系统的一部分运行并不一定是最佳解决方案。

  • 构建系统本质上是变化驱动的,因此在不更改代码的情况下对同一代码库运行多个工具并不适合许多构建系统的架构。

  • 弄清楚事物是否发生变化通常是一个与 IO 相关的过程;这使得很难构建基于构建系统的低延迟最终用户工具。

  • 构建系统在构建图中本质上是顺序的,例如由于生成的源代码。虽然独立于构建运行的工具仍然需要生成的源代码存在,但多次对不变的源代码运行工具不需要根据构建依赖关系图对运行进行序列化。

支持的系统

Clang 能够通过 -MJ argument <clang -MJ\<arg>> 生成编译数据库片段。您可以将这些片段在 [] 之间连接起来以创建编译数据库。

目前 CMake(自 2.8.5 起)支持使用选项 CMAKE_EXPORT_COMPILE_COMMANDS 为 Unix Makefile 构建(Ninja 构建正在进行中)生成编译数据库。

对于 Linux 上的项目,可以使用名为 Bear 的工具来拦截编译器调用。

Bazel 可以通过 这个提取器扩展 导出编译数据库。Bazel 否则会抵制 Bear 和其他编译器拦截技术。

Clang 的工具接口支持读取编译数据库;请参阅 LibTooling 文档。libclang 及其 python 绑定也支持此功能(自 clang 3.2 起);请参阅 CXCompilationDatabase.h

格式

编译数据库是一个 JSON 文件,它包含一个“命令对象”数组,其中每个命令对象指定在项目中编译翻译单元的一种方式。

每个命令对象包含翻译单元的主文件、编译运行的工作目录以及实际的编译命令。

示例

[
  { "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" },

  ...
]

命令对象中每个字段的契约如下

  • directory: 编译的工作目录。commandfile 字段中指定的所有路径必须是绝对路径或相对于此目录的路径。

  • file: 此编译步骤处理的主要翻译单元源代码。工具将此用作编译数据库中的键。同一个文件可能有多个命令对象,例如,如果同一个源文件使用不同的配置进行编译。

  • arguments: 编译命令 argv 作为字符串列表。这应该为翻译单元 file 运行编译步骤。 arguments[0] 应该是可执行文件名,例如 clang++。参数不应该转义,而是准备传递给 execvp()

  • command: 作为单个 shell 转义字符串的编译命令。参数可以根据平台约定使用“"”和“\”进行 shell 引用和转义,其中“"”和“\”是唯一特殊字符。不支持 shell 扩展。

    argumentscommand 是必需的。arguments 是首选的,因为 shell(未)转义是错误的潜在来源。

  • output: 此编译步骤创建的输出的名称。此字段是可选的。它可用于区分同一输入文件的不同处理模式。

构建系统集成

约定是将文件命名为 compile_commands.json 并将其放在构建目录的顶部。Clang 工具被指向构建目录的顶部以检测该文件并使用编译数据库来解析源代码树中的 C++ 代码。

备选方案

对于简单的项目,Clang 工具还识别 compile_flags.txt 文件。这应该包含每行一个参数。相同的标志将用于编译任何文件。

示例

-xc++
-I
libwidget/include/

这里 -I libwidget/include 是两个参数,因此变为两行。路径相对于包含 compile_flags.txt 的目录。