一些概念
-
CI :持续集成,持续集成是指多名开发者在开发不同功能代码的过程当中,可以频繁的将代码行合并到一起并切相互不影响工作。
-
CD:持续部署是基于某种工具或平台实现代码自动化的构建、测试和部署到线上环境以实现交付高质量的产品。
-
Pipeline:所谓Pipeline就是实现CI或CD要经历的一系列步骤或流程
常见的CI/CD工具:
-
Jenkins
-
CircleCI
-
TeamCity
-
Bamboo
-
GitLab
-
Buddy
-
Travis CI
-
Codeship
-
GoCD
-
Wercker
-
Semaphore
-
Spinnaker
-
Buildbot
以Jenkins作为Pipeline工具,从0到1对一个iOS工程实现CI/CD的功能
iOS项目接入CI/CD的目的
-
在开发阶段多人协作时高质量的合并代码到某一个分支,多人团队开发中,代码必须要经过一系列的检查,达到一定的标准才能合入到master,比如:代码是否符合lint规范,代码是够能够正常编译,单元测试是否能够通过,测试覆盖率是否达标等等条件
-
打包分发测试包给QA人员测试,在传统的开发模式中,打包是一个机械式重复的工作,我们可以把这一部分工作交给计算机自动执行,避免一些人为的机械式工作。
-
打包上传AppStore发布。发布Appstore我们也可以通过pipleine去执行,我们可以把发布的内容,checklist做成一系列的流程与规范,在发布时,只需要运行我们的Pipeline程序,就能做到一键发布,通时也能通过pipeline去检测我们的发布所需的物料是否完全具备,如果不具备,则发布会直接不成功。这么做的好处是其一pipeline为我们在发布app时严格把关发布条件,不符合发布条件,直接不允许发布,其二是节省了人力,避免一些重复打包的工作,其三更加安全,如果是手动发布的话,需要发布人员,进入到iTunes Connect后台,进行手动操作,如果使用pipeline的话,则不需要进入iTunes Connect后台,避免一些敏感信息的泄露。
Jenkins
-
安装Jenkins并设置Jenkins
- 使用以下命令直接安装并启动Jenkins服务
brew services start jenkins-lts
- 启动成功之后,浏览器访问 http://localhost:8080,便能直接访问访问Jenkins,根据指引一步一步执行初始化设置
第一个Jenkins job: Hello world
访问http://localhost:8080,进入Jenkins首页dashbord,开始第一个Jenkis job
-
新建任务
- 输入 项目名称,选择 流水线,点击 确定


- 在Conguration页面,即可看到Pipeline script的输入页面,输入Hello world代码之后,第一个pipeline job就创建成功了

-
在Hello world pipleine页面,点击 立即构建,则会看到我们的第一个pipeline job构建成功了
从Hello world pipeline的代码从可以看到,一个pipeline的job的一些关键字,其表示的意义如下:
-
pipeline:表明这是一个pipeline程序,pipeline所有程序都要写在pipeline这个代码块里
-
agent:表明这个程序的执行的位置,说通俗一点就是在哪台机器上执行或在云服务器上的哪个Node上执行
-
stages:stages部分包含一系列一个或多个阶段指令,是pipeline描述的大部分“工作”所在的位置。 对于持续交付过程的每个离散部分,建议阶段至少包含一个阶段指令,例如构建、测试和部署。
-
step: step部分定义了在给定stage阶段指令中要执行的一系列一个或多个步骤。比如在helloworld程序中,step就定义打印出了helloworld
-
关于更多的Jenkins pipeline语法可以参考这里 的Jenkins语法
Jenkins MultiBranch job
-
创建MultiBranch job的步骤与Helloworld job类似,只是在选择job类型的为 多分支流水线,在MultiBranch job的configuration的页面则可以设置代码仓库地址,轮训时机,以及分支信息,之所以叫MultiBranch,是因为可以支持多个分支job的构建(比如master,Feature等等分支),具体信息可以在Configuration页面配置。
-
MultiBranch job配置完成之后,就能开始我们的iOS的工程Pipeline了,在MultiBranch job的执行代码主要是根据工程中的JenkinsFile执行的,在配置的MultiBranch job页面,点击 立即扫描多分支流水线,Jenkins就会根据配置的分支策略进行扫描,代码拉取,依次执行各个分支的JenkinsFile里面的Pipeline代码

