LibTooling

LibTooling 是一个用于支持基于 Clang 编写独立工具的库。本文档将提供一个使用 LibTooling 编写工具的基本流程。

有关如何为 LLVM 设置 Clang Tooling 的信息,请参阅 如何为 LLVM 设置 Clang Tooling

介绍

使用 LibTooling 构建的工具(如 Clang 插件)会在代码上运行 FrontendActions

在本教程中,我们将演示在大量代码上运行 Clang 的 SyntaxOnlyAction 的不同方法,该方法进行快速语法检查。

在内存中解析代码片段

如果您想在一些示例代码上运行 FrontendAction,例如对 Clang AST 的部分进行单元测试,那么 runToolOnCode 正是您所需要的。我举个例子:

#include "clang/Tooling/Tooling.h"

TEST(runToolOnCode, CanSyntaxCheckCode) {
  // runToolOnCode returns whether the action was correctly run over the
  // given code.
  EXPECT_TRUE(runToolOnCode(std::make_unique<clang::SyntaxOnlyAction>(), "class X {};"));
}

编写独立工具

当您对 FrontendAction 进行单元测试到它不可能出现错误时,就该创建一个独立工具了。为了使独立工具运行 clang,它首先需要确定对指定文件使用哪些命令行参数。为此,我们创建了一个 CompilationDatabase。创建编译数据库有不同的方法,我们需要根据命令行选项支持所有方法。有一个 CommonOptionsParser 类负责解析与编译数据库和输入相关的命令行参数,以便所有工具共享实现。

解析常用工具选项

CompilationDatabase 可以从构建目录或命令行读取。使用 CommonOptionsParser 允许显式指定编译命令行,使用 -p 命令行选项指定构建路径,并使用源文件路径自动定位编译数据库。

#include "clang/Tooling/CommonOptionsParser.h"
#include "llvm/Support/CommandLine.h"

using namespace clang::tooling;
using namespace llvm;

// Apply a custom category to all command-line options so that they are the
// only ones displayed.
static cl::OptionCategory MyToolCategory("my-tool options");

int main(int argc, const char **argv) {
  // CommonOptionsParser::create will parse arguments and create a
  // CompilationDatabase.
  auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory);
  if (!ExpectedParser) {
    // Fail gracefully for unsupported options.
    llvm::errs() << ExpectedParser.takeError();
    return 1;
  }
  CommonOptionsParser& OptionsParser = ExpectedParser.get();

  // Use OptionsParser.getCompilations() and OptionsParser.getSourcePathList()
  // to retrieve CompilationDatabase and the list of input file paths.
}

创建和运行 ClangTool

获得 CompilationDatabase 后,我们可以创建一个 ClangTool,并在一些代码上运行我们的 FrontendAction。例如,要对文件“a.cc”和“b.cc”运行 SyntaxOnlyAction,可以使用以下代码:

// A clang tool can run over a number of sources in the same process...
std::vector<std::string> Sources;
Sources.push_back("a.cc");
Sources.push_back("b.cc");

// We hand the CompilationDatabase we created and the sources to run over into
// the tool constructor.
ClangTool Tool(OptionsParser.getCompilations(), Sources);

// The ClangTool needs a new FrontendAction for each translation unit we run
// on.  Thus, it takes a FrontendActionFactory as parameter.  To create a
// FrontendActionFactory from a given FrontendAction type, we call
// newFrontendActionFactory<clang::SyntaxOnlyAction>().
int result = Tool.run(newFrontendActionFactory<clang::SyntaxOnlyAction>().get());

整合在一起 — 第一个工具

现在我们将前面的两个步骤结合起来,创建我们的第一个真正的工具。此示例工具的更高级版本也已签入 clang 树中,位于 tools/clang-check/ClangCheck.cpp

// Declares clang::SyntaxOnlyAction.
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
// Declares llvm::cl::extrahelp.
#include "llvm/Support/CommandLine.h"

using namespace clang::tooling;
using namespace llvm;

// Apply a custom category to all command-line options so that they are the
// only ones displayed.
static cl::OptionCategory MyToolCategory("my-tool options");

// CommonOptionsParser declares HelpMessage with a description of the common
// command-line options related to the compilation database and input files.
// It's nice to have this help message in all tools.
static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);

// A help message for this specific tool can be added afterwards.
static cl::extrahelp MoreHelp("\nMore help text...\n");

int main(int argc, const char **argv) {
  auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory);
  if (!ExpectedParser) {
    llvm::errs() << ExpectedParser.takeError();
    return 1;
  }
  CommonOptionsParser& OptionsParser = ExpectedParser.get();
  ClangTool Tool(OptionsParser.getCompilations(),
                 OptionsParser.getSourcePathList());
  return Tool.run(newFrontendActionFactory<clang::SyntaxOnlyAction>().get());
}

在一些代码上运行工具

签出并构建 clang 后,clang-check 已经构建完成,您可以在构建目录的 bin/clang-check 中找到它。

您可以通过在“--” 分隔符之后指定所有必要的参数,在 llvm 存储库中的文件中运行 clang-check:

$ cd /path/to/source/llvm
$ export BD=/path/to/build/llvm
$ $BD/bin/clang-check tools/clang/tools/clang-check/ClangCheck.cpp -- \
      clang++ -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS \
      -Itools/clang/include -I$BD/include -Iinclude \
      -Itools/clang/lib/Headers -c

或者,您也可以配置 cmake 将编译命令数据库输出到其构建目录中:

# Alternatively to calling cmake, use ccmake, toggle to advanced mode and
# set the parameter CMAKE_EXPORT_COMPILE_COMMANDS from the UI.
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .

这会在构建目录中创建一个名为 compile_commands.json 的文件。现在,您可以通过指定构建路径作为第一个参数,并将一些源文件作为其他位置参数,在项目中运行 clang-check

$ cd /path/to/source/llvm
$ export BD=/path/to/build/llvm
$ $BD/bin/clang-check -p $BD tools/clang/tools/clang-check/ClangCheck.cpp

内置包含

Clang 工具需要它们的内置头文件,并以与 Clang 相同的方式搜索它们。因此,查找内置头文件的默认位置是相对于工具二进制文件的路径 $(dirname /path/to/tool)/../lib/clang/3.3/include。对于从 llvm 的顶层二进制目录运行的工具,在构建 clang-resource-headers 之后,或者对于从 clang 安装的二进制目录(位于 clang 二进制文件旁边)运行的工具,这将正常工作。

提示:如果您的工具找不到 stddef.h 或类似的头文件,请使用 -v 调用该工具,并查看它搜索的路径。

链接

要查看要链接的库列表,请查看其中一个工具的 CMake 文件(例如 clang-check/CMakeList.txt)。