- iOS 持续集成 - 开篇
- iOS 持续集成 - 自动化 Code Review
- [iOS 持续集成 - 自动化单元测试]
- [iOS 持续集成 - 自动化打包与分发]
为了保证代码质量,Code Review 是非常重要的一环。细到*
的位置是否正确,大到代码的结构是否符合了软件开发的一些基本原则,都在这项工作的范围内。
受限于现实情况,大多数团队没有足够的时间进行 Code Review,那么只能把一部分 CR 工作交给计算机去完成了。我们只需要定下合理的流程,用代码告诉计算机需要做什么,剩下的就交给我们可靠的伙伴吧。
应用了自动化 Code Review 后,如果你的代码写得不好,Xcode 会表示不开心。
如果你忽略 Xcode 的心情,那么质量管理平台会默默地记录这一切。
这套东西既帮助开发们写出更高质量的的代码,也给经理们对工程质量的评估提供了一个切面的支持,同时只需要花费较少的人力维护,听起来是不是跃跃欲试了呢 : )
流程
整体的工作流程非常简单,如图:
关键点在于本地 Review和远端 Review这两步。前者是提供给开发者一个即时的代码质量反馈,以便开发者修改,从而避免在接下来的远端 Review 中得到一个较低的得分。后者则是为了生成相关报表,为项目管理人员跟踪项目质量提供依据。在很多大公司里,这也是开发者们绩效的参考之一。
剩下的就是一些胶水步骤了,如何让过程更自动化,就是胶水步骤要做的事。例如利用 WebHook 自动触发远端 Review,利用 Git 的钩子进行增量校验而不是全量校验等。这些我们放在后面聊,先来看看本地校验的流程。
本地 Review
在本地 Review 环节,开发者只需要像往常一样按下 CMD + B,然后只要静静地等待进度条读完,满屏的⚠️就会精确地指示出某一行的代码违反了哪条规则。此时开发者就可以根据代码规范进行对应修改。
从按下按键到产生警告主要发生了这么几件事情:
- 生成 compile_commands.json 文件
- OCLint 读取相关的 Rules,逐个扫描 compile_commands.json 中的 .m 文件
- OCLint 将生成的报告展示在 Xcode 上
实现本地 Review 的核心就是 OCLint 和 compile_commands.json文件
OCLint
工欲善其事,必先利其器
OCLint 是一个开源的,基于 Clang 用 C++ 编写而成的,可以用于 C、C++ 和 Objective-C 的静态代码分析器。它可以在扫描的过程中动态加载规则文件(Rules),因此可以实现非常灵活的,高度可自定义的代码分析方案。它几乎可以和大多数系统无缝集成,例如 Cmake、Bear、xcodebuild、xctool、Xcode、xcpretty、Jenkins CI、Travis CI 等。你可以在这里找到如何将其和 Xcode 配合使用。
最新版本的 OCLint 已经自带了 71 条 Rules,基本上都是先人宝贵的经验,比如这条禁用 goto 语句的 Rule,就是来源于 Edsger W. Dijkstra 1968 年的一篇手稿。
这 71 条 Rules 已经可以帮助我们避免一部分因书写习惯和语言误区而导致的问题,但是对于有完整编码规范的公司来说显然是不够的。我们必须要自己开发 Rules。
幸运的是,OCLint 已经为我们准备好了一切。
OCLint 提供了 Clang 和 AST (Abstract Syntax Tree) 的一层封装,使我们不必对抽象语法树进行解析,只需要专注规则相关的逻辑开发即可。从其提供的接口中我们可以很明显地看出这一点。
// 遇到一元操作符
bool VisitUnaryOperator(UnaryOperator *node)
// 遇到二元操作符
bool VisitBinaryOperator(BinaryOperator *node)
// 遇到 Objective-C 的函数声明
bool VisitObjCMethodDecl(ObjCMethodDecl *node)
在开发好相关的规则后,打包成 dylib,就可以在分析的时候加载我们自己的 Rule 了。
compile_commands.json
compile_commands.json 是 Clang 定义的一个规范,里面存放了一组工作目录、目标文件、需要被执行的命令,帮助相关工具可以独立于编译系统来将源代码文件转换为 AST 并做对应的事。
看文件内容会更直观一些:
[
{
"directory": "/path/to/project/",
"command": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x ...",
"file": "/path/to/project/XXXViewController.m"
},
...
]
OCLint 可以根据 compile_commands.json 中的内容,批量检查源代码文件。
xcpretty
还有一个点需要关注的是,如何生成 compile_commands.json 文件?
最便捷的方式是使用 oclint-xcodebuild
来生成。首先,利用xcodebuild 生成 xcodebuild.log 文件。
xcodebuild | tee xcodebuild.log
然后利用 oclint-xcodebuild
生成 compile_commands.json
oclint-xcodebuild
截至 Xcode 8.1,这种做法可以正确生成 json 文件。由于 OCLint 团队已经声称不再维护 oclint-xcodebuild , 因此可能在未来的某个 Xcode 版本中这个方法将不再适用。
另一个推荐的方法是利用 xcpretty 。
xcpretty 可以一句话生成 json 文件。
xcodebuild | xcpretty -r json-compilation-database --output /path/to/compile_commands.json
使用本地 Review
了解了这些工具后就很容易明白本地自动化 Code Review 是如何工作的,使用方式也非常容易理解了:
- 首先在电脑本地安装好 OCLint 并拿到公司自定义的 Rules 文件
- 在 Xcode 上配置好工程
- build 工程,等待结果显示在 Xcode 上。
附一个我们团队的配置脚本供参考:
source ~/.bash_profile
cd ${SRCROOT}
xcodebuild clean
xcodebuild | tee xcodebuild.log
oclint-xcodebuild
oclint-json-compilation-database \
-e Vendor \
-e Pods \
-- \
-max-priority-1 100000 \
-max-priority-2 100000 \
-max-priority-3 100000 \
-report-type xcode \
-R /path/to/rules
远端 Review
远端 Review 和 本地 Review 大体相似,区别在与引用构建的脚本的对象从 Xcode 变成了 Jenkins CI ,报告的展示者从 Xcode 变成了 SonarQube 。其流程是这样的:
工程师通过 git push
提交代码
→ Web Hook 触发 Jenkins 构建
→ OCLint 扫描代码生成PMD格式报告
→ Sonar-runner 读取报告并展现到 SonarQube。
CI 环境
为了实现远端 Review ,服务端必须首先有一套 CI 环境。鉴于 iOS 的特殊性,服务器必须是 macOS 系统。CI 我们直接选择开源的 Jenkins,质量管理平台则选用开源的 SonarQube。Jenkins 大名鼎鼎大家都非常熟悉了,SonarQube 则相对少的人了解。
SonarQube 是一个质量管理平台,在 SonarQube 上,你可以看到一个项目的代码行数、文件数量、代码重复率、违反的代码规范、技术债时间等等指标。SonarQube 对 Java 的支持极度友好,提供了 SonarScanner 可以直接对 Java 源代码进行扫描。Objective-C 就没有这么幸运了。虽然 SonarQube 也提供了 Objective-C 的报告展示的支持,但静态分析还是得依靠 OCLint 。
Sonnar-Runner
我们在 Jenkins 上运行 OCLint 生成了报告。需要一个中间人将报告解析成 SonarQube 可以理解的格式并传输到 SonarQube 平台。这个中间人就是 Sonnar-Runner。Sonnar-Runner 在我们的系统中也仅仅扮演这个搬运工的角色。你可以从这里了解到如何在 Jenkins 上安装和使用 Sonnar-Runner。
Sonnar-Runner 只能解析 PMD 格式的报告,因此我们在使用 OCLint 分析代码后,需要将报告格式输出为 PMD 格式。
oclint -report-type pmd -o ./report.xml
Rules in Sonar
SonarQube 有一套规则,将代码问题按照严重程度分为 5 个等级,不同等级的问题会以不同权重影响到项目质量评分。这套规则和 OCLint 生成的报告中的 Rule name 必须要一一对应,SonarQube 才能正确将报告中的问题归类并评分。
如果你使用 OCLint 原生的 Rules 来检查代码,只需要在 SonarQube 上安装 SonarQube Plugin for Objective C 插件,相关的报告就会被正确识别了。
如果是使用了自行开发的 Rules ,只需要 Clone 上述插件,并在profile-oclint.xml 和 rules.txt 中添加相关的 rule name ,然后打包并将这个插件安装到 SonarQube 上即可。
举个例子:
当我们用自行开发的 Rule 检查完代码后,生成了report.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<pmd version="oclint-0.11">
<file name="/path/to/TerribleCode.m">
<violation rule="binary operator space (HT_iOS_Coding_style 2.8)" begincolumn="9" endcolumn="157" beginline="73" endline="73" priority="3" ruleset="HT_iOS_rules" >
多元运算符和他们的操作数之间至少需要一个空格
</violation>
</file>
</pmd>
其中 binary operator space (HT_iOS_Coding_style 2.8)
是我们定义的错误rule name。在 SonarQube 上,也必须对应有这么一条 rule 的 name,才能正确识别这个错误。
此时我们只需要在上述插件的 rules.txt 中添加一段
binary operator space (HT_iOS_Coding_style 2.8)
----------
Summary:多元运算符和他们的操作数之间至少需要一个空格。
Severity: 2
Category: Hengtian iOS Coding Standard
在上述插件的 profile-oclint.xml 中添加另外一段代码
<rule>
<repositoryKey>OCLint</repositoryKey>
<key>binary operator space (HT_iOS_Coding_style 2.8)</key>
</rule>
然后将这个插件打包并安装到 SonarQube 上,SonarQube 就可以正确识别我们的问题并分类了。
使用远端 Review
在使用前,一定要确保你的 macOS 服务器已经安装好了最新版的 Xocde、OCLint、Jenkins、sonnar-runner,安装好 Jenkins 的相关插件,并将自定义的 Rule 放置在服务器上(如果有的话)。
检查并生成报告
在 Jenkins 上新建工程并配置好Git、构建触发器等其他内容。在构建步骤中添加一步 Execute Shell ,填入下述脚本
cd YourProjectDir
xcodebuild clean
xcodebuild -workspace MyProject.xcworkspace -scheme HTMarket -sdk iphonesimulator | tee xcodebuild.log | xcpretty
oclint-xcodebuild
oclint-json-compilation-database -e Pods \
-v \
-- \
-max-priority-1 100000 \
-max-priority-2 100000 \
-max-priority-3 100000 \
-report-type pmd \
-R /path/to/diy-rules \
-o /path/to/report.xml
脚本大致和本地 Review 一致,有三个地方需要注意一下。
-
xcodebuild
命令添加了-sdk iphonesimulator
参数,以避免 build 需要 Code Sign 的问题。 -
-report-type pmd
输出格式必须为 pmd 格式 -
-o /path/to/report.xml
注意输出报告的路径,下一步sonnar-runner 读取时会用到。
读取到 SonarQube
在上一步的下方再添加一步 Invoke Standalone SonarQube Analysis,选择好你的 sonnar-runner。并在 Analysis Properties 中添加如下配置:(如果没有这一项,你可能需要安装 SonarQube 相关的插件。)
sonar.projectKey=YOUR_PROJECT_NAME
sonar.projectName=YOUR_PROJECT_NAME
sonar.projectVersion=1.0
sonar.language=objc
sonar.projectDescription=YOUR_PROJECT_DESCRIPTION
# Path to source directories
sonar.sources=/path/to/source/directories
# Xcode project configuration (.xcodeproj or .xcworkspace)
# -> If you have a project: configure only sonar.objectivec.project
# -> If you have a workspace: configure sonar.objectivec.workspace and sonar.objectivec.project
# and use the later to specify which project(s) to include in the analysis (comma separated list)
sonar.objectivec.project=YOUR_PROJECT_NAME.xcodeproj
sonar.objectivec.workspace= YOUR_PROJECT_NAME.xcworkspace
# Scheme to build your application
sonar.objectivec.appScheme=YOUR_PROJECT_NAME
sonar.sourceEncoding=UTF-8
# OCLint report generated by run-sonar.sh is stored in sonar-reports/oclint.xml
# Change it only if you generate the file on your own
sonar.objectivec.oclint.report=YOUR_REPORT_FILE_PATH
注意看注释并修改 YOUR_PROJECT_NAME
、YOUR_PROJECT_DESCRIPTION
、和 YOUR_REPORT_FILE_PATH
为你项目的值。
一切顺利的话,在 Jenkins 上立即构建,你就可以在你的 Sonar 平台上看到代码质量报告了。
配合好构建触发器 和 Git 平台的 WebHook 功能,就可以在开发提交代码或者合并分支等关键点自动触发构建了。
Troubleshooting
为什么生成的 compile_commands.json 为空
检查 log 是否为空,如果 log 为空则代表 build 失败。排除失败原因后即可正常生成。
Jenkins 构建遇到了如下问题
❌ Code signing is required for product type 'Application' in SDK 'iOS 10.0'
遇到这样的情况,是因为构建了 Release 版本,且项目在 Xcode8+ 上开启了 Automatic Code Sign。解决方法如下:
- 如果只需要检查代码规范,则在 xcodebuild 命令后添加
-sdk iphonesimulator
参数指明以 Debug 方式构建即可。 - 如果希望构建 Release 版本,那么关闭自动签名,在 CI 系统上手动配置证书和Proversion Profile。或者保留自动签名,参考这个回答用
sed
命令在构建前修改相关配置。