编写iOS工程pipeline代码
- Jenkins是一个pipeline的执行程序,我们可以在stage中,定义我们想要执行的脚本程序,在iOS工程中,我们主要想通过Jenkins执行iOS工程的setup,编译,运行,单元测试,打包,上传包等一系列代码程序,但是这些程序的书写,并不是采用Jenkins的pipeline语法去写,而是通过Jenkins执行xcodebuild或fastlane等一些命令来完成。

通过Fastlane执行iOS程序的编译,运行,单元测试,打包,上传等工作
Fastlane
-
Fastlane的优势以及Fastlane的其他的介绍,这里不过多描述,直接去它的官方文档即可了解,我们重点放在如何使用Fastlane完成我们的每个stage
-
在我们的iOS工程中执行
fastlane init
即可完成fastlane的初始化,初始化完成之后,工程的根目录会生产一个fastlane的目录,在该目录里面有两个文件分别是Appfile和Fastfile,我主要是是在Fastlane里面定义的我们的编译打包代码。
定义编译导出包的的lane
在Fastfile中,开始定义我们的打包代码,其语法规则是ruby,主要调用Fastlane提供的 build_app 这个方法即可完成,在我们的实力代码中,主要为build_app
设置如下参数,

-
schema: ipa的Schema名称,
-
workspace: 工程的workspace名称,
-
include_bitcode: 默认为true,在工程中一般设置为false,
-
configuration:当前xcconfig file的名称,xcconfig 文件主要用于设置一些环境变量或者可以通过该文件可以设置在不同的环境下buildsetting里面的值(比如可以通过该文件设置分别在Sit和UAT环境设置不同的BundleID)
-
clean: 编译前是否清理工程
-
export_method: 包的导出方式,支持app-store, validation, ad-hoc, package, development, development-id, mac-application
-
export_options: 配置导出包时的一些数据,比如描述文件的名称,证书名称等等,证书的配置方式为手动时,需要配置改信息,不然找不到证书,当然我们也可以使用Fastlane 的match lane进行自动打证书配置
-
Output_directory:包的导出目录
-
output_name:包的名称
该lane定义好了,我们就可以通过执行 fastlane + lan的名称,就能直接执行该lane了,这里我们执行 fastlane build_uat
定义单元测试的lane
Fastlane中提供了run_tests 方法运行单元测试,我们需要再iOS工程设置好单元测试测试Schema,Target 以及开启收集测试覆盖率等功能,这样run_test方法才能运行成功

run_tests方法的参数很简单,这里就不必一一解释了,需要强调的是,我们在单元测试运行完成之后,需要生成测试报告,这里我们通过coverage_report 这个lane来生成测试报告

在过coverage_report函数中,调用了xcov这个方法去生成,在这个方法中,我们可以设置测试覆盖率最低阀值,可以在fastlane目录里面添加一个.xcovignore文件,来设置测试忽略文件路径,设置了之后,在计算测试覆盖率就不会把这些文件算进去(比如 UI,Pod等文件)。xcov内部实现的主要逻辑是解析在unit test完成之后生成的xcresult文件,我们可以在Xcode中,通过以下方式查看xcresult文件的位置。同理,执行 fastlane unit_test即可执行这个lane

定义代码静态检查的lane
在fastlane中,仍然是swiftlint就行代码静态检测,但是swiftlint只提供swiftlint这个方法,并不会真正安装swiftlint,所以我们需要在setup工程时安装swiftlint,然后再把swiftlint的执行文件路径设置进去

