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)。