日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS之LLVM编译流程和Clang插件开发集成

發布時間:2024/5/28 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS之LLVM编译流程和Clang插件开发集成 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

LLVM 簡介

一、什么是 LLVM?
  • LLVM 是構架編譯器(compiler)的框架系統,以 C++ 編寫而成,用于優化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(runtime)以及空閑時間(idle-time),對開發者保持開放,并兼容已有腳本。
  • LLVM 最早的時候是 Illinois 的一個研究項目,主要負責人是 Chris Lattner,他現在就職于Apple。Apple 目前也是 LLVM 項目的主要贊助者之一。
  • 在理解 LLVM 時,我們可以認為它包括了一個狹義的 LLVM 和一個廣義的 LLVM。廣義的 LLVM 其實就是指整個 LLVM 編譯器架構,包括了前端、后端、優化器、眾多的庫函數以及很多的模塊;而狹義的 LLVM 其實就是聚焦于編譯器后端功能(代碼生成、代碼優化、JIT等)的一系列模塊和庫。
二、傳統編譯器設計
  • 傳統編譯器分三個階段: 前端(Frontend)-> 優化器(Optimizer)-> 后端(Backend);
    • 前端Frontend:負責分析源代碼,可以檢查語法級錯誤(包括詞法分析、語法分析、語義分析),并構建針對語言的抽象語法樹(AST:Abstract Syntax Tree);抽象語法樹可以進一步轉換為優化,最終轉為新的表示方式,然后再交給讓優化器和后端處理,LLVM 的前端還會生成中間代碼(intermediate representation,簡稱IR);最終由后端生成可執行的機器碼(可以理解為 LLVM 是編譯器 + 優化器, 接收的是 IR 中間代碼,輸出的還是 IR,給后端,經過后端翻譯成目標指令集);
    • 優化器 Optimizer:優化器負責進行各種優化,改善代碼的運行時間,例如消除冗余計算等;
    • 后端 Backend(代碼生成器 Code Generator):將代碼映射到目標指令集,生成機器代碼,并且進行機器代碼相關的代碼優化;
  • 源碼 Source Code + 前端 Frontend + 優化器 Optimizer + 后端 Backend(代碼生成器 CodeGenerator)+ 機器碼 Machine Code,如下所示:

  • LLVM 的優點在于,中間表示IR代碼編寫良好,而且不同的前端語言最終都轉換成同一種的IR。
三、iOS 的編譯器架構
  • OC、C、C++ 使用的編譯器前端是Clang,Swift是swift,后端都是LLVM,如下圖所示:

四、LLVM 的設計
  • LLVM 設計的最重要方面是,使用通用的代碼表示形式(IR),它是用來在編譯器中表示代碼的形式,所有 LLVM 可以為任何編程語言獨立編寫前端,并且可以為任意硬件架構獨立編寫后端,如下所示:

  • LLVM的優點:
    • 中間表示IR代碼編寫良好,而且不同的前端語言最終都轉換成統一的中間代碼LLVM IR(LLVM Intermediate Representation);
    • 如果需要支持一種新的變成語言,那么只需要實現一個新的前端;
    • 如果需要支持一種新的硬件設備,那么只需要實現一個新的后端;
    • 優化階段是一個通用的階段,它只針對統一的LLVM IR,不論是支持新的編程語言,還是支持新的硬件設備,都不需要對優化階段做修改;
    • LLVM現在被座位實現何種靜態和運行時編譯語言的通用基礎結構(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等);
    • 相比之下,GCC的前端和后臺沒分的太開,前端后端耦合在一起,所以GCC為了支持一門新的語言,或者為了支持一個新的目標平臺,就變的特別困難;
五、Clang
  • clang 是 LLVM 項目中的一個子項目,它是基于 LLVM 架構圖的輕量級編譯器,誕生之初是為了替代 GCC,提供更快的編譯速度,它是負責 C、C++、OC 語言的編譯器,屬于整個 LLVM 架構中的編譯器前端,對于開發者來說,研究 Clang 可以給我們帶來很多好處。
  • 相比于 GCC,Clang 具有如下優點:
    • 編譯速度塊:在某平臺上,Clang 的編譯速度顯著的快過 GCC(Debug 模式下編譯 OC 速度比 GCC 快 3 倍);
    • 占用內存小:Clang 生成的AST所占用的內存是 GCC 的五分之一左右;
    • 模塊化設計:Clang 采用基于庫的模塊化設計,易于 IDE 集成及其他用途的重用;
    • 診斷信息可讀性強:在編譯過程中,Clang 創建并保留了大量纖細的元數據;(metadata),有利于調試和錯誤信息更加友善;
    • 設計清晰簡單,易于理解,擴展性強。
