本章提纲
1、LLVM的编译
2、Clang小插件的实现与集成
1.LLVM的编译
准备工作:需要下载好LLVM
和Clang
, cmake
1.1LLVM的下载和配置
国内网限制,我们需要借助镜像下载,镜像地址:https://mirror.tuna.tsinghua.edu.cn/help/llvm/
- 下载LLVM命令
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
- 在LLVM的tools目录下下载Clang
cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
- 在LLVM的projects目录下下载compiler-rt,libcxx,libcxxabi
cd ../projects
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.g
it
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
- 在Clang的tools下安装extra工具
cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-e
xtra.git
- 安装cmake
由于最新的LLVM只支持cmake来编译,还需要安装cmake,通过命令brew list
查看是否安装过cmake
,没有就执行命令brew install cmake
,M1的Mac执行arch -arm64 brew install cmake
安装下。
做好这些准备工作,下一步就可以开始编译LLVM
了。
编译过程
-
llvm工程的编译
我们通过Xcode编译LLVM。使用
cmake
把llvm编译成
Xcode项目,命令如下:cd llvm-project mkdir lucky_build_xcode cd lucky_build_xcode cmake -G Xcode ../llvm
因为我进行编译的时候报错了,编译不过。主要是
cmake
报错,我安装的可能有问题,就去检查了下。也没发现啥,又通过下载的完整的llvm
项目,然后在内部进行配置就编译成功了。
我回想了下,和之前不成功的情况做了如下改动:
1、参考网络的资料,因为我是新版的mac,默认的shell是zsh,所以我终端输入了:echo 'export OSX_COMMANDLINE_SDKROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"' >> ~/.zshrc source ~/.zshrc
这两行命令。
2、我下载了完整的llvm
,然后在llvm-project
中进行llvm
的配置。
如图所标记,我的
llvm-project
中和llvm->tools
中都有需要配置的文件,之前我都是把文件直接拖进tools
中,我不知道llvm-project
中的clang
对编译是不是有影响,但是不进行拖拽,进行拷贝之后就莫名其妙成功了。。。。。。我也很迷茫。ps:因为我从git下载相关配置失败了,一直报找不到库,所以就从完整的里边拷贝给llvm。
至此
llvm
的Xcode
项目生成了,我们来下一步操作。 -
Xcode对Clang的编译
进入到lucky_build_xcode
打开LLVM.xcodeproj
,进入Xcode
界面:
这里如果选择了自动创建,编译时间会非常慢,所以尽量选择手动添加,使用哪个添加哪个就好了。自定义插件需要clang
和clangTooling
,把这两个添加进来然后分别编译一下就好了。
!??!??感觉电脑要炸了??!??!
等了大概半个小时左右吧,接着进行下一个操作Clang
插件的编写。
2.Clang小插件的实现与集成
自定义小插件的需求:编译器检测到NSString
、NSArray
、NSDictionary
的修饰属性不为copy
时,进行警告。
2.1创建插件
在llvm-project/llvm/tools/clang/tools目录下新建插件文件夹LuckyPlugin
2.2添加插件
在目录llvm-project/llvm/tools/clang/tools
下的CMakeLists.txt
文件最下边添加add_clang_subdirectory(LuckyPlugin)
。
在文件夹LuckyPlugin
中新建一个名为LuckyPlugin.cpp
的文件和CMakeLists.txt的文件。
在文件CMakeLists.txt
中添加如下代码:
add_llvm_library( XJPlugin MODULE BUILDTREE_ONLY
LuckyPlugin.cpp
)
然后使用cmake
重新构建一下Xcode项目,终端进入lucky_build_xcode
目录,重新build一下。然后把新建的插件LuckyPlugin
也导入进来。
2.3插件代码的编写
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
using namespace llvm;
using namespace ast_matchers;
namespace LuckyPlugin{
class LuckyMatchCallBack:public MatchFinder::MatchCallback{
private:
CompilerInstance &CI;
//判断是否是自己的文件
bool isUserSourceCode(const string fileName){
//查到了系统文件 所以返回false 非用户文件。
if(fileName.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
//判断是否应该用copy修饰
bool isShouldUseCopy(const string typeStr){
if(typeStr.find("NSString") != string::nops || typeStr.find("NSArray") != string::npos || typeStr.find("NSDictionary") != string::npos){
return true;
}
return false;
}
public:
LuckyMatchCallBack(CompilerInstance &CI):CI(CI){}
void run(const MatchFinder::MatchResult &Result){
//通过结果获取到节点对象
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
//获取文件名称(包含路径的)
string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
//节点存在 是用户文件
if(propertyDecl && isUserSourceCode(fileName)){
//节点类型
string typeStr = propertyDecl->getType().getAsString();
//拿到节点的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//应该使用copy但是没用
if(isShouldUseCopy(typeStr)&&!(attrKind&ObjCPropertyDecl::OBJC_PR_copy)){
//引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 报告
diag.Report(propertyDecl->getLocation(),diag.getCustomDiagID(DiagnosticsEngine::Warning,"这个地方应该用Copy"));
}
}
}
};
//自定义LuckyConsumer
class LuckyConsumer:public ASTConsumer{
private:
//节点过滤器
MatchFinder matcher;
LuckyMatchCallBack callback;
public:
LuckyConsumer(CompilerInstance &CI):callback(CI){
//添加一个MatchFinder去匹配ObjCPropertyDecl节点
//回调!
matcher.addMatcher(ObjCPropertyDecl().bind("objcPropertyDecl"),&callback);
}
//解析完毕一个顶级的声明就回调一次
bool handleTopLevelDecl(DeclGroupRef D){
return true;
}
//当整个文件都解析完成后回调??!
void HandleTranslationUnit(ASTContext &Ctx){
cout<<"文件解析完毕~~"<<endl;
matcher.matchAST(Ctx);
}
}
class LuckyASTAction:public PluginASTAction{
public:
bool ParseArgs(const CompilerInstance &CI,const vector<string> &arg){
return true;
}
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,StringRef InFile){
return unique_ptr<LuckyConsumer> (new LuckyConsumer(CI));
}
}
}
//第一步注册插件
static FrontendPluginRegistry::Add<LuckyPlugin::LuckyASTAction> X("LuckyPlugin","this is the description");
解析:
整个过程大致分为三步:
1、注册插件,并关联自定义类LuckyASTAction
。
LuckyASTAction类继承自PluginASTAction
,重载函数ParseArgs
,CreateASTConsumer
。
2、扫描配置
自定义类LuckyASTConsumer,继承自类ASTConsumer
。重写方法
HandleTopLevelDecl
:解析完一个顶级节点就回调一次;
HandleTranslationUnit
:解析完整个文件回调,将文件解析完毕后的上下文传给matcher
。
3、扫描完毕的回调
自定义了回调类LuckyMatchCallBack
,继承自MatchCallback
,声明私有变量CI,用于接收ASTConsumer类传递过来的CI
。重写run
方法。
- 通过
Result
根据节点id
获取节点对象。 - 判断节点是否存在且文件是否为用户文件
- 获取节点的描述信息,获取节点的属性
- 判断属性是否需要使用
copy
,但是没有使用 - 获取引擎诊断
- 通过引擎报告错误
2.4Xcode集成自定义插件
- 配置插件
打开测试项目,在Build Settings->Other C Flags添加上如下内容:
-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang LuckyPlugin
- 设置编译器
由于Clang
插件需要使用对应的版本去加载,如果版本不一致则会导致编译错误Expected in: flat namespace
。- 新增自定义用户设置
在Build Settings 栏目中新增两项用户自定义设置Add User-Defined Setting
分别是CC和CXX:
CC:对应的是自己编译的clang的绝对路径
CXX:对应的是自己编译的clang++的绝对路径。 - 接下来在Build Settings栏目中搜索index,将Enable Index-While-Building Functionality的Default改为NO。
- 新增自定义用户设置
至此,插件集成完毕。
友情提示:集成这个插件前后操作大概需要50G的硬盘空间支持。