如何在 LLVM 上设置 Clang 工具

Clang Tooling 提供基础设施来编写需要程序语法和语义信息的工具。该术语还涉及使用此基础设施的一组特定工具(例如 clang-check)。本文档提供了有关如何在 LLVM 源代码中设置和使用 Clang Tooling 的信息。

简介

Clang Tooling 需要一个编译数据库来找出每个文件的特定构建选项。目前它可以从 compile_commands.json 文件(由 CMake 生成)创建编译数据库。在调用 clang 工具时,您可以使用命令行参数 -p 指定构建目录的路径,也可以让 Clang Tooling 在您的源代码树中查找该文件。无论哪种情况,您都需要使用 CMake 配置您的构建以使用 clang 工具。

使用 CMake 和 Make 设置 Clang Tooling

如果您打算使用 make 构建 LLVM,您应该安装 CMake 2.8.6 或更高版本(可以在 这里 找到)。

首先,您需要使用 CMake 为 LLVM 生成 Makefile。您需要创建一个构建目录并从中运行 CMake

$ mkdir your/build/directory
$ cd your/build/directory
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON path/to/llvm/sources

如果您想使用 clang 而不是 GCC,您可以添加 -DCMAKE_C_COMPILER=/path/to/clang -DCMAKE_CXX_COMPILER=/path/to/clang++。您也可以使用 ccmake,它提供了一个 curses 接口来配置 CMake 变量。

结果,新的 compile_commands.json 文件应该出现在当前目录中。您应该将其链接到 LLVM 源代码树,以便 Clang Tooling 能够使用它

$ ln -s $PWD/compile_commands.json path/to/llvm/source/

现在,您可以使用 make 构建和测试 LLVM

$ make check-all

在 Windows 上使用 CMake 设置 Clang Tooling

对于 Windows 开发人员,CMake 中的 Visual Studio 项目生成器不支持 CMAKE_EXPORT_COMPILE_COMMANDS。但是,Ninja 生成器确实支持此变量,并且可以在 Windows 上使用它来生成一个合适的 compile_commands.json,该文件调用 MSVC 编译器。

首先,您需要安装 Ninja。安装完成后,Ninja 可执行文件需要在您的搜索路径中,以便 CMake 能够找到它。

接下来,假设您已经在机器上安装了 Visual Studio,您需要配置相应的环境变量,以便 CMake 能够为 Ninja 生成器找到 MSVC 编译器。文档 描述了必要的环境变量设置,但最简单的方法是使用 开发人员命令提示符窗口 或调用 开发人员命令文件 以适当地设置环境变量。

现在,您可以使用 Ninja 生成器运行 CMake 来导出编译数据库

C:\> mkdir build-ninja
C:\> cd build-ninja
C:\build-ninja> cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON path/to/llvm/sources

最好将您的 Visual Studio IDE 构建文件夹与 Ninja 构建文件夹分开。这可以防止两个构建系统相互之间产生负面影响。

一旦 compile_commands.json 文件由 Ninja 创建,您就可以将该编译数据库与 Clang Tooling 一起使用。需要注意的是,由于存在通过环境变量获得的间接设置,您可能需要通过为使用 Visual Studio 创建的命令提示符窗口运行任何 Clang Tooling 可执行文件,如上所述。另一种方法(例如,在 Clang Tooling 可执行文件上使用 Visual Studio 调试器)是确保环境变量也对调试器设置可见。这可以通过在 Visual Studio 的调试器配置中本地执行,也可以通过从合适的命令提示符窗口启动 Visual Studio IDE 来全局执行。

使用 Clang 工具

完成以上步骤后,您就可以运行 clang 工具了。如果您安装了最新的 clang,您应该在 $PATH 中有 clang-check。尝试在 LLVM 源代码树中的任何 .cpp 文件上运行它

$ clang-check tools/clang/lib/Tooling/CompilationDatabase.cpp

如果您使用的是 vim,那么将 clang-check 集成起来很方便。将此内容放入您的 .vimrc

function! ClangCheckImpl(cmd)
  if &autowrite | wall | endif
  echo "Running " . a:cmd . " ..."
  let l:output = system(a:cmd)
  cexpr l:output
  cwindow
  let w:quickfix_title = a:cmd
  if v:shell_error != 0
    cc
  endif
  let g:clang_check_last_cmd = a:cmd
endfunction

