Jenkins + Xcode9 持续集成环境搭建
最近搭建了一套 Jenkins + Xcode9 持续集成环境,记录一下。
安装 Jenkins
推荐通过 homebrew 安装,也可以通过下载 pkg 包安装(Jenkins OSX Installer),不过还是 brew 方便些。
1 | brew install jenkins |
如果没安装 Java,需要先装一下:
1 | brew cask install caskroom/versions/java8 |
初始化配置
这里提一下关键的点,就不一一截图细说了。
浏览器中打开 http://localhost:8080
第一次打开会提示去取 initialAdminPassword(一行红字标明路径,很明显)里的密码。cat path/to/initialAdminPassword 拿到后贴到输入框中 next。
→ 安装推荐的插件 → 创建用户。完成后重新登录 Jenkins。
接下来在 Manage Jenkins -> Manage Plugins -> Available 安装一些需要的插件:
Xcode integration
Keychains and Provisioning Profiles Management可能一些教程推荐装这个插件,然后配置一下 login.keychain 填一些证书信息什么的就很方便导出
.ipa了。but,很遗憾 Xcode9 无法读取 login.keychain 的信息,现在导出需要提供一份ExportOptions.plist,后面再具体说怎么操作。GIT plugin
Git Parameter
局域网内访问 Jenkins
使用 brew 安装 Jenkins 默认将 httpListenAddress 设置为 127.0.0.1,本机可以通过 localhost:8080 访问,但局域网内无法通过 本机 ip:8080 访问。
1 | ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist |
将两个 plist 文件中 httpListenAddress 改为 0.0.0.0 重启 Jenkins 即可。
重启 Jenkins 可以执行:
1 | brew services restart jenkins |
或者在浏览器里:
1 | http://localhost:8080/restart |
创建/配置项目
Net Item → 输入 item name → Freestyle project → OK
Source Code Management 选择 Git,并填入相应的信息。

切换到 Build 栏,按顺序添加 build step。
由于我们使用 Cocoapods,首先安装 pod 的第三方库。
点击 Add build step → Execute shell

切换的 Podfile 文件目录下,执行 pod install 命令
1 | !/bin/bash -l |

接下来添加另一个 build step,这次选择的是 Xcode。
使用 Cocoapods,Target 这栏不用填,点击右侧的 Settings,按截图设置下。

Code signing & OS X keychain options 可以不设置。

这里有个注意的地方,在 Xcode9 以前,可以勾选底下的 Pack application, build and sign .ipa?,并设置些相关的信息就能导出 .ipa。但是使用 Xcode9 会报这样的错误:
1 | "Error Domain=IDEProvisioningErrorDomain Code=9 \"\"MyApp.app\" requires a provisioning profile.\" UserInfo={NSLocalizedDescription=\"MyApp.app\" requires a provisioning profile., NSLocalizedRecoverySuggestion=Add a profile to the \"provisioningProfiles\" dictionary in your Export Options property list.}" |
大概意思就是需要指定 ExportOptions.plist,这个文件内容大概是这样的:
1 |
|
可以拷贝然后修改一下相应的 key value。不过一个简单获取这个文件的方式是:手动使用 Xcode9 打包,在导出 .ipa 的文件夹里应该有 4 个文件,其中一个就是 ExportOptions.plist。可以把它拷贝到 workspace 目录下。
☕️ 补充:如果 Signing 勾选了 Automatically manage signing,就简单多了,它的 exportOptons.plist 是这样的:
1 |
|

在 Xcode step 下面再添加一个 Execute shell
1 | xcodebuild -exportArchive -archivePath ${WORKSPACE}/build/${JOB_NAME}.xcarchive -exportPath ${JENKINS_HOME}/jobs/${JOB_NAME}/builds/${BUILD_NUMBER}/archive -exportOptionsPlist ${WORKSPACE}/ExportOptions.plist |
-exportOptionsPlist ${WORKSPACE}/ExportOptions.plist 这个选项就是指定刚刚拷贝到 workspace 的 plist。
到这里配置就完成了。回到 item 页面,点击 Build Now。

左侧就能看到 building 的项目了,点击 Console Output 可以看到 log 记录,如无意外最后输出 Finished: SUCCESS。
可以在两个目录找到 .xcarchive 和 .ipa :
1 | /Users/yourname/.jenkins/workspace/jobName/build/jobName.xcarchive |
配置 Git Parameter
有时候想指定打某个分支的包,用 Git Parameter 就很方便了。
回到配置页最顶部,勾选 This project is parameterized,选择 Git Parameter


