Contents
  1. 1. 关于iOS的构建
    1. 1.1. 源码 -> .archive -> .ipa
    2. 1.2. 源码 -> .app -> .ipa
    3. 1.3. 参数说明
    4. 1.4. 补充说明
  2. 2. 关于Android的构建
  3. 3. 关于构建脚本
  4. 4. Jenkins的详细配置
    1. 4.1. 1、参数的传递
    2. 4.2. 2、修改build名称
    3. 4.3. 3、展示二维码图片
    4. 4.4. 4、收集编译成果物
  5. 5. 总结
  6. 6. 开箱即用
    1. 6.1. 1、添加构建脚本
    2. 6.2. 2、运行jenkins,安装必备插件
    3. 6.3. 3、创建Jenkins Job
    4. 6.4. 4、done!

《使用Jenkins搭建iOS/Android持续集成打包平台》一文中,我对如何使用Jenkins搭建iOS/Android持续集成打包平台的基础概念和实施流程进行了介绍。本文作为配套,对搭建持续集成打包平台中涉及到的执行命令、构建脚本(build.py),以及Jenkins的配置进行详细的补充说明。

当然,如果你不关心技术实现细节,也可以完全不用理会,直接参照【开箱即用】部分按照步骤进行操作即可。

关于iOS的构建

对iOS源码进行构建,目标是要生成.ipa文件,即iOS应用安装包。

当前,构建方式主要包括两种:

  • 源码 -> .archive文件 -> .ipa文件
  • 源码 -> .app文件 -> .ipa文件

这两种方式的主要差异是生成的中间产物不同,对应的,两种构建方式采用的命令也不同。

源码 -> .archive -> .ipa

1
2
3
4
5
6
7
8
# build archive file from source code
xcodebuild \ # xctool
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
-sdk ${SDK}
-archivePath ${archive_path}
archive

archive:对编译结果进行归档,会生成一个.xcarchive的文件,位于-archivePath指定的目录中。需要注意的是,对模拟器类型的sdk无法使用archive命令。

1
2
3
4
5
6
7
8
# export ipa file from .archive
xcodebuild -exportArchive \
-exportFormat format \
-archivePath xcarchivepath \
-exportPath destinationpath \
-exportProvisioningProfile profilename \
[-exportSigningIdentity identityname]
[-exportInstallerIdentity identityname]

源码 -> .app -> .ipa

1
2
3
4
5
6
7
# build .app file from source code
xcodebuild \ # xctool
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
-sdk ${SDK}
-derivedDataPath ${OUTPUT_FOLDER}
1
2
3
4
5
6
# convert .app file to ipa file
xcrun \
-sdk iphoneos \
PackageApplication \
-v ${OUTPUT_FOLDER}/Release-iphoneos/xxx.app \
-o ${OUTPUT_FOLDER}/Release-iphoneos/xxx.ipa

参数说明

xcodebuild/xctool参数

  • -workspace:需要打包的workspace,后面接的文件一定要是.xcworkspace结尾的;
  • -scheme:需要打包的Scheme,一般与$project_name相同;
  • -sdk:区分iphone device和Simulator,可通过xcodebuild -showsdks获取,例如iphoneosiphonesimulator9.3
  • -configuration:需要打包的配置文件,我们一般在项目中添加多个配置,适合不同的环境,Release/Debug;
  • -exportFormat:导出的格式,通常填写为ipa
  • -archivePath.xcarchive文件的路径;
  • -exportPath:导出文件(.ipa)的路径;
  • -exportProvisioningProfile:profile文件证书;
  • -derivedDataPath:指定编译结果文件的存储路径;例如,指定-derivedDataPath ${OUTPUT_FOLDER}时,将在项目根目录下创建一个${OUTPUT_FOLDER}文件夹,生成的.app文件将位于${OUTPUT_FOLDER}/Build/Products/${CONFIGURATION}-iphoneos中。

除了采用官方的xcodebuild命令,还可以使用由Facebook开发维护的xctoolxctool命令的使用方法基本与xcodebuild一致,但是输出的日志会清晰很多,而且还有许多其它优化,详情请参考xctool的官方文档。

xcrun参数

  • -v:指定.app文件的路径
  • -o:指定生成.ipa文件的路径

补充说明

1、获取Targets、Schemes、Configurations参数

