我有一个基本的 clang AST 访问者(在 chatGPT 的帮助下构建)
#include <iostream>
#include <llvm/Support/CommandLine.h>
#include <clang/AST/ASTConsumer.h>
#include <clang/ASTMatchers/ASTMatchers.h>
#include <clang/ASTMatchers/ASTMatchFinder.h>
#include <clang/AST/RecordLayout.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendAction.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Rewrite/Frontend/FrontendActions.h>
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tooling;
static llvm::cl::OptionCategory MyToolCategory("my-tool options");
class OffsetOfVisitor : public RecursiveASTVisitor<OffsetOfVisitor> {
public:
explicit OffsetOfVisitor(ASTContext *Context)
: Context(Context) {}
bool VisitFieldDecl(FieldDecl *FD) {
const RecordDecl *Parent = FD->getParent();
std::string FieldName = FD->getNameAsString();
if (FieldName.empty()) {
return true;
}
std::string ParentName;
if (const TypedefNameDecl *TND = Parent->getTypedefNameForAnonDecl()) {
ParentName = TND->getNameAsString();
}
if (ParentName.empty()) {
return true;
}
llvm::outs() << ParentName << ' ' << FieldName << "\n";
return true;
}
private:
ASTContext *Context;
};
class OffsetOfConsumer : public ASTConsumer {
public:
explicit OffsetOfConsumer(ASTContext *Context)
: Visitor(Context) {}
void HandleTranslationUnit(ASTContext &Context) override {
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
OffsetOfVisitor Visitor;
};
class OffsetOfAction : public ASTFrontendAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef File) override {
return std::make_unique<OffsetOfConsumer>(&CI.getASTContext());
}
};
int main(int argc, const char **argv) {
auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory);
if (!ExpectedParser) {
return 1;
}
clang::tooling::CommonOptionsParser &OptionsParser = ExpectedParser.get();
clang::tooling::ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());
int result = Tool.run(newFrontendActionFactory<OffsetOfAction>().get());
return result;
}
// vim: et ts=4 sw=4
但是当我运行该工具时,它无法找到一些依赖的包含文件:
# ../visitor t2.cpp
In file included from /home/pjoot/vv/test/t2.cpp:1:
/usr/include/stdio.h:33:10: fatal error: 'stddef.h' file not found
33 | #include <stddef.h>
| ^~~~~~~~~~
__fsid_t __val
__mbstate_t __count
__mbstate_t __value
1 error generated.
Error while processing /home/pjoot/vv/test/t2.cpp.
如果我运行 clang 预处理器,我会看到 stddef.h 在 clang 特定路径中找到:
# 28 "/usr/include/stdio.h" 2 3 4
extern "C" {
# 1 "/usr/bin/../lib/clang/17/include/stddef.h" 1 3 4
因此我可以通过使用附加包含路径调用该工具来解决此故障,如下所示:
../visitor t2.cpp -- -I /usr/bin/../lib/clang/17/include
两个问题:
编辑:
斯科特的答案有效。 我添加了一个 make 规则来将我的工具和所有依赖的头文件复制到适当的目录结构中:
> make install
rm -rf tool
mkdir -p tool/bin tool/lib/clang/17
install visitor tool/bin/
cp -a /usr/bin/../lib/clang/17/include tool/lib/clang/17/include
> find tool -name stddef.h
tool/lib/clang/17/include/stddef.h
> cd test
> ../tool/bin/visitor t2.cpp
__fsid_t __val
__mbstate_t __count
__mbstate_t __value
我想我也可以使用符号链接来避免复制 220 个文件。 在我的 makefile 中对内部 clang 路径进行硬编码有点难看,但我可以忍受。
虽然措辞完全不同,但问题 Bash 调用的 Clang 和 ClangTool 之间有什么区别? 解决了相同的根本问题,并且其 answer(由我写)是相关的。
我不确定这是否应该被视为重复的问题(部分原因是另一个问题的标题有些误导性),所以现在我只引用答案的关键部分:
解析 C++ 需要的不仅仅是一个可以读取 C++ 语法的程序。它还需要某些逻辑上属于编译器而不是 C 库的头文件; stddef.h 是前者之一,而(比如说)stdio.h 是后者之一。如果您使用 LibTooling 创建解析 C++ 的可执行文件,那么如果没有编译器标头,它是不完整的,就像缺少艺术资源的视频游戏可执行文件一样。
并且:
正确的方法是让你的工具有一个“安装”步骤或类似的步骤,将 Clang 编译器头和可执行文件复制到后者运行的位置。您需要复制 lib/clang/$(version)/include 中的每个文件。
链接的问题后来被编辑以添加一个
CMakelists.txt
片段来进行复制,尽管我还没有测试它。
一旦正确的标头与可执行文件一起安装,它将能够在解析过程中找到并使用它们。
链接的答案(以及其他几个类似的 Q+As 它链接到的答案)还建议了使用
clang
编译器头而不复制它们的方法,但这充其量只是在工具开发过程中有用的一种 hack。