安装配置 CI 系列工具
得益于 mac os 上的神器homebrew
,我们可以很快的安装配置以下工具:
- jenkins:持续集成界的明星项目,提供了工程自动化的服务端支持。
- fastlane:ruby 编写的一套用于 iOS 自动化编译、打包、测试、发布的工具集合。
- oclint:c / c++ / objc 代码质量静态检查工具,可自定义检查规范。
- clang-format:批量化地对代码格式进行规范化。
- xcpretty:格式化 xcodebuild 输出的工具。
以上工具大多数可以用brew install XXX
进行安装,具体步骤可参看各工具官方网站。
jenkins 实用插件
在安装好 jenkins 并运行后,可以对 jenkins 进行一系列配置。因为公司的项目代码集中管理在 SVN 服务上,下面会主要介绍针对 SVN 来进行 jenkins 的工程配置。在 jenkins 进行一次构建之前,需要将 SVN 上的最新代码拉到 jenkins 本地的 workspace 文件夹,所以需要让 jenkins 加载 SVN 的支持插件Subversion Plug-in。针对 iOS 工程的签名和打包,安装Xcode integration和Keychains and Provisioning Profiles Management两个插件来使 jenkins 支持 iOS APP 的打包过程和证书管理。在 jenkins 运行一次构建之后,一般还希望运行一些脚本来进行更多的操作,那么还需要加载一个插件Post-Build Script Plug-in。插件AnsiColor会使 jenkins 的控制台输出可读性更高,插件HTML Publisher plugin可以帮助快速生成 fastlane / oclint 运行结果报告到 workspace 的首页。以上插件都提高 iOS 工程持续集成的效率,还有更多插件有待探索。
安装好插件后,通常在 jenkins 的系统设置页面中会添加相应的待设置的栏目。例如,安装了Keychains and Provisioning Profiles Management插件后,要在系统设置页面对证书和许可文件信息进行完善。
要注意这里需要导入的是含有证书的钥匙串文件,一般位于 /Users/Username/Library/keychains/login.keychain,也可以重新在钥匙串.app
添加一个只用于 jenkins 的钥匙串并上传。项目的Provisioning Profiles
可以从 XCode 的 Preferences —> Accounts —> Apple IDs 找到。保存文件的路径最好为下图所示:
fastlane 实用工具
fastlane 是一套自动化工具的集合,极大地方便了我们执行和配置命令行任务。在 jenkins 的构建过程中,可以运行自定义的 shell 脚本,这就给 fastlane 和 jenkins 的紧密结合提供了条件。fastlane iOS 套件共包含 13 种工具:deliver(APP Store 快速发布) / snapshot(APP Store 各分辨率截图) / frameit(在 snapshot 基础上添加各种设备的框架) / pem(更新推送证书) / sigh(管理 Provisioning Profiles) / produce(在 iTunes Connect 创建新项目) / cert(管理代码签名证书) / spaceship(访问 iTunes Connect 和开发者中心) / pilot(管理 TestFlight 测试) / boarding(邀请 TestFlight beta 测试用户) / gym(自动化编译) / match(用 Git 管理证书文件) / scan (自动化测试)。
下面详细介绍 fastlane 工具链中的几个常用组成部分。
gym
gym 是 iOS 应用编译和打包的工具。在比较了 facebook 开源项目 xctool 之后,发现 gym 的便捷性和自动化程度都略胜一筹。其主要是用脚本简化了 xcodebuild 命令行工具的使用,将相关配置信息统一存入 Gymfile 进行管理。例如,以前想要打包并导出 ipa 可以在命令行运行以下命令:
1 | xcodebuild clean archive -archivePath build/MyApp \ |
而现在只需要fastlane gym
命令即可完成编译和打包的任务。
gym 的使用
gym 的使用非常简单,如针对特定的 Scheme 编译,可运行以下命令:
1 | fastlane gym --scheme "AppName" --clean |
运行这个命令,其实整个过程是对 xcodebuild 一系列命令的自动化运行。先运行了 xcodebuild -list
和 xcodebuild clean -showBuildSettings
得到了项目的 target / schemes / build settings 等信息,然后运行
1 | set -o pipefail && xcodebuild -scheme AppName -project ./Example.xcodeproj -destination 'generic/platform=iOS' -archivePath /archivePath/build/xxx/xxx.xcarchive clean archive | xcpretty |
编译结束后,如果项目配置了签名信息,会进行代码签名并打包成 AppName.app 至指定输出目录(如没有特殊配置,则为项目根目录),随后还会生成一个压缩好的 DSYM 文件和 ipa 安装文件。如果想在编译时得到更多的信息,可以在运行 gym 命令时添加--verbose
选项。输出的信息是经过 xcpretty 格式化的,具有可读性。
Gymfile
每次手动输入信息并运行 gym 命令略显麻烦,可以先把每次默认的信息添加到 Gymfile 中。
在项目目录运行fastlane gym init
会得到一个新的 Gymfile。可添加如下信息到 Gymfile:
1 | scheme "AppName" |
scan
scan 工具可以在模拟器或连接设备上自动化运行测试,和 fastlane 其他命令一样,它对用户隐藏了苹果官方复杂的命令行工具的使用细节,用 ruby 脚本完成自动化配置和执行。它和 gym 一样都是由xcodebuild
和xcpretty
两大部分组成。
scan 的使用
简单在命令行运行即可:
1 | fastlane scan |
想要在指定模拟器设备上运行测试,只需要在 fastlane 命令里指定参数--device
:
1 | fastlane scan --scheme "AppName" --device "iPhone 6" --clean |
当运行 scan 命令时,先取得项目的 target / schemes / build settings 等信息,然后找到 Scanfile 中或命令行参数中指定的设备名,对运行测试的各个参数进行汇总。准备就绪后运行:
1 | set -o pipefail && env NSUnbufferedIO=YES xcodebuild -scheme AppName -project ./Example.xcodeproj -destination 'platform=iOS Simulator,id=XXXxxx' -derivedDataPath '/Users/XXX/.../DerivedData/Example-xxxx' build test | xcpretty |
运行测试过程中,会把日志保存到 XCode 的 DerivedData 文件夹。当测试完成,会生成最终的结果汇总如下图。
根据默认选项,还会生成 html 的测试报告,以供用户查看。
Scanfile
在项目目录运行fastlane scan init
会得到一个新的 Scanfile,可以编辑其内容指定默认配置:
1 | scheme "AppName" |
snapshot
如今 iOS 设备数逐年增加,提高了适配的难度,也经常会有在多个设备上进行截图的需求。如直观检验 App 在各种设备上的实际 UI 效果与设计图的差别,或者 Apple Store 上架时提供多种类型尺寸的 APP 截屏图片。本来这一任务需要手动进行,重复性劳动多,而借助 snapshot 可以较好地自动完成。
snapshot 的使用
首先在工程添加一个新的 UI Test target.
在项目根目录执行
fastlane snapshot init
。上一步骤会自动生成文件
./SnapshotHelper.swift
,把它加入到 UI Test target.在 UI Test target 编译设置里把
Enable Modules
改为Yes
。在”MyUITests.m”文件中导入桥接头文件
#import "MYUITests-Swift.h"
。这样在这个文件里才能使用 Swift 代码。需要注意的是,MYUITests-Swift.h
不需要手动生成,导入之后就能使用所有工程中的 Swift 代码。在 UI Test 类文件里,点击 Xcode 界面左下方的录制按钮,启动模拟器,在模拟器上操作想要测试的动作,测试代码会自动生成。
在测试代码中,可以插入如下代码进行截屏:
1
2Swift: snapshot("01Screen")
Objective C: [Snapshot snapshot:@"01Screen" waitForLoadingIndicator:YES];在测试类中的
setUp()
方法中添加:1
2
3XCUIApplication *app = [[XCUIApplication alloc] init];
[Snapshot setupSnapshot:app];
[app launch];最后运行
fastlane snapshot
开始截图。snapshot 的命令还有多种参数可以指定,如遇到错误立即停止:1
fastlane snapshot --stop_after_first_errorsnapshot
同样支持编辑 Snapfile 来指定默认的设置。
snapshot 原理
当 XCode 运行 UI 测试时,会生成一个 plist 记录发生的事件,在每个事件之前、之中、之后 XCode 都会生成截图。这些截图会被保存在一个临时文件夹。如果在测试类中调用snapshot()
时, snapshot 会进行一次转向 .Unknown 的旋转,表面上这不会引起任何改变,但是它会触发截屏。然后 snapshot 会遍历所有事件,找到这个旋转事件和它对应的截图。
fastlane 自动化工作流
Fastfile 任务描述
fastlane 工具套件的出色之处不仅在于它们单个组件都有强大的功能,还在于通过一个 Fastfile 能将各种工具整合,形成完整的编译—>测试—>打包的自动化工作流。如在 fastlane 工作流中实现一次 release 版本的打包和 APP Store 分发过程,只需编辑 Fastfile 内容,用 fastlane 的 DSL 描述一系列自动化任务:
1 | desc "Deploys a new version to the App Store" |
保存 Fastfile 到指定位置./fastlane/Fastfile
后,命令行运行fastlane release
即可自动运行上述过程。注意到这个自动化过程结合了上文提到的 snapshot 和 gym 工具,还用到了 fastlane 的其它工具套件如 match / deliver 等。而increment_build_number
和cocoapods
这两行分别对应了 fastfile 中的一个动作(action),即递增版本号,安装项目的 cocoapods 依赖。fastlane 本身定义了很多类似的细粒度的动作,每个动作对应了一个 ruby 脚本,基本支持了持续集成过程中常见的操作。但如果这些动作脚本并没有涵盖实际工作中的需要,就要自己去创建一个动作,也就是 fastlane 提供的自定义动作的机制。
fastlane 自定义 Action
fastlane 目前包含的 action 有 180 个之多(version 2.24.0),基本涵盖了持续集成的需求。由于目前公司是使用 SVN 来管理 cocoapods 的私有 repo,在编写 pods 代码 —> 验证有效性 —> 推 pods 到服务器这一工作流中,需要使用 cocoapods 的 SVN 支持插件,执行的命令与 fastlane 自带的 cocoapods 相关 action 有所不同。那么就要自定义一些 action 来支持 pods 开发体系中的自动化上传 —> 验证 —> 工程构建的过程。
首先在项目根目录,运行:
1 | fastlane new_action |
会提示输入 action 的名称。输入后回车,fastlane 会在项目目录./fastlane/actions
生成一个相应名字的 ruby 脚本。打开之后发现其实是一个模板,只要摸清模板的结构,就能轻松地自定义 action 去运行指定命令。为了支持 SVN 管理的私有 pods 的上传(push)工作,下面为修改后的 action 脚本:
1 | module Fastlane |
编辑好脚本后,就可以在 Fastfile 方便地使用这一动作。fastlane 这一机制使其更好地与第三方持续集成工具进行结合,糅合了更多的第三方工具到 fastlane 工作流中。
jenkins 构建选项
在熟悉了 fastlane 工具链和 Fastfile 的编写后,就可以把 fastlane 运用于 jenkins 的工程构建中。
构建中
jenkins 创建一个 iOS 工程后,应首先对工程进行正确的配置。要确保 jenkins 有能力访问 svn 服务器的地址,并选择该工程构建所需的开发证书、发布证书和许可文件。
对其他的配置选项如有不清楚的地方,可以点击最右侧的问号按钮,来详细地了解它的作用。在构建选项设置模块,在Execute shell
中填写命令行命令如下:
1 | fastlane release |
再在 jenkins 项目首页左边栏点击构建,就会自动运行 Fastfile 中的 release lane, 进行一次 release 版本的自动化操作。
持续集成主要完成自动化打包、自动化测试、静态代码分析、自动化部署等任务,release lane 其实就是一次自动化打包和部署的过程。先递增版本号,再安装项目的 cocoapods 依赖,对工程进行编译并截图准备提交到 APP Store 的图片,然后自动化打包成 APP Store ipa 包,并发布到应用商店。
借助 fastlane 和 jenkins,还可以很轻松地运行自动化测试和静态代码分析。在 Fastfile 中定义一个 lane 如下:
1 | lane :test do |
clang_format 是自定义的 action, 用来规范化代码格式。主要就是遍历工程根目录下的 .h / .m 文件,对其运行命令即可:
1 | clang-format -i localpath -style=File |
scan 上文已经分析,它最终可以生成 html 形式的测试报告。
oclint_ci.sh
是运用 oclint 来进行静态代码分析的脚本:
1 | echo "xcodebuild clean" |
更多的 -disable-rule 选项可以参见 oclint 的官方文档。
构建后
在运行完 test lane 之后在项目目录生成了静态代码检查报告。报告的文件形式为 html 网页,通过添加 post build —> Publish HTML Reports 可以很方便地将该网页形式的报告发布在项目 workspace 的首页,只需要在 HTML directory to archive 空格处填写 html 归档的路径即可,如下图所示:
至此,基于 jenkins 服务端和 fastlane 脚本工具的持续集成方案得到了实践和验证。通过在实际应用中不断完善这一方案,iOS APP 的编译、测试、打包、发布等流程都实现了高度的自动化,给团队的开发效率提高了一个台阶。