在填写target/workspace/scheme/configuration等参数时,如果不知道该怎么填写,可以在项目根目录下执行xcodebuild -list命令,它会列出当前项目的所有可选参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜ 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之前都清理一下上一次编译遗留的缓存文件。

1
2
3
4
5
6
# clean before build
xctool \
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
clean

clean:清除编译产生的问题,下次编译就是全新的编译了

3、处理Cocoapod依赖库

另外一个需要注意的是,若项目是采用Cocoapod管理项目依赖,每次拉取最新代码后直接编译可能会报错。这往往是因为其他同事更新了依赖库(新增了第三方库或升级了某些库),而本地还采用之前的第三方库进行编译,从而会出现依赖库缺失或版本不匹配等问题。

应对的做法是,在每次build之前都更新一下Cocoapod。

1
2
3
4
# 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文件中的CFBundleVersionCFBundleShortVersionString进行修改即可。在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应用》

关于Android的构建

待续

关于构建脚本

对于构建脚本(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_PROFILEpgyer/fir.im账号这种比较固定的配置参数,就可以写死在脚本中。因此,在使用构建脚本(build.py)之前,需要先在脚本中配置下PROVISIONING_PROFILEpgyer/fir.im账号。

另外还想多说一句,pyger/fir.im这类第三方平台在为我们提供便利的同时,稳定性不可控也是一个不得不考虑的问题。在我使用pgyer平台期间,就遇到了平台服务变动、接口时而不稳定出现502等问题。因此,最好的方式还是自行搭建一套类似的服务,反正我是打算这么做了。

Jenkins的详细配置

对于Jenkins的详细配置,需要补充说明的有四点。

1、参数的传递

在构建脚本中,我们已经对常用的可配置参数实现了可传参机制。例如,在Terminal中可以通过如下形式调用构建脚本。

1
$ python build.py --scheme SCHEME --workspace Store.xcworkspace --configuration CONFIGURATION --output OUTPUT_FOLDER

那么我们在Jenkins中要怎样才能指定参数呢?

实际上,Jenkins针对项目具有参数化的功能。在项目的配置选项中,勾选This project is parameterized后,就可以为当前project添加多种类型的参数,包括:

  • Boolean Parameter
  • Choice Parameter
  • Credentials Parameter
  • File Parameter
  • Multi-line String Parameter
  • Password Parameter
  • Run Parameter
  • String Parameter

通常,我们可以选择使用String Parameter来定义自定义参数,并可对每个参数设置默认值。

当我们配置了BRANCHSCHEMECONFIGURATIONOUTPUT_FOLDERBUILD_VERSION这几个参数后,我们就可以在Build配置区域的Execute shell通过如下形式来进行参数传递。

1
2
3
4
5
6
$ python ${WORKSPACE}/Build_scripts/build.py \
true--scheme ${SCHEME} \
--workspace ${WORKSPACE}/Store.xcworkspace \
--configuration ${CONFIGURATION} \
--output ${WORKSPACE}/${OUTPUT_FOLDER} \
--build_version ${BUILD_VERSION}.${BUILD_NUMBER}

可以看出,参数的传递方式很简单,只需要预先定义好了自定义参数,然后就可以通过${Param}的形式来进行调用了。

不过你也许会问,WORKSPACEBUILD_NUMBER这两个参数我们并未进行定义,为什么也能进行调用呢?这是因为Jenkins自带部分与项目相关的环境变量,例如BRANCH_NAMEJOB_NAME等,这部分参数可以在shell脚本中直接进行调用。完整的环境变量可在Jenkins_Url/env-vars.html/中查看。

配置完成后,就可以在Build with Parameters中通过如下形式手动触发构建。

Jenkins manul build

2、修改build名称

Build History列表中,构建任务的名称默认显示为按照build次数递增的BUILD_NUMBER。有时候我们可能想在build名称中包含更多的信息,例如包含当次构建的SCHEMECONFIGURATION,这时我们就可以通过修改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还可以实现许多更加强大的自定义功能,大家可自行探索。

3、展示二维码图片

然后再说下如何在Build History列表中展示每次构建对应的二维码图片。

Jenkins build history

需要说明的是,在上图中,绿色框对应的内容是BuildName,我们可以通过build-name-setter插件来实现自定义配置;但是红色框已经不在BuildName的范围之内,而是对应的BuildDescription

同样地,Jenkins默认不支持在构建过程中自动修改BuildDescription,需要通过安装description setter plugin插件来辅助实现。安装description setter plugin插件后,在配置页面的Build栏目下,Add build step中会出现Set build description配置项,添加该配置项后就会出现如下配置框。

Jenkins set build description

该功能的强大之处在于,它可以在构建日志中通过正则表达式来匹配内容,并将匹配到的内容添加到BuildDescription中去。

例如,我们想要展示的二维码图片是在每次构建过程中生成的,因此我们首先要获取到二维码图片文件。

我的做法是,在build.py中将蒲公英平台返回的应用下载页面地址和二维码图片地址打印到log中。

1
2
3
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就可以按照如下正则表达式进行匹配:

1
appDownloadPage: (.*)$

接下来,就可以在Description中对匹配到的结果进行引用。

1
<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持续集成打包平台》中已进行了详细说明,在此就不再重复。

通过以上方式,就可以实现前面图片中的效果。

4、收集编译成果物

在上面讲解的展示二维码图片一节中,用到了${BUILD_URL}artifact/build_outputs/QRCode.png一项,这里的URL就是用到了编译成果物收集后保存的路径。

Archives build artifacts是Jenkins默认自带的功能,无需安装插件。该功能在配置页面的Post-build Actions栏目下,在Add post-build action的列表中选择添加Archives build artifacts

添加后的配置页面如下图所示:

Jenkins archive the artifacts

通常,我们只需要配置Files to archive即可。定位文件时,可以通过正则表达式进行匹配,也可以调用项目的环境变量;多个文件通过逗号进行分隔。

例如,假如我们想收集QRCode.pngStoreCI_Release.ipaInfo.plist这三个文件,那么我们就可以通过如下表达式来进行指定。

1
${OUTPUT_FOLDER}/*.ipa,${OUTPUT_FOLDER}/QRCode.png,${OUTPUT_FOLDER}/*.xcarchive/Info.plist

当然,目标文件的具体位置是我们在构建脚本(build.py)中预先进行处理的。

通过这种方式,我们就可以实现在每次完成构建后将需要的文件收集起来进行存档,以便后续在Jenkins的任务页面中进行下载。

show artifacts of 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

1、添加构建脚本

  • 在构建脚本中配置PROVISIONING_PROFILEpgyer/fir.im账号;
  • 在目标构建代码库的根目录中,创建Build_scripts文件夹,并将build.py拷贝到Build_scripts中;
  • Build_scripts/build.py提交到项目中。

除了与Jenkins实现持续集成,构建脚本还可单独使用,使用方式如下:

1
2
3
4
5
$ python ${WORKSPACE}/Build_scripts/build.py \
true--scheme ${SCHEME} \
--workspace ${WORKSPACE}/Store.xcworkspace \
--configuration ${CONFIGURATION} \
--output ${WORKSPACE}/${OUTPUT_FOLDER}

2、运行jenkins,安装必备插件

1
$ nohup java -jar jenkins_located_path/jenkins.war &

3、创建Jenkins Job

  • 在Jenkins中创建一个Freestyle project类型的Job,先不进行任何配置;
  • 然后将config.xml文件拷贝到~/.jenkins/jobs/YourProject/中覆盖原有配置文件,重启Jenkins;
  • 完成配置文件替换和重启后,刚创建好的Job就已完成了大部分配置;
  • Job Configure中根据项目实际情况调整配置,其中Git Repositories是必须修改的,其它配置项可选择性地进行调整。

4、done!

Contents
  1. 1. 关于iOS的构建
    1. 1.1. 源码 -> .archive -> .ipa
    2. 1.2. 源码 -> .app -> .ipa
    3. 1.3. 参数说明
    4. 1.4. 补充说明
  2. 2. 关于Android的构建
  3. 3. 关于构建脚本
  4. 4. Jenkins的详细配置
    1. 4.1. 1、参数的传递
    2. 4.2. 2、修改build名称
    3. 4.3. 3、展示二维码图片
    4. 4.4. 4、收集编译成果物
  5. 5. 总结
  6. 6. 开箱即用
    1. 6.1. 1、添加构建脚本
    2. 6.2. 2、运行jenkins,安装必备插件
    3. 6.3. 3、创建Jenkins Job
    4. 6.4. 4、done!