本章提綱
1、LLVM的編譯
2、Clang小插件的實(shí)現(xiàn)與集成
1.LLVM的編譯
準(zhǔn)備工作:需要下載好LLVM和Clang, cmake
1.1LLVM的下載和配置
國(guó)內(nèi)網(wǎng)限制,我們需要借助鏡像下載,鏡像地址: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來(lái)編譯,還需要安裝cmake,通過(guò)命令brew list查看是否安裝過(guò)cmake,沒(méi)有就執(zhí)行命令brew install cmake,M1的Mac執(zhí)行arch -arm64 brew install cmake安裝下。
做好這些準(zhǔn)備工作,下一步就可以開(kāi)始編譯LLVM了。
編譯過(guò)程
-
llvm工程的編譯
我們通過(guò)Xcode編譯LLVM。使用
cmake把llvm編譯成Xcode項(xiàng)目,命令如下:cd llvm-project mkdir lucky_build_xcode cd lucky_build_xcode cmake -G Xcode ../llvm因?yàn)槲疫M(jìn)行編譯的時(shí)候報(bào)錯(cuò)了,編譯不過(guò)。主要是
cmake報(bào)錯(cuò),我安裝的可能有問(wèn)題,就去檢查了下。也沒(méi)發(fā)現(xiàn)啥,又通過(guò)下載的完整的llvm項(xiàng)目,然后在內(nèi)部進(jìn)行配置就編譯成功了。
編譯成功截圖
我回想了下,和之前不成功的情況做了如下改動(dòng):
1、參考網(wǎng)絡(luò)的資料,因?yàn)槲沂切掳娴膍ac,默認(rèn)的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中進(jìn)行llvm的配置。
llvm-project如圖所標(biāo)記,我的
llvm-project中和llvm->tools中都有需要配置的文件,之前我都是把文件直接拖進(jìn)tools中,我不知道llvm-project中的clang對(duì)編譯是不是有影響,但是不進(jìn)行拖拽,進(jìn)行拷貝之后就莫名其妙成功了。。。。。。我也很迷茫。ps:因?yàn)槲覐膅it下載相關(guān)配置失敗了,一直報(bào)找不到庫(kù),所以就從完整的里邊拷貝給llvm。
至此
llvm的Xcode項(xiàng)目生成了,我們來(lái)下一步操作。 -
Xcode對(duì)Clang的編譯
進(jìn)入到lucky_build_xcode打開(kāi)LLVM.xcodeproj,進(jìn)入Xcode界面:
LLVM.xcodeproj
這里如果選擇了自動(dòng)創(chuàng)建,編譯時(shí)間會(huì)非常慢,所以盡量選擇手動(dòng)添加,使用哪個(gè)添加哪個(gè)就好了。自定義插件需要clang和clangTooling,把這兩個(gè)添加進(jìn)來(lái)然后分別編譯一下就好了。

?。?!??感覺(jué)電腦要炸了???。。?br>
等了大概半個(gè)小時(shí)左右吧,接著進(jìn)行下一個(gè)操作Clang插件的編寫(xiě)。
2.Clang小插件的實(shí)現(xiàn)與集成
自定義小插件的需求:編譯器檢測(cè)到NSString、NSArray、NSDictionary的修飾屬性不為copy時(shí),進(jìn)行警告。
2.1創(chuàng)建插件
在llvm-project/llvm/tools/clang/tools目錄下新建插件文件夾LuckyPlugin

