使用Clang插件
最近在把一个功能模块从一个独立的项目迁入另一个大型项目中,要求这个功能模块只在某一业务的页面上开启。 首先想到的方法是判断一个页面的类是否属于该业务。对于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里查找了。