六、Clang 與 LLVM

LLVM編譯流程

一、通過命令打印源碼編譯階段
  • clang -ccc-print-phases main.m
    • 輸入文件:找到源文件
      ± 0: input, “main.m”, objective-c
    • 預處理階段:這個過程處理包括宏的替換,頭文件的導入
      ± 1: preprocessor, {0}, objective-c-cpp-output
    • 編譯階段:進行詞法分析、語法分析、檢測語法是否正確,最終生成IR
      ± 2: compiler, {1}, ir
    • 后端:這里LLVM會通過一個一個的pass去優化,每個pass做一些事情,最終生成匯編代碼
      ± 3: backend, {2}, assembler
    • 匯編代碼生成目標文件
      ± 4: assembler, {3}, object
    • 鏈接:鏈接需要的動態庫和靜態庫,生成可執行文件
      ± 5: linker, {4}, image(鏡像文件)
    • 綁定:通過不同的架構,生成對應的可執行文件
      6: bind-arch, “x86_64”, {5}, image
  • 新建一個工程,cd 到 main.m 路徑,然后執行 clang -ccc-print-phases main.m,結果如下:
yangdw@Kody LLVM % clang -ccc-print-phases main.m +- 0: input, "main.m", objective-c+- 1: preprocessor, {0}, objective-c-cpp-output+- 2: compiler, {1}, ir+- 3: backend, {2}, assembler+- 4: assembler, {3}, object+- 5: linker, {4}, image6: bind-arch, "x86_64", {5}, image
二、預處理編譯階段
  • 在 main.m 中鍵入以下代碼:
int test(int a,int b){return a + b + 3;}int main(int argc, const char * argv[]) {@autoreleasepool {int a = test(1, 2);printf("%d",a);}return 0;}
  • 然后執行 clang -E main.m,可以看到,結果如下:
# 9 "main.m" 2int test(int a,int b){return a + b + 3;}int main(int argc, const char * argv[]) {@autoreleasepool {int a = test(1, 2);printf("%d",a);}return 0;}
  • 不難看出,這個階段主要是處理了包括宏的替換和頭文件的導入;
三、編譯階段
① 詞法分析
  • 預處理完成后就會進行詞法分析,這里會把代碼切成一個個Token,比如大小括號、等于號還有字符串等。
  • 通過如下命令查看詞法分析的結果:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
  • 執行結果如下:
yangdw@Kody LLVM % clang -fmodules -fsyntax-only -Xclang -dump-tokens main.mannot_module_include '#import <Foundation/Foundation.h>int test(int a,int b){return a + b + 3;}int main(int argc, con' Loc=<main.m:8:1>int 'int' [StartOfLine] Loc=<main.m:10:1>identifier 'test' [LeadingSpace] Loc=<main.m:10:5>l_paren '(' Loc=<main.m:10:9>int 'int' Loc=<main.m:10:10>identifier 'a' [LeadingSpace] Loc=<main.m:10:14>comma ',' Loc=<main.m:10:15>int 'int' Loc=<main.m:10:16>identifier 'b' [LeadingSpace] Loc=<main.m:10:20>r_paren ')' Loc=<main.m:10:21>l_brace '{' Loc=<main.m:10:22>return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:11:5>identifier 'a' [LeadingSpace] Loc=<main.m:11:12>plus '+' [LeadingSpace] Loc=<main.m:11:14>identifier 'b' [LeadingSpace] Loc=<main.m:11:16>plus '+' [LeadingSpace] Loc=<main.m:11:18>numeric_constant '3' [LeadingSpace] Loc=<main.m:11:20>
  • 如果頭文件找不到,指定sdk:
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -dump-tokens main.mclang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -dump-tokens main.m
② 語法分析
  • 詞法分析完成后就是語法分析,它的任務是驗證語法是否正確,在詞法分析的基礎上將單詞序列組合成各類此法短語,如程序、語句、表達式等,然后將所有節點組成抽象語法樹(Abstract Syntax Tree􏰊AST),語法分析程序判斷程序在結構上是否正確。
  • 可以通過下面命令查看語法分析的結果:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
  • 如果導入頭文件找不到,可以指定SDK:
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.mclang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -ast-dump main.m
  • 語法分析的結果如下所示:

