iOS 底层探索 文章汇总
目录
- 一、LLVM编译
- 二、创建自定义Clang插件
一、LLVM编译
1.1、LLVM 下载
当前系统环境如下:
OS:macOS Big Sur, 芯片:Apple Silicon M1
根据Mac当前系统下载LLVM Releases源码,保存的路径中不要包含空格之类的字符,这里选择当前最新的llvmorg-11.0.0源码。
https://github.com/llvm/llvm-project/releases/tag/llvmorg-11.0.0
下载 Source code(zip)
1.2、LLVM编译
由于最新的L LVM只支持
cmake
来编译了,我们还需要安装cmake
。
安装 cmake
- 查看
brew
是否安装cmake
如果有就跳过下面的步骤
brew list
- 通过brew安装cmake
brew install cmake
1.2.1、通过Xcode编译LLVM
cd llvm-project文件夹
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
cmake
完成后会在build_xcode
中出现Xcode
项目
在Xcode
中使用Automatically Create Schemes
然后选择ALL_BUILD Schemes
编译项目(Xcode
能编译成功)
1.2.2、通过ninja编译LLVM
- 使用
ninja
进行编译则还需要安装ninja
。使用brew install ninja
命令即可安装ninja
。 - 在
llvm
源码文件夹中新建一个build_ninja
目录, 最终会在build_ninja
目录下生成build.ninja
。 - 在
llvm
源码文件夹中新建一个llvm_release
目录,最终编译文件会在llvm_release
文件夹路径下。
cd build_ninja
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安装路径(本机为/Users/xxx/xxx/LLVM/llvm_release)
注意DCMAKE_INSTALL_PREFIX后面不能有空格。
- 依次执行编译、安装指令。
ninja
ninja install(这步执行报错,可能是ninja未适配M1)
二、创建自定义Clang插件
2.1、创建插件
1、在/llvm-project/clang/tools
目录下新建插件CJPlugin
2 、修改/llvm-project/clang/tools
目录下的CMakeLists.txt
文件,新增add_clang_subdirectory(CJPlugin)
3、在CJPlugin
目录下新建一个名为CJPlugin.cpp
的文件和CMakeLists.txt
的文件。在CMakeLists.txt
中添加如下代码:
add_llvm_library( CJPlugin MODULE BUILDTREE_ONLY
CJPlugin.cpp
)
4、接下来利用cmake
重新生成一下Xcode
项目
cd build_xcode
cmake -G Xcode -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" ../llvm //此命令会编译clang目录下的自定义插件
5、最后可以在LLVM
的Xcode
项目中可以看到Loadable modules
目录下有自己的Plugin
目录了。我们可以在这里面编写插件代码。
2.2、编写插件代码
#include
#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 CJPlugin {
class CJASTConsumer: public ASTConsumer {
public:
// clang 解析完一个顶级的声明的回调
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"正在解析..."< CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
return unique_ptr (new CJASTConsumer);
}
bool ParseArgs(const CompilerInstance &CI, const std::vector &args) {
return true;
}
};
// 注册插件
static FrontendPluginRegistry::Add CJ("CJPlugin", "This is the description of the plugin");
}
2.3、测试插件代码
- 编译
Xcode
项目 - 编写测试源码
vi hello.m
int sum (int a);
int a;
int sum (int a) {
int b = 10;
return a + b + 10;
}
int sum2 (int a, int b) {
int c = 10;
return a + b + c;
}
- 使用自己编译的
clang
文件路径和插件路径测试
命令如下:
自己编译的clang文件路径 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk -Xclang -load -Xclang 插件(.dylib)路径 -Xclang -add-plugin -Xclang CJPlugin(插件名) -c 源码路径
测试结果:
2.4、Xcode集成插件
1、新建测试项目Test_LLVM
,并添加如下代码:
@interface ViewController ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSArray *arrs;
@end
我们接下来使用我们自定义的
clang
插件提示属性定义存在的问题。
使用原生的clang
解析ViewController.m
并生成AST
:
cd ViewController.m所在的目录
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
从AST
中我们发现属性定义节点名为ObjCPropertyDecl
,因此我们自定clang
中需要过滤出ObjCPropertyDecl
节点。
2、修改代码过滤出ViewController.m
中的ObjCPropertyDecl
节点
namespace CJPlugin {
class CJMatchCallback: 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;
}
// 判断是否使用copy修饰
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:
CJMatchCallback(CompilerInstance &CI) :CI(CI) {}
void run(const MatchFinder::MatchResult &Result) {
// 通过Result获取到节点
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs("objcPropertyDecl");
// 获取文件名称
string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
if (propertyDecl && isUserSourceCode(fileName)) {
string typeStr = propertyDecl->getType().getAsString();
cout<<"拿到属性:"<getPropertyAttributes();
if (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyAttribute::kind_copy)) {
cout<<"推荐使用 copy 修饰"< CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
return unique_ptr (new CJASTConsumer(CI));//CI用于户过滤系统属性
}
bool ParseArgs(const CompilerInstance &CI, const std::vector &args) {
return true;
}
};
// 注册插件
static FrontendPluginRegistry::Add CJ("CJPlugin", "This is the description of the plugin");
}
使用自定义clang
解析ViewController.m
代码如下:
cd ViewController.m所在的目录
/Users/ztkj/Projects/LLVM_Projects/llvm-project/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk -Xclang -load -Xclang /Users/ztkj/Projects/LLVM_Projects/llvm-project/build_xcode/Debug/lib/CJPlugin.dylib -Xclang -add-plugin -Xclang CJPlugin -c ViewController.m
测试结果:
3、添加Xcode显示自定义提示的代码
cout<<"推荐使用 copy 修饰"<getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0推荐使用 copy 修饰"))<
测试结果:
4、加载插件
打开测试项目,在Build Settings -> Other C Flags
添加如下内容:
-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang CJPlugin
5、设置编译器
-
由于
Clang
插件需要使用对应的版本去加载,如果版本不一致就会导致编译错误
,如下图所示:
-
在
Build Settings
栏目中新增两项用户定义的设置
- 分别是
CC
和CXX
CC
对应的是自己编译的clang
的绝对路径
CXX
对应的是自己编译的clang++
的绝对路径
- 接下来在
Build Settings
栏目中搜索index
,将Enable Index-Wihle-Building Functionality的Default
改为NO
。
6、编译Test_LLVM
项目成功后即可看到Xcode
显示自定义clang
插件中的提示
目前自定义的插件必须编译后才能显示提示,代码修改后也不会自动更新提示,且还没完善Fix功能。