2.2添加插件
在目錄llvm-project/llvm/tools/clang/tools下的CMakeLists.txt文件最下邊添加add_clang_subdirectory(LuckyPlugin)。
在文件夾LuckyPlugin中新建一個(gè)名為LuckyPlugin.cpp的文件和CMakeLists.txt的文件。
在文件CMakeLists.txt中添加如下代碼:
add_llvm_library( XJPlugin MODULE BUILDTREE_ONLY
LuckyPlugin.cpp
)
然后使用cmake重新構(gòu)建一下Xcode項(xiàng)目,終端進(jìn)入lucky_build_xcode目錄,重新build一下。然后把新建的插件LuckyPlugin也導(dǎo)入進(jìn)來(lái)。
2.3插件代碼的編寫(xiě)
#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){
//查到了系統(tǒng)文件 所以返回false 非用戶(hù)文件。
if(fileName.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
//判斷是否應(yīng)該用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){
//通過(guò)結(jié)果獲取到節(jié)點(diǎn)對(duì)象
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
//獲取文件名稱(chēng)(包含路徑的)
string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
//節(jié)點(diǎn)存在 是用戶(hù)文件
if(propertyDecl && isUserSourceCode(fileName)){
//節(jié)點(diǎn)類(lèi)型
string typeStr = propertyDecl->getType().getAsString();
//拿到節(jié)點(diǎn)的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//應(yīng)該使用copy但是沒(méi)用
if(isShouldUseCopy(typeStr)&&!(attrKind&ObjCPropertyDecl::OBJC_PR_copy)){
//引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報(bào)告
diag.Report(propertyDecl->getLocation(),diag.getCustomDiagID(DiagnosticsEngine::Warning,"這個(gè)地方應(yīng)該用Copy"));
}
}
}
};
//自定義LuckyConsumer
class LuckyConsumer:public ASTConsumer{
private:
//節(jié)點(diǎn)過(guò)濾器
MatchFinder matcher;
LuckyMatchCallBack callback;
public:
LuckyConsumer(CompilerInstance &CI):callback(CI){
//添加一個(gè)MatchFinder去匹配ObjCPropertyDecl節(jié)點(diǎn)
//回調(diào)!
matcher.addMatcher(ObjCPropertyDecl().bind("objcPropertyDecl"),&callback);
}
//解析完畢一個(gè)頂級(jí)的聲明就回調(diào)一次
bool handleTopLevelDecl(DeclGroupRef D){
return true;
}
//當(dāng)整個(gè)文件都解析完成后回調(diào)!!
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));
}
}
}
//第一步注冊(cè)插件
static FrontendPluginRegistry::Add<LuckyPlugin::LuckyASTAction> X("LuckyPlugin","this is the description");
解析:
整個(gè)過(guò)程大致分為三步:
1、注冊(cè)插件,并關(guān)聯(lián)自定義類(lèi)LuckyASTAction。
LuckyASTAction類(lèi)繼承自PluginASTAction,重載函數(shù)ParseArgs,CreateASTConsumer。
2、掃描配置
自定義類(lèi)LuckyASTConsumer,繼承自類(lèi)ASTConsumer。重寫(xiě)方法
HandleTopLevelDecl:解析完一個(gè)頂級(jí)節(jié)點(diǎn)就回調(diào)一次;
HandleTranslationUnit:解析完整個(gè)文件回調(diào),將文件解析完畢后的上下文傳給matcher。
3、掃描完畢的回調(diào)
自定義了回調(diào)類(lèi)LuckyMatchCallBack,繼承自MatchCallback,聲明私有變量CI,用于接收ASTConsumer類(lèi)傳遞過(guò)來(lái)的CI。重寫(xiě)run方法。
- 通過(guò)
Result根據(jù)節(jié)點(diǎn)id獲取節(jié)點(diǎn)對(duì)象。 - 判斷節(jié)點(diǎn)是否存在且文件是否為用戶(hù)文件
- 獲取節(jié)點(diǎn)的描述信息,獲取節(jié)點(diǎn)的屬性
- 判斷屬性是否需要使用
copy,但是沒(méi)有使用 - 獲取引擎診斷
- 通過(guò)引擎報(bào)告錯(cuò)誤
2.4Xcode集成自定義插件
- 配置插件
打開(kāi)測(cè)試項(xiàng)目,在Build Settings->Other C Flags添加上如下內(nèi)容:
-Xclang -load -Xclang (.dylib)動(dòng)態(tài)庫(kù)路徑 -Xclang -add-plugin -Xclang LuckyPlugin
- 設(shè)置編譯器
由于Clang插件需要使用對(duì)應(yīng)的版本去加載,如果版本不一致則會(huì)導(dǎo)致編譯錯(cuò)誤Expected in: flat namespace。- 新增自定義用戶(hù)設(shè)置
在Build Settings 欄目中新增兩項(xiàng)用戶(hù)自定義設(shè)置Add User-Defined Setting
分別是CC和CXX:
CC:對(duì)應(yīng)的是自己編譯的clang的絕對(duì)路徑
CXX:對(duì)應(yīng)的是自己編譯的clang++的絕對(duì)路徑。 - 接下來(lái)在Build Settings欄目中搜索index,將Enable Index-While-Building Functionality的Default改為NO。
- 新增自定義用戶(hù)設(shè)置
至此,插件集成完畢。
友情提示:集成這個(gè)插件前后操作大概需要50G的硬盤(pán)空間支持。