③ 生成中間代碼IR
  • 生成中間代碼IR,代碼生成器(Code Generation)會將語法樹自頂向下遍歷逐步翻譯成LLVM IR;
  • 可以通過下面命令可以生成.ll的文本文件,查看IR代碼。
clang -S -fobjc-arc -emit-llvm main.m
  • OC 代碼在這一步會進行 runtime 橋接:property合成、ARC處理等;
  • IR 的基本語法:
    • @ 全局標識
    • % 局部標識
    • alloca 開辟空間
    • align 內存對齊
    • i32 32bit 4個字節
    • store 寫入內存
    • load 讀取數據
    • call 調用函數
    • ret 返回
  • 生成的中間代碼.ll文件如下:

  • 其中,test 函數的參數為:

  • IR 文件在OC中是可以進行優化的,一般設置是在target - Build Setting - Optimization Level(優化器等級)中設置。LLVM的優化級別分別是-O0 -O1 -O2 -O3 -Os(第一個是大寫英文字母O),下面是帶優化的生成中間代碼IR的命令:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
  • 優化之后如下所示:

  • Xcode7 以后開啟 bitcode,蘋果會做進一步優化,生成.bc的中間代碼,通過優化后的IR代碼生成 .bc 代碼:
clang -emit-llvm -c main.ll -o main.bc
四、生成匯編代碼(后端)
  • 通過最終的.bc或者.ll代碼生成匯編代碼:
clang -S -fobjc-arc main.bc -o main.s clang -S -fobjc-arc main.ll -o main.s
  • 生成匯編代碼也可以進行優化:
clang -Os -S -fobjc-arc main.m -o main.s
  • 此時生成的main.s文件的格式為匯編代碼:
yangdw@Kody LLVM % clang -emit-llvm -c main.ll -o main.bcyangdw@Kody LLVM % clang -S -fobjc-arc main.bc -o main.s yangdw@Kody LLVM % clang -Os -S -fobjc-arc main.m -o main.syangdw@Kody LLVM % file main.smain.s: assembler source text, ASCII text
五、生成目標文件(編譯器)
  • 目標文件的生成,是匯編器以匯編代碼作為插入,將匯編代碼轉換為機器代碼,最后輸出目標文件(object file)
clang -fmodules -c main.s -o main.o
  • 可以通過 nm 命令,查看下 main.o 中的符號:
$xcrun nm -nm main.o
  • 以下是 main.o 中的符號,其文件格式為目標文件:
yangdw@Kody LLVM % clang -fmodules -c main.s -o main.oyangdw@Kody LLVM % $xcrun nm -nm main.o(undefined) external _objc_autoreleasePoolPop(undefined) external _objc_autoreleasePoolPush(undefined) external _printf0000000000000000 (__TEXT,__text) external _test000000000000000a (__TEXT,__text) external _mainyangdw@Kody LLVM % file main.o main.o: Mach-O 64-bit object x86_64
  • 分析說明:
    • _printf 函數是一個是undefined 、external 的
    • undefined 表示在當前文件暫時找不到符號_printf
    • external 表示這個符號是外部可以訪問的
六、鏈接
  • 鏈接主要是鏈接需要的動態庫和靜態庫,生成可執行文件,其中
    • 靜態庫會和可執行文件合并;
    • 動態庫是獨立的;
  • 連接器把編譯生成的 .o 文件和 .dyld .a 文件鏈接,生成一個mach-o文件:
clang main.o -o main
  • 查看鏈接之后的符號:
$xcrun nm -nm main
  • 其中的undefined表示會在運行時進行動態綁定:
clang main.o -o main$xcrun nm -nm main(undefined) external _printf(from libSystem) (undefined) external dyld_stub_binder(from libSystem)0000000100000000 (__TEXT,__text)[referenced dynamically] external __execute_header0000000100003f20 (__TEXT,__text) external _test0000000100003f40 (__TEXT,__text) external _main0000000100008008 (__DATA,__data) non_external _dyld_private
  • 查看 main 是什么格式,此時是 mach-o可執行文件:
yangdw@Kody LLVM % file mainmain:Mach-O 64-bit executable x86_64
七、綁定

綁定主要是通過不同的架構,生成對應的mach-o格式可執行文件

八、LLVM 的編譯流程如下

Clang插件開發

一、LLVM 下載
  • 由于國內網絡限制,需要借助鏡像下載 LLVM 的源碼:LLVM 的鏡像鏈接
  • 下載 LLVM 項目:
git clone https://github.com/llvm/llvm-project.git或者git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
  • 在 LLVM 的 tools 目錄下下載 Clang:
cd llvm/toolsgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
  • 在 LLVM 的 projects 目錄下下載 compiler-rt、libcxx、libcxxabi:
cd ../projectsgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.gitgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.gitgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
  • 在 Clang 的 tools 下安裝 extra 工具:
cd ../tools/clang/toolsgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
二、LLVM 編譯

由于最新的 LLVM 只支持 cmake 來編譯,所以需要安裝 cmake。

① 安裝 cmake
  • 查看brew是否安裝cmake,如果已經安裝,則跳過下面步驟:
brew list
  • 通過 brew 安裝 cmake:
brew install cmake
② 通過 Xcode 編譯 LLVM
  • cmake 編譯成 Xcode 項目:
// 在llvm同級目錄下新建一個build_xcode文件mkdir build_xcodecd build_xcode// 編譯llvmcmake -G Xcode ../llvm
  • 如果編譯過程中遇到如下的錯誤:刪除構建目錄下的 CMakeCache.txt 即可。
CMake Error: Error: generator : XcodeDoes not match the generator used previously: NinjaEither remove the CMakeCache.txt file and CMakeFiles directory or choose a different binary directory.
  • 使用 Xcode 編譯 Clang,選擇自動創建 Schemes:

  • 編譯(CMD + B),選擇 ALL_BUILD Secheme 進行編譯(時間較長,預計一個小時以上)

  • 如果編譯過程中遇到錯誤:error: The i386 architecture is deprecated. You should update your ARCHS build setting to remove the i386 architecture
    只需要將對應中的 Build Settings 選項 Architectures 中的值切換為 Standard Architectures(64-bit Intel) 即可。
    或者:選擇手動創建Schemes,然后編譯編譯 Clang + ClangTooling 即可。
③ 通過 ninja 編譯 LLVM
  • 使用 ninja 進行編譯則還需要安裝 ninja,使用以下命令安裝ninja:
brew install ninja
  • 在 LLVM 源碼根目錄下新建 build_ninja 目錄,最終會在 build_ninja 目錄下生成build.ninja;
  • 在 LLVM 源碼根目錄下新建 llvm_release 目錄,最終編譯文件會在 llvm_release 文件夾路徑下:
cd llvm_build// -DCMAKE_INSTALL_PREFIX 指定 LLVM 的安裝路徑,注意:DCMAKE_INSTALL_PREFIX后面不能有空格cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機為/ Users/xxx/xxx/LLVM/llvm_release)
  • 一次執行編譯,安裝指令:
ninjaninja install
三、創建插件
  • 在 /llvm/tools/clang/tools 目錄下新建插件 YDWPlugin:

  • 在 /llvm/tools/clang/tools 目錄下的 CMakeLists.txt 文件,新增add_clang_subdirectory(YDWPlugin),此處的 YDWPlugin 即為上一步創建的插件名稱;

  • 在 YDWPlugin 目錄下新建兩個文件,分別是 YDWPlugi.cpp 和 CMakeLists.txt,并在CMakeLists.txt 中加上以下代碼:
// 通過終端在YDWPlugin目錄下創建touch YDWPlugin.cpptouch CMakeLists.txt// CMakeLists.txt中添加以下代碼add_llvm_library(YDWPlugin MODULE BUILDTREE_ONLY YDWPlugin.cpp )
  • 利用 cmake 重新生成 Xcode 項目,在 build_xcode 目錄下執行以下命令:
cmake -G Xcode ../llvm
  • 最后可以在 LLVM 的 Xcode 項目中可以看到 Loadable modules 目錄下由自定義的YDWPlugin 目錄,然后就可以在里面編寫相應的插件代碼。

