今天初步学习了 Clang 插件的开发, 特此小结一下. 如果哪里不当, 还请指出, 感激不尽, 😄...
-
场景 :
警告
如果在 OC 项目中, 使用@property
声明NSString
,NSArray
,NSDictionary
三种对象时没有使用copy
关键字修饰, 就给出警告⚠️信息, 如:--------- NSString * 没用 copy 修饰 ---------
-
分析 :
创建一个插件名称为GFPlugin
的插件来实现此警告功能. -
由于是第一次练习插件编写, 所以需要配置插件开发环境, 整个配置过程和实现过程如下 :
主要分为以下 5 大步骤 :
- LLVM 下载
- LLVM 编译
- 创建插件
- 编写插件代码并编译
- 插件的测试和集成
LLVM下载 --------------------
由于国内的网络限制, 我们需要借助镜像下载 LLVM 的源码.
https://mirror.tuna.tsinghua.edu.cn/help/llvm/
首先要cd
到存放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.git
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-extra.git
LLVM编译 --------------------
通过 Xcode 编译 LLVM
- 使用 cmake 编译成 Xcode 项目
# 创建 build_xcode 文件夹
mkdir build_xcode
cd build_xcode
# 编译 llvm
cmake -G Xcode ../llvm
build_xcode
与llvm
同级.
- 如果使用
cmake -G Xcode ../llvm
时遇到如下报错信息 , 请点击这里 : 查看解决方案
-- The C compiler identification is unknown
-- The CXX compiler identification is unknown
-- The ASM compiler identification is Clang
-- Found assembler: /Library/Developer/CommandLineTools/usr/bin/cc
CMake Error at CMakeLists.txt:49 (project):
No CMAKE_C_COMPILER could be found.
CMake Error at CMakeLists.txt:49 (project):
No CMAKE_CXX_COMPILER could be found.
错误信息
- 使用 Xcode 编译 Clang
-
创建 Schemes, 手动或者自动,
选择自动或者手动 -
手动要添加以下两项, 这个是后来截图, 就当没看见
手动添加GFPlugin
, 😄😄😄
-
编译, 选择
clang
进行编译, 编译时间比较长, 关注电脑的温度, 随时准备用水降温, 😄 开个玩笑,
本应该选择 ALL_BUILD Secheme 进行编译, 但是其中包含 i386 架构, 编译不会通过, 此处我选择
clang
也是可以完成案例的, 还没有寻找解决办法, 哪位兄弟知道怎么解决就麻烦告诉我 , 我后续也会补上.
创建插件 --------------------
-
在
创建插件/llvm/tools/clang/tools
目录下创建插件 GFPlugin
-
修改
add_clang_subdirectory(GFPlugin)/llvm/tools/clang/tools
目录下的CMakeLists.txt
文件,
新增add_clang_subdirectory(GFPlugin)
-
在
GFPlugin
目录下创建CMakeLists.txt
和GFPlugin.cpp
文件, 并在CMakeLists.txt
文件中写入
add_llvm_library( GFPlugin MODULE BUILDTREE_ONLY
GFPlugin.cpp
)
- 接下来用
cmake
重新生成一下 xcode 项目, 命令行切换到build_xcode
目录
cmake -G Xcode ../llvm
- 最后在
build_xcode
中llvm
的 xcode 项目中看到Loadable modules
目录中有自己的Plugin
目录, 我们可以在这里编写插件代码, - 文件
GFPlugin.cpp
就是编写插件代码的位置, 插件是用c++
编写.
插件代码编写和编译 --------------------
具体代码讲解, 请移步这里 : iOS Clang 插件开发代码流程分析
// create by guofei
// 2020/11/14
#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 GFPlugin {
class GFMatchHandler: public MatchFinder::MatchCallback {
private:
CompilerInstance &CI;
bool isUserSourceCode(const string filename) {
if (filename.empty()) return false;
if (filename.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
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:
GFMatchHandler(CompilerInstance &CI) :CI(CI) {}
void run(const MatchFinder::MatchResult &Result) {
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
if (propertyDecl && isUserSourceCode(CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str()) ) {
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
string typeStr = propertyDecl->getType().getAsString();
if (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
DiagnosticsEngine &diag = CI.getDiagnostics();
diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "--------- %0 没用 copy 修饰 ---------")) << typeStr;
}
}
}
};
//-------------------------------------------------------------
class GFASTConsumer: public ASTConsumer {
private:
MatchFinder matcher;
GFMatchHandler handler;
public:
GFASTConsumer(CompilerInstance &CI) : handler(CI) {
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &handler);
}
void HandleTranslationUnit(ASTContext &context) {
matcher.matchAST(context);
}
};
//-------------------------------------------------------------
class GFASTAction: public PluginASTAction {
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
return unique_ptr<GFASTConsumer> (new GFASTConsumer(CI));
}
bool ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args) {
return true;
}
};
}
static FrontendPluginRegistry::Add<GFPlugin::GFASTAction> X("GFPlugin", "This is the description of the plugin");
- 编译插件, 将
GFPlugin
添加到Schemes
中, 并选中编译一下, 在products
文件夹下会找到自己的插件
将 GFPlugin 添加到 Schemes
由于文件太多, products
滚动到上边去了.
插件的测试和集成 --------------------
1. 命令行测试插件
测试命令格式如下 :
你自己编译好的clang -isysroot 模拟器sdk路径/ -Xclang -load -Xclang 编译好的GFPlugin.dylib的绝对路径 -Xclang -add-plugin -Xclang GFPlugin(插件名) -c viewController.m的绝对路径
- 具体可以参照下图
2. Xcode 项目集成插件
在 Build Settings 栏目中搜索 other c, 添加如下图的命令和绝对路径,
-Xclang -load -Xclang 动态库绝对路径 -Xclang -add-plugin -Xclang 插件名称
other c flags 设置
动态库绝对路径
和插件名称
如下图所示
-
由于 Clang 插件需要使用对应的版本去加载, 如果不一致则会导致 编译错误 会出现如下图的错误
集成到项目编译报错
-
在 Build Settings 栏目中新增两项用户自定义设置, 如下图所示
设置编译器
分别是 CC 和 CXX
CC 是对应自己编译的clang
的绝对路径
CXX 是对应自己编译的clang++
的绝对路径
-
接下来在 Build Settings 栏目中搜索 index, 将
Enable Index-Wihle-Building Functionality设置Enable Index-Wihle-Building Functionality
的Default
改为NO
重新编译项目, 错误消失, 并在 ViewController.m
中出现如下警告⚠️信息,
-
说明我的插件集成成功了. 开不开心 😄😄😄😄😄😄
编译结果报警告
网友评论