PPPan's 平凡之路

做一个互联网内容的贡献者

PPPan's 平凡之路 一个技术博客覆盖范围包括 iOS Objecticve-C Swift Xcode 等


iOS 持续集成系列 - 自动化 Code Review

为了保证代码质量,Code Review 是非常重要的一环。细到*的位置是否正确,大到代码的结构是否符合了软件开发的一些基本原则,都在这项工作的范围内。

受限于现实情况,大多数团队没有足够的时间进行 Code Review,那么只能把一部分 CR 工作交给计算机去完成了。我们只需要定下合理的流程,用代码告诉计算机需要做什么,剩下的就交给我们可靠的伙伴吧。

应用了自动化 Code Review 后,如果你的代码写得不好,Xcode 会表示不开心。

如果你忽略 Xcode 的心情,那么质量管理平台会默默地记录这一切。

这套东西既帮助开发们写出更高质量的的代码,也给经理们对工程质量的评估提供了一个切面的支持,同时只需要花费较少的人力维护,听起来是不是跃跃欲试了呢 : )

流程

整体的工作流程非常简单,如图:

自动化 Code Review 总体流程

关键点在于本地 Review远端 Review这两步。前者是提供给开发者一个即时的代码质量反馈,以便开发者修改,从而避免在接下来的远端 Review 中得到一个较低的得分。后者则是为了生成相关报表,为项目管理人员跟踪项目质量提供依据。在很多大公司里,这也是开发者们绩效的参考之一。

剩下的就是一些胶水步骤了,如何让过程更自动化,就是胶水步骤要做的事。例如利用 WebHook 自动触发远端 Review,利用 Git 的钩子进行增量校验而不是全量校验等。这些我们放在后面聊,先来看看本地校验的流程。

本地 Review

本地自动化 Code 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) 的一层封装,使我们不必对抽象语法树进行解析,只需要专注规则相关的逻辑开发即可。从其提供的接口中我们可以很明显地看出这一点。

1
2
3
4
5
6
7
8
// 遇到一元操作符
bool VisitUnaryOperator(UnaryOperator *node)

// 遇到二元操作符
bool VisitBinaryOperator(BinaryOperator *node)

// 遇到 Objective-C 的函数声明
bool VisitObjCMethodDecl(ObjCMethodDecl *node)

在开发好相关的规则后,打包成 dylib,就可以在分析的时候加载我们自己的 Rule 了

compile_commands.json

compile_commands.json 是 Clang 定义的一个规范,里面存放了一组工作目录目标文件需要被执行的命令,帮助相关工具可以独立于编译系统来将源代码文件转换为 AST 并做对应的事。

看文件内容会更直观一些:

1
2
3
4
5
6
7
8
[
{
"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 文件。

1
xcodebuild | tee xcodebuild.log

然后利用 oclint-xcodebuild 生成 compile_commands.json

1
oclint-xcodebuild

截至 Xcode 8.1,这种做法可以正确生成 json 文件。由于 OCLint 团队已经声称不再维护 oclint-xcodebuild , 因此可能在未来的某个 Xcode 版本中这个方法将不再适用。

另一个推荐的方法是利用 xcpretty

xcpretty 可以一句话生成 json 文件。

1
xcodebuild | xcpretty -r json-compilation-database --output /path/to/compile_commands.json

使用本地 Review

了解了这些工具后就很容易明白本地自动化 Code Review 是如何工作的,使用方式也非常容易理解了:

  1. 首先在电脑本地安装好 OCLint 并拿到公司自定义的 Rules 文件
  2. 在 Xcode 上配置好工程
  3. build 工程,等待结果显示在 Xcode 上。

附一个我们团队的配置脚本供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

远端自动化 Code 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 格式

1
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.xmlrules.txt 中添加相关的 rule name ,然后打包并将这个插件安装到 SonarQube 上即可。

举个例子:

当我们用自行开发的 Rule 检查完代码后,生成了report.xml,内容如下:

1
2
3
4
5
6
7
8
<?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 中添加一段

1
2
3
4
5
6
7
binary operator space (HT_iOS_Coding_style 2.8)
----------

Summary:多元运算符和他们的操作数之间至少需要一个空格。

Severity: 2
Category: Hengtian iOS Coding Standard

在上述插件的 profile-oclint.xml 中添加另外一段代码

1
2
3
4
 <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 ,填入下述脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
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 一致,有三个地方需要注意一下。

  1. xcodebuild 命令添加了 -sdk iphonesimulator参数,以避免 build 需要 Code Sign 的问题。
  2. -report-type pmd 输出格式必须为 pmd 格式
  3. -o /path/to/report.xml 注意输出报告的路径,下一步sonnar-runner 读取时会用到。

读取到 SonarQube

在上一步的下方再添加一步 Invoke Standalone SonarQube Analysis,选择好你的 sonnar-runner。并在 Analysis Properties 中添加如下配置:(如果没有这一项,你可能需要安装 SonarQube 相关的插件。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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_NAMEYOUR_PROJECT_DESCRIPTION、和 YOUR_REPORT_FILE_PATH为你项目的值。

一切顺利的话,在 Jenkins 上立即构建,你就可以在你的 Sonar 平台上看到代码质量报告了。

配合好构建触发器 和 Git 平台的 WebHook 功能,就可以在开发提交代码或者合并分支等关键点自动触发构建了。

Troubleshooting

为什么生成的 compile_commands.json 为空

检查 log 是否为空,如果 log 为空则代表 build 失败。排除失败原因后即可正常生成。

Jenkins 构建遇到了如下问题

1
❌  Code signing is required for product type 'Application' in SDK 'iOS 10.0'

遇到这样的情况,是因为构建了 Release 版本,且项目在 Xcode8+ 上开启了 Automatic Code Sign。解决方法如下:

  1. 如果只需要检查代码规范,则在 xcodebuild 命令后添加 -sdk iphonesimulator 参数指明以 Debug 方式构建即可。
  2. 如果希望构建 Release 版本,那么关闭自动签名,在 CI 系统上手动配置证书和Proversion Profile。或者保留自动签名,参考这个回答sed 命令在构建前修改相关配置。

参考链接

Newer Post

全站 HTTPS 边做边记

没错,这是一篇可以 Step by Step 跟着做的操作指南。 keyword: Let’s Encrypt, Ubuntu 14.04, Nginx 前言关于 HTTPS 可以先看看鹅厂的这篇文章涨涨姿势。 我们知道 HTTPS 需要一个 CA 来解决信息抵赖和中间人攻击的隐患。Let’s En …

继续阅读
Older Post

深挖 Swift 中的字符串可比性

在写 《从 Swift 中的 max(_:_:) 看设计哲学》的过程中,产生了这样一个想法: 既然 max(_:_:) 可以用来对比任何 Comparable ,而 String 是符合 Comparable 的,那么用它来比较两个字符串”谁大谁小”会怎么样呢? 由于实验结果的捉摸不定,于是产生了 …

继续阅读