31.iOS底層學(xué)習(xí)之LLVM編譯與Clang插件

本章提綱
1、LLVM的編譯
2、Clang小插件的實(shí)現(xiàn)與集成

1.LLVM的編譯

準(zhǔn)備工作:需要下載好LLVMClang, 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。使用cmakellvm編譯成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。

    至此llvmXcode項(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è)就好了。自定義插件需要clangclangTooling,把這兩個(gè)添加進(jìn)來(lái)然后分別編譯一下就好了。

Manually Manage Schemes

?。?!??感覺(jué)電腦要炸了???。。?br> 等了大概半個(gè)小時(shí)左右吧,接著進(jìn)行下一個(gè)操作Clang插件的編寫(xiě)。

2.Clang小插件的實(shí)現(xiàn)與集成

自定義小插件的需求:編譯器檢測(cè)到NSStringNSArray、NSDictionary的修飾屬性不為copy時(shí),進(jìn)行警告。

2.1創(chuàng)建插件

在llvm-project/llvm/tools/clang/tools目錄下新建插件文件夾LuckyPlugin


image.png
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。

至此,插件集成完畢。


友情提示:集成這個(gè)插件前后操作大概需要50G的硬盤(pán)空間支持。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容