function! ClangCheck()
  let l:filename = expand('%')
  if l:filename =~ '\.\(cpp\|cxx\|cc\|c\)$'
    call ClangCheckImpl("clang-check " . l:filename)
  elseif exists("g:clang_check_last_cmd")
    call ClangCheckImpl(g:clang_check_last_cmd)
  else
    echo "Can't detect file's compilation arguments and no previous clang-check invocation!"
  endif
endfunction

nmap <silent> <F5> :call ClangCheck()<CR><CR>

在编辑 .cpp/.cxx/.cc/.c 文件时,按 F5 重新解析该文件。如果当前文件具有不同的扩展名(例如,.h),F5 将重新运行从该 vim 实例(如果有)发出的最后一次 clang-check 调用。输出将进入错误窗口,该窗口在 clang-check 发现错误时会自动打开,并且可以使用 :cope 重新打开。

其他在使用 clang AST 时可能会有用的 clang-check 选项

  • -ast-print - 构建 AST,然后漂亮地打印它们。

  • -ast-dump - 构建 AST,然后调试转储它们。

  • -ast-dump-filter=<string> - 与 -ast-dump-ast-print 一起使用,仅转储/打印在限定名称中具有特定子字符串的 AST 声明节点。使用 -ast-list 列出所有可过滤的声明节点名称。

  • -ast-list - 构建 AST 并打印声明节点限定名称列表。

示例

$ clang-check tools/clang/tools/clang-check/ClangCheck.cpp -ast-dump -ast-dump-filter ActionFactory::newASTConsumer
Processing: tools/clang/tools/clang-check/ClangCheck.cpp.
Dumping ::ActionFactory::newASTConsumer:
clang::ASTConsumer *newASTConsumer() (CompoundStmt 0x44da290 </home/alexfh/local/llvm/tools/clang/tools/clang-check/ClangCheck.cpp:64:40, line:72:3>
  (IfStmt 0x44d97c8 <line:65:5, line:66:45>
    <<<NULL>>>
      (ImplicitCastExpr 0x44d96d0 <line:65:9> '_Bool' <UserDefinedConversion>
...
$ clang-check tools/clang/tools/clang-check/ClangCheck.cpp -ast-print -ast-dump-filter ActionFactory::newASTConsumer
Processing: tools/clang/tools/clang-check/ClangCheck.cpp.
Printing <anonymous namespace>::ActionFactory::newASTConsumer:
clang::ASTConsumer *newASTConsumer() {
    if (this->ASTList.operator _Bool())
        return clang::CreateASTDeclNodeLister();
    if (this->ASTDump.operator _Bool())
        return clang::CreateASTDumper(nullptr /*Dump to stdout.*/,
                                      this->ASTDumpFilter);
    if (this->ASTPrint.operator _Bool())
        return clang::CreateASTPrinter(&llvm::outs(), this->ASTDumpFilter);
    return new clang::ASTConsumer();
}

使用 Ninja 构建系统

您可以选择使用 Ninja 构建系统,而不是 make。它旨在使您的构建速度更快。目前,此步骤将需要从源代码构建 Ninja。

要利用使用 Clang Tools 与 Ninja 构建的优势,您至少需要 CMake 2.8.9。

克隆 Ninja git 存储库并从源代码构建 Ninja

$ git clone git://github.com/martine/ninja.git
$ cd ninja/
$ ./bootstrap.py

这将导致当前目录中出现单个二进制文件 ninja。它不需要安装,只需复制到 $PATH 中的任何位置即可,例如 /usr/local/bin/

$ sudo cp ninja /usr/local/bin/
$ sudo chmod a+rx /usr/local/bin/ninja

完成所有这些操作后,您需要使用 CMake 为 LLVM 生成 Ninja 构建文件。您需要创建一个构建目录并从中运行 CMake

$ mkdir your/build/directory
$ cd your/build/directory
$ cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON path/to/llvm/sources

如果您想使用 clang 而不是 GCC,您可以添加 -DCMAKE_C_COMPILER=/path/to/clang -DCMAKE_CXX_COMPILER=/path/to/clang++。您也可以使用 ccmake,它以交互方式提供了一个 curses 接口来配置 CMake 变量。

结果,新的 compile_commands.json 文件应该出现在当前目录中。您应该将其链接到 LLVM 源代码树,以便 Clang Tooling 能够使用它

$ ln -s $PWD/compile_commands.json path/to/llvm/source/

现在,您可以使用 Ninja 构建和测试 LLVM

$ ninja check-all

其他目标名称可以像使用 make 一样使用。