四、編寫插件代碼
  • 在 YDWPlugin 目錄下的 YDWPlugin.cpp 文件中,加入以下代碼:
// create by YDW // 2020/11/25#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 YDWPlugin {//第三步:掃描完畢的回調函數 //4、自定義回調類,繼承自MatchCallback class YDWMatchCallback: public MatchFinder::MatchCallback {private://CI傳遞路徑:YDWASTAction類中的CreateASTConsumer方法參數 - YDWConsumer的構造函數 - YDWMatchCallback的私有屬性,通過構造函數從YDWASTConsumer構造函數中獲取CompilerInstance &CI;//判斷是否是用戶源文件bool isUserSourceCode(const string filename) {//文件名不為空if (filename.empty()) return false;//非xcode中的源碼都認為是用戶的if (filename.find("/Applications/Xcode.app/") == 0) return false;return true;}//判斷是否應該用copy修飾bool isShouldUseCopy(const string typeStr) {//判斷類型是否是NSString | NSArray | NSDictionaryif (typeStr.find("NSString") != string::npos ||typeStr.find("NSArray") != string::npos ||typeStr.find("NSDictionary") != string::npos/*...*/){return true;}return false;}public:YDWMatchCallback(CompilerInstance &CI) :CI(CI) {}//重寫run方法void run(const MatchFinder::MatchResult &Result) {//通過result獲取到相關節點 -- 根據節點標記獲取(標記需要與YDWASTConsumer構造方法中一致)const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");//判斷節點有值,并且是用戶文件if (propertyDecl && isUserSourceCode(CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str()) ) {//15、獲取節點的描述信息ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();//獲取節點的類型,并轉成字符串string typeStr = propertyDecl->getType().getAsString(); // cout<<"---------拿到了:"<<typeStr<<"---------"<<endl;//判斷應該使用copy,但是沒有使用copyif (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {//使用CI發警告信息//通過CI獲取診斷引擎DiagnosticsEngine &diag = CI.getDiagnostics();//通過診斷引擎 report報告 錯誤,即拋出異常/*錯誤位置:getBeginLoc 節點開始位置錯誤:getCustomDiagID(等級,提示)*/diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - 這個地方推薦使用copy!!"))<< typeStr;}}} };//第二步:掃描配置完畢 //3、自定義YDWASTConsumer,繼承自ASTConsumer,用于監聽AST節點的信息 -- 過濾器 class YDWASTConsumer: public ASTConsumer { private://AST節點的查找過濾器MatchFinder matcher;//定義回調類對象YDWMatchCallback callback;public://構造方法中創建matcherFinder對象YDWASTConsumer(CompilerInstance &CI) : callback(CI) {//添加一個MatchFinder,每個objcPropertyDecl節點綁定一個objcPropertyDecl標識(去匹配objcPropertyDecl節點)//回調callback,其實是在YDWMatchCallback里面重寫run方法(真正回調的是回調run方法)matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);}//實現兩個回調方法 HandleTopLevelDecl 和 HandleTranslationUnit//解析完一個頂級的聲明,就回調一次(頂級節點,相當于一個全局變量、函數聲明)bool HandleTopLevelDecl(DeclGroupRef D){ // cout<<"正在解析..."<<endl;return true;}//整個文件都解析完成的回調void HandleTranslationUnit(ASTContext &context) { // cout<<"文件解析完畢!"<<endl;//將文件解析完畢后的上下文context(即AST語法樹) 給 matchermatcher.matchAST(context);} };//2、繼承PluginASTAction,實現我們自定義的Action,即自定義AST語法樹行為 class YDWASTAction: public PluginASTAction {public://重載ParseArgs 和 CreateASTConsumer方法bool ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args) {return true;}//返回ASTConsumer類型對象,其中ASTConsumer是一個抽象類,即基類/*解析給定的插件命令行參數。- param CI 編譯器實例,用于報告診斷。- return 如果解析成功,則為true;否則,插件將被銷毀,并且不執行任何操作。該插件負責使用CompilerInstance的Diagnostic對象報告錯誤。*/unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {//返回自定義的YDWASTConsumer,即ASTConsumer的子類對象/*CI用于:- 判斷文件是否使用戶的- 拋出警告*/return unique_ptr<YDWASTConsumer> (new YDWASTConsumer(CI));}};}// 第一步:注冊插件,并自定義AST語法樹Action類 // 1、注冊插件 static FrontendPluginRegistry::Add<YDWPlugin::YDWASTAction> YDW("YDWPlugin", "This is YDWPlugin");
  • 原理分析:
    • ① 注冊插件,并自定義AST語法樹Action類
      • 繼承自 PluginASTAction,自定義 ASTAction,需要重載兩個方法 ParseArgs 和 CreateASTConsumer,其中的重點方法是 CreateASTConsumer,方法中有個參數 CI 即編譯實例對象,主要用于判斷文件是否是屬于用戶拋出警告
      • 通過 FrontendPluginRegistry 注冊插件,需要關聯插件名與自定義的 ASTAction 類;
    • ② 掃描配置完畢
      • 繼承自 ASTConsumer 類,實現自定義的子類 YDWASTConsumer,有兩個參數 MatchFinder 對象 matcher 以及 YDWMatchCallback 自定義的回調對象 callback;
      • 實現構造函數,主要是創建 MatchFinder 對象,以及將 CI 傳遞給回調對象;
      • 實現兩個回調方法:HandleTopLevelDecl:解析完一個頂級的聲明,就回調一次;
        HandleTranslationUnit:整個文件都解析完成的回調,將文件解析完畢后的上下文context(即AST語法樹) 給 matcher;
    • ③ 掃描完畢的回調函數
      • 繼承自 MatchFinder::MatchCallback,自定義回調類 YDWMatchCallback;
      • 定義 CompilerInstance 私有屬性,用于接收 ASTConsumer 類傳遞過來的 CI 信息;
      • 重寫 run 方法:
        • 通過 result,根據節點標記,獲取相應節點,此時的標記需要與 YDWASTConsumer 構造方法中一致;
        • 判斷節點有值,并且是用戶文件即 isUserSourceCode 私有方法;
        • 獲取節點的描述信息;
        • 獲取節點的類型,并轉成字符串;
        • 判斷應該使用 copy,但是沒有使用 copy;
        • 通過 CI 獲取診斷引擎;
        • 通過診斷引擎報告錯誤;
