在《使用 Jenkins 搭建 iOS/Android 持续集成打包平台》一文中,我对如何使用 Jenkins 搭建 iOS/Android 持续集成打包平台的基础概念和实施流程进行了介绍。本文作为配套,对搭建持续集成打包平台中涉及到的执行命令、构建脚本(build.py),以及 Jenkins 的配置进行详细的补充说明。
当然,如果你不关心技术实现细节,也可以完全不用理会,直接参照【开箱即用】部分按照步骤进行操作即可。
对 iOS 源码进行构建,目标是要生成.ipa
文件,即 iOS 应用安装包。
当前,构建方式主要包括两种:
源码
-> .archive
文件 -> .ipa
文件源码
-> .app
文件 -> .ipa
文件这两种方式的主要差异是生成的中间产物不同,对应的,两种构建方式采用的命令也不同。
源码
-> .archive
-> .ipa
# build archive file from source code
xcodebuild \ # xctool
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
-sdk ${SDK}
archive -archivePath ${archive_path}
archive
:打包命令,会生成一个.xcarchive
的文件;archive 命令需要接一个参数-archivePath
,即存放 Archive 文件的目录
# export ipa file from .archive
xcodebuild -exportArchive \
-exportFormat format \
-archivePath xcarchivepath \
-exportPath destinationpath \
-exportProvisioningProfile profilename \
[-exportSigningIdentity identityname]
[-exportInstallerIdentity identityname]
源码
-> .app
-> .ipa
# build .app file from source code
xcodebuild \ # xctool
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
-sdk ${SDK}
-derivedDataPath build
# convert .app file to ipa file
xcrun \
-sdk iphoneos \
PackageApplication \
-v build/Release-iphoneos/xxx.app \
-o build/Release-iphoneos/xxx.ipa
xcodebuild/xctool 参数:
-workspace
:需要打包的 workspace,后面接的文件一定要是.xcworkspace
结尾的;-scheme
:需要打包的 Scheme,一般与$project_name
相同;-sdk
:区分 iphone device 和 Simulator;-configuration
:需要打包的配置文件,我们一般在项目中添加多个配置,适合不同的环境,Release/Debug;-derivedDataPath
:指定编译结果文件的存储路径;例如,指定-derivedDataPath build
时,将在项目根目录下创建一个build
文件夹,生成的.app
文件将位于build/Build/Products/Release-iphoneos
中。除了采用官方的xcodebuild
命令,还可以使用由 Facebook 开发维护的xctool
。xctool
命令的使用方法基本与xcodebuild
一致,但是输出的日志会清晰很多,而且还有许多其它优化,详情请参考xctool
的官方文档。
xcrun 参数:
-v
:指定.app
文件的路径-o
:指定生成.ipa
文件的路径1、获取 Targets、Schemes、Configurations 参数
在填写target
/workspace
/scheme
/configuration
等参数时,如果不知道该怎么填写,可以在项目根目录下执行xcodebuild -list
命令,它会列出当前项目的所有可选参数。
➜ Store_iOS git:(NPED) ✗ xcodebuild -list
Information about project "Store":
Targets:
Store
StoreCI
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
Store
StoreCI
2、清除缓存文件
在每次 build 之后,工程目录下会遗留一些缓存文件,以便下次 build 时减少编译时间。然而,若因为工程配置错误等问题造成编译失败后,下次再编译时就可能会受到缓存的影响。
因此,在持续集成构建脚本中,比较好的做法是在每次 build 之前都清理一下上一次编译遗留的缓存文件。
# clean before build
xctool \
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
clean
clean
:清除编译产生的问题,下次编译就是全新的编译了
3、处理 Cocoapod 依赖库
另外一个需要注意的是,若项目是采用 Cocoapod 管理项目依赖,每次拉取最新代码后直接编译可能会报错。这往往是因为其他同事更新了依赖库(新增了第三方库或升级了某些库),而本地还采用之前的第三方库进行编译,从而会出现依赖库缺失或版本不匹配等问题。
应对的做法是,在每次 build 之前都更新一下 Cocoapod。
# Update pod repository
pod repo update
# Install pod dependencies
pod install
4、修改编译包的版本号
通过持续集成打包,我们会得到大量的安装包。为了便于区分,比较好的做法是在 App 中显示版本号,并将版本号与 Jenkins 的BUILD_NUMBER
关联起来。
例如,当前项目的主版本号为2.6.0
,本次构建的BUILD_NUMBER
为 130,那么我们就可以将本次构建的 App 版本号设置为2.6.0.130
。通过这种方式,我们可以通过 App 中显示的版本号快速定位到具体到构建历史,从而对应到具体的代码提交记录。
要实现对 App 版本号的设置,只需要在打包前对Info.plist
文件中的CFBundleVersion
和CFBundleShortVersionString
进行修改即可。在 Python 中,利用plistlib
库可以很方便地实现对Info.plist
文件的读写。
5、模拟器运行
如果持续集成测试是要运行在 iOS 模拟器上,那么就需要构建生成.app
文件。
在前面讲解的两种构建方式中,中间产物都包含了.app
文件。对于以.xcarchive
为中间产物的方式,生成的.app
文件位于output_dir/StoreCI_Release.xcarchive/Products/Applications/
目录中。
不过,这个.app
文件在模拟器中还无法直接运行,还需要在 Xcode 中修改Supported Platforms
,例如,将iphoneos
更改为iOS
。详细原因请参考《从 0 到 1 搭建移动 App 功能自动化测试平台(1):模拟器中运行 iOS 应用》
待续
对于构建脚本(build.py
)本身,源码应该是最好的说明文档。
在build.py
脚本中,主要实现的功能就四点:
.ipa
文件,这部分包含了关于iOS的构建
部分的全部内容;Info.plist
,将编译包的版本号与 Jenkins 的 BuildNumber 关联起来;.ipa
文件至pyger
/fir.im
平台,并且做了失败重试机制;pyger
/fir.im
平台页面中的二维码,将二维码图片保存到本地。需要说明的是,对于构建任务中常用的可配置参数,例如BRANCH
/SCHEME
/CONFIGURATION
/OUTPUT_FOLDER
等,需要在构建脚本中通过OptionParser
的方式实现可传参数机制。这样我们不仅可以命令行中通过传参的方式灵活地调用构建脚本,也可以在 Jenkins 中实现参数传递。
之所以强调常用的
可配置参数,这是为了尽可能减少参数数目,降低脚本调用的复杂度。像PROVISIONING_PROFILE
和pgyer/fir.im
账号这种比较固定的配置参数,就可以写死在脚本中。因此,在使用构建脚本(build.py)之前,需要先在脚本中配置下PROVISIONING_PROFILE
和pgyer/fir.im
账号。
另外还想多说一句,pyger
/fir.im
这类第三方平台在为我们提供便利的同时,稳定性不可控也是一个不得不考虑的问题。在我使用pgyer
平台期间,就遇到了平台服务变动、接口时而不稳定出现 502 等问题。因此,最好的方式还是自行搭建一套类似的服务,反正我是打算这么做了。
对于 Jenkins 的详细配置,需要补充说明的有四点。
在构建脚本中,我们已经对常用的可配置参数实现了可传参机制。例如,在 Terminal 中可以通过如下形式调用构建脚本。
$ python build.py --scheme SCHEME --workspace Store.xcworkspace --configuration CONFIGURATION --output OUTPUT_FOLDER
那么我们在 Jenkins 中要怎样才能指定参数呢?
实际上,Jenkins 针对项目具有参数化的功能。在项目的配置选项中,勾选This project is parameterized
后,就可以为当前 project 添加多种类型的参数,包括:
通常,我们可以选择使用String Parameter
来定义自定义参数,并可对每个参数设置默认值。
当我们配置了BRANCH
、SCHEME
、CONFIGURATION
、OUTPUT_FOLDER
、BUILD_VERSION
这几个参数后,我们就可以在Build
配置区域的Execute shell
通过如下形式来进行参数传递。
$ python ${WORKSPACE}/Build_scripts/build.py \
--scheme ${SCHEME} \
--workspace ${WORKSPACE}/Store.xcworkspace \
--configuration ${CONFIGURATION} \
--output ${WORKSPACE}/${OUTPUT_FOLDER} \
--build_version ${BUILD_VERSION}.${BUILD_NUMBER}
可以看出,参数的传递方式很简单,只需要预先定义好了自定义参数,然后就可以通过${Param}
的形式来进行调用了。
不过你也许会问,WORKSPACE
和BUILD_NUMBER
这两个参数我们并未进行定义,为什么也能进行调用呢?这是因为 Jenkins 自带部分与项目相关的环境变量,例如BRANCH_NAME
、JOB_NAME
等,这部分参数可以在 shell 脚本中直接进行调用。完整的环境变量可在Jenkins_Url/env-vars.html/
中查看。
配置完成后,就可以在Build with Parameters
中通过如下形式手动触发构建。
在Build History
列表中,构建任务的名称默认显示为按照 build 次数递增的BUILD_NUMBER
。有时候我们可能想在 build 名称中包含更多的信息,例如包含当次构建的SCHEME
和CONFIGURATION
,这时我们就可以通过修改BuildName
实现。
Jenkins 默认不支持BuildName
设置,但可通过安装build-name-setter
插件进行实现。安装build-name-setter
插件后,在配置页面的Build Environment
栏目下会出现Set Build Name
配置项,然后在Build Name
中就可以通过环境变量参数来设置 build 名称。
例如,要将 build 名称设置为上面截图中的StoreCI_Release_#130
样式,就可以在Build Name
中配置为${SCHEME}_${CONFIGURATION}_#${BUILD_NUMBER}
。
除了在Build Name
中传递环境变量参数,build-name-setter
还可以实现许多更加强大的自定义功能,大家可自行探索。
然后再说下如何在Build History
列表中展示每次构建对应的二维码图片。
需要说明的是,在上图中,绿色框对应的内容是BuildName
,我们可以通过build-name-setter
插件来实现自定义配置;但是红色框已经不在BuildName
的范围之内,而是对应的BuildDescription
。
同样地,Jenkins 默认不支持在构建过程中自动修改BuildDescription
,需要通过安装description setter plugin
插件来辅助实现。安装description setter plugin
插件后,在配置页面的Build
栏目下,Add build step
中会出现Set build description
配置项,添加该配置项后就会出现如下配置框。
该功能的强大之处在于,它可以在构建日志中通过正则表达式来匹配内容,并将匹配到的内容添加到BuildDescription
中去。
例如,我们想要展示的二维码图片是在每次构建过程中生成的,因此我们首先要获取到二维码图片文件。
我的做法是,在build.py
中将蒲公英平台返回的应用下载页面地址和二维码图片地址打印到 log 中。
appDownloadPage: https://www.pgyer.com/035aaf10acf5dd7c279c4fe423a57674
appQRCodeURL: https://o1wjx1evz.qnssl.com/app/qrcodeHistory/fe7a8c9051f0c7fc0affc78f40c20a4b5e4bdb4c77b91a29501f55fd9039c659
Save QRCode image to file: /Users/Leo/.jenkins/workspace/DJI_Plus_Store_iOS/build_outputs/QRCode.png
然后,在Set build description
配置项的Regular expression
就可以按照如下正则表达式进行匹配:
appDownloadPage: (.*)$
接下来,就可以在Description
中对匹配到的结果进行引用。
<img src='${BUILD_URL}artifact/build_outputs/QRCode.png'>\n<a href='\1'>Install Online</a>
在这里,我们用到了 HTML 的标签,而 Jenkins 的Markup Formatter
默认是采用Plain text
模式,因此还需要对 Jenkins 对系统配置进行修改,在《使用 Jenkins 搭建 iOS/Android 持续集成打包平台》中已进行了详细说明,在此就不再重复。
通过以上方式,就可以实现前面图片中的效果。
在上面讲解的展示二维码图片一节中,用到了${BUILD_URL}artifact/build_outputs/QRCode.png
一项,这里的 URL 就是用到了编译成果物收集后保存的路径。
Archives build artifacts
是 Jenkins 默认自带的功能,无需安装插件。该功能在配置页面的Post-build Actions
栏目下,在Add post-build action
的列表中选择添加Archives build artifacts
。
添加后的配置页面如下图所示:
通常,我们只需要配置Files to archive
即可。定位文件时,可以通过正则表达式进行匹配,也可以调用项目的环境变量;多个文件通过逗号进行分隔。
例如,假如我们想收集QRCode.png
、StoreCI_Release.ipa
、Info.plist
这三个文件,那么我们就可以通过如下表达式来进行指定。
${OUTPUT_FOLDER}/*.ipa,${OUTPUT_FOLDER}/QRCode.png,${OUTPUT_FOLDER}/*.xcarchive/Info.plist
当然,目标文件的具体位置是我们在构建脚本(build.py
)中预先进行处理的。
通过这种方式,我们就可以实现在每次完成构建后将需要的文件收集起来进行存档,以便后续在 Jenkins 的任务页面中进行下载。
也可以直接通过归档文件的 URL 进行访问。例如,上图中QRCode.png
的 URL 为Jenkins_Url/job/JenkinsJobName/131/artifact/build_outputs/QRCode.png
,而Jenkins_Url/job/JenkinsJobName/131/
即是${BUILD_URL}
,因此可以直接通过${BUILD_URL}artifact/build_outputs/QRCode.png
引用。
至此,《使用 Jenkins 搭建 iOS/Android 持续集成打包平台》一文中涉及到的 Jenkins 配置和构建脚本实现细节均已补充完毕了。相信大家结合这两篇文章,应该会对如何使用 Jenkins 搭建 iOS/Android 持续集成打包平台的基础概念和实现细节都有一个比较清晰的认识。
对于还未完善的部分,我后续将在博客中进行更新。
操作手册请参考文章末尾的【开箱即用】部分,祝大家玩得愉快!
GitHub 地址:https://github.com/debugtalk/JenkinsTemplateForApp
PROVISIONING_PROFILE
和pgyer/fir.im
账号;Build_scripts
文件夹,并将build.py
拷贝到Build_scripts
中;Build_scripts/build.py
提交到项目中。除了与 Jenkins 实现持续集成,构建脚本还可单独使用,使用方式如下:
$ python ${WORKSPACE}/Build_scripts/build.py \
--scheme ${SCHEME} \
--workspace ${WORKSPACE}/Store.xcworkspace \
--configuration ${CONFIGURATION} \
--output ${WORKSPACE}/${OUTPUT_FOLDER}
$ nohup java -jar jenkins_located_path/jenkins.war &
Freestyle project
类型的 Job,先不进行任何配置;config.xml
文件拷贝到~/.jenkins/jobs/YourProject/
中覆盖原有配置文件,重启 Jenkins;Job Configure
中根据项目实际情况调整配置,其中Git Repositories
是必须修改的,其它配置项可选择性地进行调整。微信公众号:DebugTalk
原文链接:http://debugtalk.com/post/iOS-Android-Packing-with-Jenkins-details
构建脚本&配置文件:https://github.com/debugtalk/JenkinsTemplateForApp