目錄
一池颈、LLVM編譯
1.1凡涩、LLVM 下載
當(dāng)前系統(tǒng)環(huán)境如下:
OS:macOS Big Sur搂鲫, 芯片:Apple Silicon M1
根據(jù)Mac當(dāng)前系統(tǒng)下載LLVM Releases源碼,保存的路徑中不要包含空格之類的字符潜必,這里選擇當(dāng)前最新的llvmorg-11.0.0源碼靴姿。
https://github.com/llvm/llvm-project/releases/tag/llvmorg-11.0.0
下載 Source code(zip)
1.2、LLVM編譯
由于最新的L LVM只支持
cmake
來編譯了磁滚,我們還需要安裝cmake
佛吓。
安裝 cmake
- 查看
brew
是否安裝cmake
如果有就跳過下面的步驟
brew list
- 通過brew安裝cmake
brew install cmake
1.2.1、通過Xcode編譯LLVM
cd llvm-project文件夾
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
cmake
完成后會在build_xcode
中出現(xiàn)Xcode
項(xiàng)目
在Xcode
中使用Automatically Create Schemes
然后選擇ALL_BUILD Schemes
編譯項(xiàng)目(Xcode
能編譯成功)
1.2.2恨旱、通過ninja編譯LLVM
- 使用
ninja
進(jìn)行編譯則還需要安裝ninja
辈毯。使用brew install ninja
命令即可安裝ninja
。 - 在
llvm
源碼文件夾中新建一個build_ninja
目錄搜贤, 最終會在build_ninja
目錄下生成build.ninja
谆沃。 - 在
llvm
源碼文件夾中新建一個llvm_release
目錄,最終編譯文件會在llvm_release
文件夾路徑下仪芒。
cd build_ninja
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機(jī)為/Users/xxx/xxx/LLVM/llvm_release)
注意DCMAKE_INSTALL_PREFIX后面不能有空格唁影。
- 依次執(zhí)行編譯、安裝指令掂名。
ninja
ninja install(這步執(zhí)行報(bào)錯据沈,可能是ninja未適配M1)
二、創(chuàng)建自定義Clang插件
2.1饺蔑、創(chuàng)建插件
1锌介、在/llvm-project/clang/tools
目錄下新建插件CJPlugin
2 、修改/llvm-project/clang/tools
目錄下的CMakeLists.txt
文件猾警,新增add_clang_subdirectory(CJPlugin)
3孔祸、在CJPlugin
目錄下新建一個名為CJPlugin.cpp
的文件和CMakeLists.txt
的文件。在CMakeLists.txt
中添加如下代碼:
add_llvm_library( CJPlugin MODULE BUILDTREE_ONLY
CJPlugin.cpp
)
4发皿、接下來利用cmake
重新生成一下Xcode
項(xiàng)目
cd build_xcode
cmake -G Xcode -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" ../llvm //此命令會編譯clang目錄下的自定義插件
5崔慧、最后可以在LLVM
的Xcode
項(xiàng)目中可以看到Loadable modules
目錄下有自己的Plugin
目錄了。我們可以在這里面編寫插件代碼穴墅。
2.2惶室、編寫插件代碼
#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 clang::ast_matchers;
namespace CJPlugin {
class CJASTConsumer: public ASTConsumer {
public:
// clang 解析完一個頂級的聲明的回調(diào)
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"正在解析..."<<endl;
return true;
}
// clang 解析完整個文件的回調(diào)
void HandleTranslationUnit(ASTContext &context) {
cout<<"文件解析完畢!"<<endl;
}
};
// 繼承PluginASTAction實(shí)現(xiàn)我們自定義的Action
class CJASTAction: public PluginASTAction {
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
return unique_ptr<CJASTConsumer> (new CJASTConsumer);
}
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &args) {
return true;
}
};
// 注冊插件
static FrontendPluginRegistry::Add<CJPlugin::CJASTAction> CJ("CJPlugin", "This is the description of the plugin");
}
2.3玄货、測試插件代碼
- 編譯
Xcode
項(xiàng)目 - 編寫測試源碼
vi hello.m
int sum (int a);
int a;
int sum (int a) {
int b = 10;
return a + b + 10;
}
int sum2 (int a, int b) {
int c = 10;
return a + b + c;
}
- 使用自己編譯的
clang
文件路徑和插件路徑測試
命令如下:
自己編譯的clang文件路徑 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk -Xclang -load -Xclang 插件(.dylib)路徑 -Xclang -add-plugin -Xclang CJPlugin(插件名) -c 源碼路徑
測試結(jié)果:
2.4皇钞、Xcode集成插件
1、新建測試項(xiàng)目Test_LLVM
誉结,并添加如下代碼:
@interface ViewController ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSArray *arrs;
@end
我們接下來使用我們自定義的
clang
插件提示屬性定義存在的問題鹅士。
使用原生的clang
解析ViewController.m
并生成AST
:
cd ViewController.m所在的目錄
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
從AST
中我們發(fā)現(xiàn)屬性定義節(jié)點(diǎn)名為ObjCPropertyDecl
,因此我們自定clang
中需要過濾出ObjCPropertyDecl
節(jié)點(diǎn)惩坑。
2掉盅、修改代碼過濾出ViewController.m
中的ObjCPropertyDecl
節(jié)點(diǎn)
namespace CJPlugin {
class CJMatchCallback: public MatchFinder::MatchCallback {
private:
CompilerInstance &CI;
// 排除系統(tǒng)的屬性
bool isUserSourceCode(const string filename) {
if (filename.empty()) return false;
if (filename.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
// 判斷是否使用copy修飾
bool isShouldUseCopy(const string typeStr) {
if (typeStr.find("NSString") != string::npos || typeStr.find("NSArray") != string::npos || typeStr.find("NSDictionary") != string::npos/*...*/) {
return true;
}
return false;
}
public:
CJMatchCallback(CompilerInstance &CI) :CI(CI) {}
void run(const MatchFinder::MatchResult &Result) {
// 通過Result獲取到節(jié)點(diǎn)
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();
cout<<"拿到屬性:"<<typeStr<<"屬于文件:"<<fileName<<endl;
// 拿到節(jié)點(diǎn)的描述信息
ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();
if (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyAttribute::kind_copy)) {
cout<<"推薦使用 copy 修飾"<<endl;
}
}
}
};
class CJASTConsumer: public ASTConsumer {
private:
MatchFinder matcher;// AST節(jié)點(diǎn)查找過濾器
CJMatchCallback callback;
public:
CJASTConsumer(CompilerInstance &CI) :callback(CI) {
// 添加一個MatchFinder去匹配objcPropertyDecl節(jié)點(diǎn)
// 回調(diào)在CJMatchCallback中的run方法中
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);//綁定標(biāo)記"objcPropertyDecl"和取的時(shí)候一致即可
}
// clang 解析完一個頂級的聲明的回調(diào)
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"正在解析..."<<endl;
return true;
}
// clang 解析完整個文件的回調(diào)
void HandleTranslationUnit(ASTContext &context) {
matcher.matchAST(context);
cout<<"文件解析完畢也拜!"<<endl;
}
};
// 繼承PluginASTAction實(shí)現(xiàn)我們自定義的Action
class CJASTAction: public PluginASTAction {
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
return unique_ptr<CJASTConsumer> (new CJASTConsumer(CI));//CI用于戶過濾系統(tǒng)屬性
}
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &args) {
return true;
}
};
// 注冊插件
static FrontendPluginRegistry::Add<CJPlugin::CJASTAction> CJ("CJPlugin", "This is the description of the plugin");
}
使用自定義clang
解析ViewController.m
代碼如下:
cd ViewController.m所在的目錄
/Users/ztkj/Projects/LLVM_Projects/llvm-project/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk -Xclang -load -Xclang /Users/ztkj/Projects/LLVM_Projects/llvm-project/build_xcode/Debug/lib/CJPlugin.dylib -Xclang -add-plugin -Xclang CJPlugin -c ViewController.m
測試結(jié)果:
3、添加Xcode顯示自定義提示的代碼
cout<<"推薦使用 copy 修飾"<<endl;
// 診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0推薦使用 copy 修飾"))<<typeStr;//%必須帶參數(shù)否者會報(bào)錯
測試結(jié)果:
4趾痘、加載插件
打開測試項(xiàng)目慢哈,在Build Settings -> Other C Flags
添加如下內(nèi)容:
-Xclang -load -Xclang (.dylib)動態(tài)庫路徑 -Xclang -add-plugin -Xclang CJPlugin
5、設(shè)置編譯器
-
由于
Clang
插件需要使用對應(yīng)的版本去加載永票,如果版本不一致就會導(dǎo)致編譯錯誤
卵贱,如下圖所示:
-
在
Build Settings
欄目中新增兩項(xiàng)用戶定義的設(shè)置
- 分別是
CC
和CXX
CC
對應(yīng)的是自己編譯的clang
的絕對路徑
CXX
對應(yīng)的是自己編譯的clang++
的絕對路徑
- 接下來在
Build Settings
欄目中搜索index
,將Enable Index-Wihle-Building Functionality的Default
改為NO
侣集。
6键俱、編譯Test_LLVM
項(xiàng)目成功后即可看到Xcode
顯示自定義clang
插件中的提示
目前自定義的插件必須編譯后才能顯示提示,代碼修改后也不會自動更新提示世分,且還沒完善Fix功能编振。