五、測試插件
  • 在終端中測試插件:
// 命令格式自己編譯的clang文件路徑 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang 插件(.dyld)路徑 -Xclang -add-plugin -Xclang 插件名 -c 源碼路徑// 舉例/Users/XXX/Desktop/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang /Users/XXXX/Desktop/build_xcode/Debug/lib/YDWPlugin.dylib -Xclang -add-plugin -Xclang YDWPlugin -c /Users/XXXX/Desktop/XXX/XXXX/測試demo/testClang/testClang/ViewController.m
  • 結果展示:
/Users/XXX/Desktop/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang /Users/XXXX/Desktop/build_xcode/Debug/lib/YDWPlugin.dylib -Xclang -add-plugin -Xclang YDWPlugin -c /Users/XXXX/Desktop/XXX/XXXX/測試demo/testClang/testClang/ViewController.m...Controller.m:12: Warning:------- NSString* 沒用 copy 修飾 --------@property (nonatomic, strong) NSString *name;------- NSArray* 沒用 copy 修飾 --------@property (nonatomic, strong) NSArray *list;...
六、Xcode 集成插件
  • 加載插件,打開測試項目,在 target -> Build Settings -> Other C Flags 添加以下內容:
-Xclang -load -Xclang (.dylib)動態庫路徑 -Xclang -add-plugin -Xclang YDWPlugin

  • 設置編譯器,由于 Clang 插件需要使用對應的版本去加載,如果版本不一致會導致編譯失敗,如下所示:

  • 在 Build Settings 欄目中新增兩項用戶定義的設置,分別是 CC 和 CXX;
    • CC 對應的是自己編譯的 Clang 的絕對路徑;
    • CXX 對應的是自己編譯的 Clang++ 的絕對路徑;

  • 接下來在 Build Settings 中搜索 index,將 Enable Index-Wihle-Building Functionality 的 Default 改為 NO;

  • 最后,重新編譯測試項目,會出現下面的效果:

總結

以上是生活随笔為你收集整理的iOS之LLVM编译流程和Clang插件开发集成的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。