使用Clang插件

作者: flexih | 来源:发表于2019-06-04 12:27 被阅读0次

    最近在把一个功能模块从一个独立的项目迁入另一个大型项目中,要求这个功能模块只在某一业务的页面上开启。
    首先想到的方法是判断一个页面的类是否属于该业务。对于dynamic framework可以通过NSBundle方法判断

    + (NSBundle *)bundleForClass:(Class)aClass;
    

    对于static链接的framework或者library无法使用这种方式。
    只能换一种思路,通过正则搜索代码里哪些类继承自UIViewController,但是对于继承UIViewController的子类,这种手工搜索方式就显得捉襟见肘了。
    然后想到能不能在编译的时候,去获得UIViewController的子类名称。这时候Clang插件就派上用场了。

    编译Clang插件

    首先要下载Clang的代码:

    git clone -b release_80 https://github.com/llvm-mirror/llvm.git
    git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
    git clone -b release_80 https://github.com/llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
    git clone -b release_80 https://github.com/llvm-mirror/clang-tools-extra.git llvm/tools/clang/extra
    
    插件

    在 llvm/tools/clang/examples/下增加一个文件夹,新建CPlugin.cpp

    #include <iostream>
    #include <set>
    #include "clang/AST/AST.h"
    #include "clang/AST/ASTConsumer.h"
    #include "clang/AST/RecursiveASTVisitor.h"
    #include "clang/Frontend/CompilerInstance.h"
    #include "clang/Frontend/FrontendPluginRegistry.h"
    
    using namespace clang;
    using namespace std;
    using namespace llvm;
    
    set<string> classNames;
    
    namespace CPlugin {
        class MyASTVisitor: public RecursiveASTVisitor <MyASTVisitor> {
        private:
            CompilerInstance &instance;
            ASTContext *context;
        public:
            MyASTVisitor(CompilerInstance &inst): instance(inst) {}
            void setContext(ASTContext &context) {
                this->context = &context;
            }
            
            bool VisitDecl(Decl *decl) {
                string filename = instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str();
                if(filename.find("/Applications/Xcode.app/") == 0) return true;
                if (isa<ObjCInterfaceDecl>(decl)) {
                    ObjCInterfaceDecl *interDecl = (ObjCInterfaceDecl *)decl;
                    if (interDecl->getSuperClass()) {
                        string interName = interDecl->getNameAsString();
                        string superClassName = interDecl->getSuperClass()->getNameAsString();
                        if (superClassName.compare("UIViewController") == 0 || classNames.find(superClassName) != classNames.end()) {
                            if (classNames.find(interName) == classNames.end()) {
                                classNames.insert(interName);
                                cout << "-------- ClassName:" << interName << endl;
                            }
                        }
                    }
                }
                
                return true;
            }
        };
        
        class MyASTConsumer: public ASTConsumer {
        private:
            MyASTVisitor visitor;
            void HandleTranslationUnit(ASTContext &context) {
                visitor.setContext(context);
                visitor.TraverseDecl(context.getTranslationUnitDecl());
            }
        public:
            MyASTConsumer(CompilerInstance &inst): visitor(MyASTVisitor(inst)) {}
        };
        class MyASTAction: public PluginASTAction {
        public:
            unique_ptr <ASTConsumer> CreateASTConsumer(CompilerInstance & Compiler, StringRef InFile) {
                return unique_ptr <MyASTConsumer> (new MyASTConsumer(Compiler));
            }
            bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string>& args) {
                return true;
            }
        };
    }
    static clang::FrontendPluginRegistry::Add<CPlugin::MyASTAction> X("CPlugin", "CPlugin desc");
    
    

    新建CMakeLists.txt

    add_llvm_library(CPlugin MODULE CPlugin.cpp PLUGIN_TOOL clang)
    
    if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
      target_link_libraries(CPlugin PRIVATE
        clangAST
        clangBasic
        clangFrontend
        clangLex
        LLVMSupport
        )
    endif()
    

    修改examples/CMakeLists.txt,在文件的末尾增加add_subdirectory(CPlugin)

    编译

    需要先安装cmake

    brew install cmake
    

    编译方法

    mkdir llvm/build
    cd llvm/build
    cmake -G Xcode ../ -DCMAKE_BUILD_TYPE:STRING=MinSizeRel
    

    等待结束后在llvm/build目录下找到LLVM.xcodeproj,使用Xcode打开后编译clang,libclang和CPlugin三个target。

    运行

    打开目标project,在Build Settings里Add User-Defined Setting增加

    CC=/path/to/llvm/build/Debug/bin/clang
    CXX=/path/to/llvm/build/Debug/bin/clang++
    

    在Build Settings里的Other C Flags增加

    -Xclang -load -Xclang /path/to/llvm/build/Debug/lib/CPlugin.dylib -Xclang -add-plugin -Xclang CPlugin
    

    如果遇到错误

    clang: error: cannot specify -o when generating multiple output files
    

    关掉Build Settings里的Enable Index-While-Building Functionality


    收工

    经过一系列的操作,就能得到所有UIViewController的子类类名了。
    PS:可以优化类名的写操作,直接写到项目的某个文件里,这样就无需在build info里查找了。

    相关文章

      网友评论

        本文标题:使用Clang插件

        本文链接:https://www.haomeiwen.com/subject/cnzhxctx.html