Name 后面需要用到,Type 这里就选 Branch 了。
再到 Source Code Management,修改一下设置。

红框部分修改为刚刚设置的 Name,以 $ 开头。

这时 Build Now 就变成 Build with Parameters 了。右侧选择分支,然后开 build。
这个插件适合手动构建,如果设置了轮询自动构建,会因为找不到分支而构建失败,有什么好的方案告诉我啊。

点击右侧的 Advanced,有一栏「Default Value」可以指定默认的分支。这样轮询构建的时候就会自动选择这个分支。
获取 Bundle ID 和 Provisioning Profile 生成 ExportOptions.plist
☕️ 补充:Signing 勾选了 Automatically manage signing 可以不操作这步。
现在可以指定任意的分支打包了,但可能每个分支的 Bundle ID 或者 Provisioning Profile 跟之前准备的 exportOptions 不一致,这样就得准备多一份 ExportOptions.plist。就写个脚本来获取吧。
方案是这样的,先准备一份 exportOptions 模版,通过 xcodebuild -showBuildSettings 获取 Bundle ID 和 Provisioning Profile,替换模版生成一个新的 .plist,export 命令指定这个新的 .plist 就好了。
在 xcodebuild -exportArchive 前 Add execute shell
这里就玩玩 Swift 脚本了,在文本开头加上 !/usr/bin/env swift
1 | #!/usr/bin/env swift |
后面的 -exportOptionsPlist 指定 directoryPath/ExportOptions.plist 。
单元测试
采用 xcodebuild test + xcpretty 方案。
xcpretty 可以将 xcodebuild test 输出内容格式化,并且可以导出 xml、json 或者 html。
格式化的内容大概是这样子的:
1 | ✓ testExample (0.001 seconds) |
首先安装 xcpretty :
1 | gem install xcpretty |
Add execute shell:
1 | xcodebuild test \ |
&& exit ${PIPESTATUS[0]} 使用 exit 参数帮助 Jenkins 确定是否失败。
可以把这段 shell 放在 pod install 之后,测试失败后 build 失败而不继续 archive。
上传 ipa 到测试平台
你可以找到平台提供的集成文档,例如 fir.im Jenkins 插件使用方法 或者 蒲公英 - 使用 Jenkins 实现持续集成 (iOS)
这里演示使用 ios-ipa-server 搭建「本地自签名 https 服务器,快速安装 ipa」。
安装 ios-ipa-server
1 | npm install -g ios-ipa-server |
开启 ios-ipa-server
1 | cd /path/of/ipa |
将导出的 ipa 文件复制到 ios-ipa-server 目录
add Execute Shell:
1 | !/bin/bash -l |
关于证书问题,iOS 10.3 (不确定版本了) 以上除了需要安装描述文件外,还需要到「关于本机」- 「证书信任设置」将 ios-ipa-server 开启。
Build Triggers
可以设置一些触发构建条件。
Trigger builds remotely

在远程终端就可以通过以下指令来触发构建:
1 | curl http://username:password@ipAddr:8080/job/jobName/build\?token\=tokenName |
或者 /buildWithParameters?,指定 Git Parameter 的分支 &Branches=master
1 | curl http://username:password@ipAddr:8080/job/jobName/buildWithParameters\?token\=tokenName\&Branches\=master |
Poll SCM
Poll SCM 可以设置一个轮询周期,当 Git 上有代码更新才会触发构建:

可能出现的错误和解决
pod: command not found
Execute shell 第一行加上 !/bin/bash -l
1 | !/bin/bash -l |
export 语句将控制台语言环境设置为 UTF-8
xcode-select: error: tool ‘xcodebuild’ requires Xcode
xcode 路径错误,在终端输入以下:
1 | sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/ |
error: exportArchive: “xx.app” requires a provisioning profile Jenkins
正文已解决,参考链接。
最后,备份 Jenkins 配置
理论上,把 Jenkins Home,也就是用户目录下 .jenkins 文件夹,打个 .zip 保存起来就可以了。thinBackup 就能做到这些,可以完全备份或者差异备份。
不过这里用的是 GitHub 上的一个脚本(Backup Jenkins home periodicallly with git.),将 *.xml 配置文件保存到 git。
1 | !/bin/bash |

新建一个 Item,设置一个周期性 Build Trigger。H 18 * * 5 表示每周五 18 点…
Add Execute shell,将上面脚本填进去就行了。