移动测试基础 关于持续集成打包平台的 Jenkins 配置和构建脚本实现细节

debugtalk · 2016年07月26日 · 最后由 小背影 回复于 2016年12月06日 · 2753 次阅读

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

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

关于 iOS 的构建

对 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 开发维护的xctoolxctool命令的使用方法基本与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文件中的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 中可以通过如下形式调用构建脚本。

$ 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通过如下形式来进行参数传递。

$ 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}的形式来进行调用了。

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

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

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列表中展示每次构建对应的二维码图片。

需要说明的是,在上图中,绿色框对应的内容是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 持续集成打包平台》中已进行了详细说明,在此就不再重复。

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

4、收集编译成果物

在上面讲解的展示二维码图片一节中,用到了${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.pngStoreCI_Release.ipaInfo.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

1、添加构建脚本

  • 在构建脚本中配置PROVISIONING_PROFILEpgyer/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}

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

$ 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!

Read More ...

微信公众号:DebugTalk
原文链接:http://debugtalk.com/post/iOS-Android-Packing-with-Jenkins-details
构建脚本&配置文件:https://github.com/debugtalk/JenkinsTemplateForApp

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞

今天试试,赞

求问个问题:iOS 打正式包之前,需要先 debug 生成一个 bundle 资源包,然后才能打正式包,这种情况命令行怎么实现?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册