-
executable:swiftlint的二进制文件路径
-
config_file:swiftlint.yml路径,定义了lint的规则
-
mode:lint的模式,可以设置为lin,fix, auto_correct等值
定义上传Testfight的lane
该lane的作用是为了完成测试包上传Appstore,主要用到的方法是upload_to_testflight 。
因为需要往开发者的iTunes Connect后台上传ipa包,所以我们需要提供一些身份验证信息来完成对fastlane的鉴权,其中api_key就是我们的授权的信息,在app_store_connect_api_key中key_id, issuer_id, 这两个信息可以在iTunes Connect后台生成,具体步骤,可以参考一下截图


更多的fastlane的功能可以参考去官网,这里就不一一赘述了
Jenkins 执行 fastlane命令
fastlane定义好了各个阶段的具体执行的内容,那么Jenkins要做的就是在stage中使用这些定义好的lane,在我们的Sample工程中,主要定义以下stage来完成这个pipeline
-
stage(‘Setup’):主要完成工程的Setup,包含bundle包的安装,Pod的安装 (bundle里面主要安装fastlane和pod,我们一个工程所需要的所有命令都通过一个bundler来管理,这样就不需要进行全局安装了,互不干扰)
-
stage(‘Swiftlint’):主要完成代码的静态检查,执行我们刚刚定义好的 fastlane static_code_check 这个lane
-
stage(‘Unit Tests’): 主要完成单元测试的执行,输出测试覆盖率,如果测试不达标,则直接pipleine直接报错,抛出failure异常,执行我们刚刚定义的 fastlane unit_tests
-
Stage(‘Upload Testflight’): 主要完成iOS工程的编译,包的导出和上传Testflight, 执行fastlane release_to_testflight这个lane
-
除了定义这几个步骤之外,我们还需要在这些步骤执行完成之后,post一些信息,这里我们使用Jenkins中的post语法来完成,其作用就是在所有的stage完成之后,执行,在这里可以定义stage成功之后的行为,或者失败之后的行为。我们这里post里面做两件事情,其一是 stages完成之后,把生成的测试覆盖率的html抛出来,这样我们就能pipeline的job详情页面通过网页的方式查看测试覆盖率;其二是 定义stages完成之后的清理工作,主要完成workspace的清理或一些临时数据的清理。
pipeline {
agent any
environment {
RUBY_HOME = "/usr/local/opt/ruby"
PATH = "$RUBY_HOME/bin:$PATH"
LANG = "en_US.UTF-8"
}
parameters {
string(name: 'APP_BUILD_FOLDER', defaultValue: './build', description: 'Application build output folder')
string(name: 'APP_PACKAGES_FOLDER', defaultValue: './build/packages', description: 'Application packages output folder')
string(name: 'APP_TEST_FOLDER', defaultValue: './build/tests', description: 'Application test output folder')
}
stages {
stage('Setup') {
steps {
sh 'gem install bundler'
sh 'bundle install'
sh 'bundle exec pod install --verbose'
}
}
stage('Swiftlint') {
steps {
script {
sh 'bundle exec fastlane static_code_check'
}
}
}
stage('Unit Test') {
steps {
script {
sh 'bundle exec fastlane unit_tests'
junit 'build/tests/report.junit'
}
}
}
stage('Upload TestFlight') {
steps {
script {
sh 'bundle exec fastlane release_to_testflight_mock'
}
}
}
}
post {
success {
dir("${params.APP_TEST_FOLDER}") {
publishHTML target: [
allowMissing : false,
alwaysLinkToLastBuild: false,
keepAll : true,
reportDir : './coverage',
reportFiles : '**/*.html',
reportName : 'Test Coverage'
]
}
}
cleanup {
cleanWs()
}
}
}
最后把我们的iOS工程推送到远端仓库,在Jenkins job页面点击 立即扫描多分支流水线 就能开始拉取最新的代码,根据Jenkinsfile执行我们的定义的stage,如果不出意外的话,将会看到如下结果

网友评论