iOS 持续集成

iOS 持续集成系列 - 开篇

前言

iOS 开发在经过这几年的野蛮生长之后,慢慢地趋于稳定。无论开发语言是 Objective-C 还是 Swift,工程类型是 Hybird 还是原生,开发思想是 OOP 还是函数式,随着项目逐渐变大都在面临相同的问题: 测试、发布等重复性工作占了很大一部分时间,回归成本越来越高。持续集成不可避免地被提上了日程。

本文主要阐述 iOS 下的持续集成,以目标、内容、流程、工具入手,希望可以为大家描绘一幅 iOS 持续集成的蓝图。这可能不是一篇可以让你 Step by Step 跟着做的文章,但愿可以在你脑海中建立相关概念,以便在实操时走对方向。

我们会在后面几篇内容中,详细阐述 是什么 和 如何做 。

目标

对于我们来说,减少重复工作、提升团队效率。 对于公司来说,省钱!

狭义上持续集成指早集成早测试尽早早发现问题早修复,并辅以一些自动化的手段,其目标是减少修复的成本。通过其目的,我们可以发现,其实通过自动化减少重复工作和通过早发现问题降低成本是持续集成的核心理念。因此,我把自动化 Code Review 也放到这里讲。

内容

iOS 下的持续集成内容包括自动化 Code Reivew、自动化单元测试、自动化打包和自动化分发。自动化 Code Review 保证团队遵守代码规范,在编码阶段减少 BUG 的产生。在人员流动较大的公司里,用同一套代码规范,也可以保证项目交接更流畅。自动化单元测试在集成阶段检测出 BUG ,以减少回归成本。自动化打包和自动化分发则是减少重复劳动,毕竟,工程师的时间就是金钱啊。

自动化 Code Review

顾名思义,自动化 Code Review 既采用自动化的手段,对团队成员得代码进行 Review,以保证代码质量。从现实角度来说,自动化的 Code Review 更多地是对代码进行静态分析,通过扫描代码并对比制定的规则,产出所需要的结果。这个所需要的结果,可以是工程总体的量化的质量报告,也可以是显示在 Xcode 中的一条警告⚠️。这取决于用户是什么角色。

在实际实践中,一般会有两种角色会关注这份结果 -- 工程师和管理层。工程师需要在开发的过程中及时了解代码错误,以便及时纠正。管理层需要了解工程的总体代码质量,以掌握项目的相关风险。同时,也可以作为工程师绩效的依据之一。

要完成这一块内容,我们需要这些工具:

通过这三者协作,我们可以实现以下流程:

工程师通过 `Git Commit` 提交代码 → Web Hook 触发 `Jenkins` 构建 → `OCLint` 扫描代码生成PMD格式报告 → `Sonar-runner` 读取报告并展现到 `SonarQube`。

关于 Code Review 需要指出的是,自动化工具是有局限性的。其无法分析出较为"智能"的规则。比如说下面这条

4.7 方法名以小写字母动词作为开头

还有下面这种代码,尽管含有多个容易造成闪退的 BUG,也是可以顺利逃过分析器的眼睛的

if result?.bindPhone == "true" {
let range = (result?.loginId?.startIndex.advancedBy(3))!...(result?.loginId?.endIndex.advancedBy(-5))!
let phoneNumber: String? = result?.loginId?.stringByReplacingCharactersInRange(range, withString: "****")
self.phoneNumLabel.text = phoneNumber!
}

因此,想要达到 “保证代码质量” 的目的,还需要人工 Review 和自动化 Review 相结合。

自动化单元测试

自动化地执行单元测试,在测试失败的情况下中断集成,并通知相关人员,就是这一块工作的内容。

为此,我们需要如下的工具:

我们可以实现以下的流程:

`Git Merge`  → Web Hook 触发 `Jenkins` 构建 → xctool 执行单元测试 → 如果失败则发邮件给相关人员。

当然,如果你公司的项目托管在 GitHub 上,业界有两个非常优秀的 Jenkins 替代产品 travis-ci 和 circle-ci。他们对 GitHub 的支持完美,且对开源项目是免费的。私有项目则需要付费试用。

在单元测试这一块,最高的成本还是写单元测试的时间成本。脑洞大开地说,如果能实现一款工具,可以自动地为业务代码生成测试代码,那才是生产力的大大提升。

自动化打包与分发

一次完整的打包,需要经历配置证书、切换环境、调整参数(构建版本号等)、Archive、导出。 根据工程的复杂程度,以上过程快则五分钟,慢则半小时。当需要频繁发版的情况下,浪费了工程师非常多的时间。

我们其实可以利用一些工具来实现这样的一个流程:

Git Merge 到 Master(通常这意味着一个 Release)→ 触发 Jenkins 构建生成 ipa → 自动化分发。

自动分发包含以下工作:

  • 自动上传 App Store
  • 自动使用 TestFlight 分发
  • 自动上传到 蒲公英/Fir 等平台
  • 自动上传到企业 App Store

我们可以分别配置相关的脚本,来实现测试的分发,和发布等。

要实现这样的流程,我们需要这些工具:

Fastlane 是由一个个小组件组成的工具,功能包括上传截图到 iTunes Connect,创建 provisioning file, 管理 TestFlight 的测试员,分发等。这个工具几乎可以用命令行来实现打包上传分发相关的所有功能,并且越来越有成为默认的业界持续集成标准工具的样子。就像 CocoaPods 在依赖管理方面一样。

总结

持续集成,可以看做是通过版本控制系统、CI平台(如Jenkins)和相关工具链来完成一套工作流,以减少团队重复性工作,并保证软件可以不停地迭代而不出太大的差错。iOS 下的持续集成大体就是上述几块内容,我们在实现的过程中遇到了不少坑,后面的文章分块详细讲。

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

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

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

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

iOS 持续集成

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

iOS 持续集成

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

流程

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

iOS 持续集成

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

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

本地 Review

iOS 持续集成

在本地 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 是如何工作的,使用方式也非常容易理解了:

  1. 首先在电脑本地安装好 OCLint 并拿到公司自定义的 Rules 文件
  2. 在 Xcode 上配置好工程
  3. 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- \
-max-priority- \
-max-priority- \
-report-type xcode \
-R /path/to/rules

远端 Review

iOS 持续集成

远端 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="" endcolumn="" beginline="" endline="" priority="" 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:
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- \
-max-priority- \
-max-priority- \
-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 相关的插件。)

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- # 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。解决方法如下:

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

参考链接

  • [iOS 持续集成 - 自动化单元测试]
  • [iOS 持续集成 - 自动化打包与分发]
上一篇:epoll使用详解


下一篇:一步一步构建iOS持续集成:Jenkins+GitLab+蒲公英+FTP