一、目的
通过标识关键代码,利用Lint定期检测关键代码,当关键代码发生变更时,可以在报告中体现,避免引入意外的修改。
二、Lint
Lint介绍 Lint 作为一种工具程序,主要负责静态源码分析,负责代码规范、代码缺陷等检测。(https://zh.wikipedia.org/wiki/Lint) Android 原生已经提供了 Lint 静态分析工具,而 iOS 则有对应的 OCLint 和 SwiftLint。
三、android实现代码监控的原理
Android Lint可以对Java/Kotlin源码使用uast进行语法分析,最后生成一棵AST抽象语法树,这棵树的每个节点都是一个方法,包含方法签名和方法体。 通过自定义的Lint规则,找到应用启动的入口方法。从入口方法开始遍历整棵调用树,即可对每个方法节点的代码段进行分析。
1. 获取关键代码段
open fun shouldMonitor(node: UMethod): Boolean {
return node.getAnnotations().find { it.qualifiedName == LINT_MONITOR_ANNOTATION } != null
}
android通过注解的方式,标识需要检测的方法实现。通过Android Lint的自定义规则判断注解标识的方法,找到入口方法,然后对代码段进行分析。
iOS对应的OCLint也可以通过自定义规则进行静态分析,但是iOS没有注解,iOS如果需要标识关键代码段,目前想到的只能hardcode检测列表(包括类和方法名)来实现标识关键代码段的效果。但是这样的维护成本比较大。
iOS可以利用类似以下的代码片段进行类和方法的判断:
for(auto it = parent->meth_begin(), end = parent->meth_end(); it != end; ++it) {
const auto method = *it;
if(declHasEnforceAttribute(method, *this)) {
const auto selector = method->getSelector();
if(!implementation->getMethod(selector, method->isInstanceMethod())) {
const string className = parent->getNameAsString();
const string methodName = selector.getAsString();
addViolation(implementation, this,
"subclasses of " + className + " must implement " + methodName);
}
}
}
注:在生成OCLint自定义规则工程时,Xcode 12会因架构问题出错,暂未找到解决方案,可暂时先切换Xcode 11生成OCLint自定义规则工程
2. 给Report增加代码diff功能
报告中可视化的代码diff功能主要是基于这个开源库diff2html。但原生的Lint Report不支持直接嵌入html的,这里通过重写了HtmlReporter,实现html注入,详细代码可以参考 CodeLink
获取到代码片段后,安卓利用diff2html工具可显示代码修改记录,理论上iOS只要获取到代码片段后,也可以利用这个工具进行diff显示。
四、iOS平台的OCLint初步使用
1. 流程
2. 安装OCLint
参考官方文档:http://docs.oclint.org/en/stable/
安装方式有三种:
- Homebrew安装
- 源码编译安装
- 安装包安装
这里使用Homebrew安装会比较方便:
Installing OCLint
$ brew tap oclint/formulae
$ brew install oclint
利用oclint --version
命令验证OCLint安装成功
4. 安装xcpretty
xcpretty用于对xcodebuild的输出进行格式化(https://github.com/xcpretty/xcpretty
)
安装xcpretty
$ gem install xcpretty
5. 执行命令
# 生成编译数据
xcodebuild -scheme $myscheme -workspace $myworkspace -configuration Debug | xcpretty -r json-compilation-database -o compile_commands.json
# 生成报表
oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html \
-rc LONG_LINE=200 \
-disable-rule ShortVariableName \
-disable-rule ObjCAssignIvarOutsideAccessors \
-disable-rule AssignIvarOutsideAccessors \
-max-priority-1=100000 \
-max-priority-2=100000 \
-max-priority-3=100000
利用浏览器打开以上命令生成的oclintReport.html
,即可以查看报告
6. 报告
7. 错误
1、oclint: error: one compiler command contains multiple jobs
将Project和Targets中Building Settings下的COMPILER_INDEX_STORE_ENABLE设置为NO
解决方案:
# Podfile文件添加如下
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['COMPILER_INDEX_STORE_ENABLE'] = "NO"
end
end
end
https://github.com/oclint/oclint/issues/462
2、扫描的生成的compile_commands.json文件过大
Traceback (most recent call last):
File "/usr/local/bin/oclint-json-compilation-database", line 86, in <module>
exit_code = subprocess.call(oclint_arguments)
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 172, in call
return Popen(*popenargs, **kwargs).wait()
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 394, in __init__
errread, errwrite)
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1047, in _execute_child
raise child_exception
OSError: [Errno 7] Argument list too long
解决方案:
- 通过自定义规则过滤不重要的报错,减少分析文件大小
- 网上有解决方案,但目前我尝试过并不能解决,具体原因未知
https://github.com/oclint/oclint/issues/233
https://github.com/wuwen1030/oclint_argument_list_too_long_solution/tree/master
五、结论
1、查询较多文档发现,OCLint多用于代码的静态分析,约束代码成员的代码风格。问过平台组的同事,表示OCLint的静态分析不大靠谱; 2、如果要达到代码监控的目的,必须自定义规则,缺少注解功能的OC语言不太好标识关键代码。且自定义规则需要翻阅OCLint开放的接口和学习C++语言,有比较高的入门门槛;
综上所述,方案应该可行,但受限于语言特性,OC的实现难度会更大,且最终的效果可能达不到android的效果。
六、参考
http://docs.oclint.org/en/stable/ http://yulingtianxia.com/blog/2019/01/27/MVVM-Rules-for-OCLint/ https://juejin.im/post/6844903940375445517#heading-1