利用 Jenkins 实现 Xcode 自动打包

Jenkins 是什么?


Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建、测试和部署软件。 Jenkins 支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序。

Jenkins 是一个 Java 程序,那么安装 Jenkins 就分为两步:

  • 安装 Java 环境
  • 安装 Jenkins

具体安装方式有两种:

  • 手动安装:在 JavaJenkins 官网下载相应的安装包进行安装
  • 通过包管理工具 Homebrew 进行安装

这里为了方便管理,本文采用 Homebrew 进行安装,手动安装过程稍有差异,不过安装完后 Jenkins 的配置过程完全一致。

安装 Jenkins


没有 Java 环境的话,执行 brew install jenkins 会提示没有 Java 环境:

~ brew install jenkins
jenkins: Java 1.8 is required to install this formula.
Install AdoptOpenJDK 8 with Homebrew Cask:
  brew cask install homebrew/cask-versions/adoptopenjdk8
Error: An unsatisfied requirement failed this build.

按照提示:

  • 执行 brew cask install homebrew/cask-versions/adoptopenjdk8 安装 Java
  • 执行 brew install jenkins 安装 Jenkins

启动 Jenkins


通过 Homebrew 安装 Jenkins 的 log 来看,可以通过执行 jenkins 手动启动,也可以通过 brew services 来启动。

To have launchd start jenkins now and restart at login:
  brew services start jenkins
Or, if you don't want/need a background service you can just run:
  jenkins

还有一种方式,就是自己手动维护:

# 当前用户登录时自动启动 Jenkins
ln -sfv /usr/local/opt/jenkins/homebrew.mxcl.jenkins.plist ~/Library/LaunchAgents

# 手动启动 Jenkins
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist

如果需要外部访问,可以把 plist 文件里的 httpListenAddress 改为广播地址

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>homebrew.mxcl.jenkins</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/libexec/java_home</string>
      <string>-v</string>
      <string>1.8</string>
      <string>--exec</string>
      <string>java</string>
      <string>-Dmail.smtp.starttls.enable=true</string>
      <string>-jar</string>
      <string>/usr/local/opt/jenkins/libexec/jenkins.war</string>
      <string>--httpListenAddress=127.0.0.1</string>
      <string>--httpPort=8080</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

附上一张安装配置 Jenkins 的终端截图

Jenkins 初始化


启动了 Jenkins 后,就可以用浏览器打开 plist 配置的 httpListenAddress:httpPort 来访问 Jenkins 了

默认为 localhost:8080

解锁 Jenkins

打开网页后经过一段初始化后,会出现如下界面,按照提示找到密码输入即可

安装插件

可以自己选择插件进行安装,也可以选择安装推荐的插件。如果不太了解,请选安装推荐的插件。

创建管理员用户

这里按照自己的要求来配置即可

到这里 Jenkins 的初始化就已经完成了

Jenkins 基本配置


安装 Xcode 打包需要的插件

通过点击 Manage Jenkins > Manage Plugins 进入插件管理页面,勾选好插件之后点击直接安装即可

配置 Keychains and Provisioning Profiles Management

  • 上传login.keychain,具体获取方式如下
~ ls ~/Library/Keychains
AE7B340D-384F-56B5-A4A5-91174B979B21
metadata.keychain-db
login.keychain-db
~ cp ~/Library/Keychains/login.keychain-db ~/Desktop/Jenkins/login.keychain
  • 添加 Code Signing Identity,获取方式: 打开钥匙串,找到相应的证书,显示简介,细节里的常用名称即为 Code Signing Identity
  • 添加 Provisioning Profiles Directory Path,mobileprovision 文件一般在 ~/Library/MobileDevice/Provisioning\ Profiles 文件夹内

  • 上传 Provisioning Profiles,这个根据自己需要选择文件上传即可

配置 Jenkins 全局的 PATH 和 Provisioning Profiles Directory Path

Manage Jenkins > Configure System

  • PATH:新增键值对 key 为 PATH,value 可以通过命令获得:
~ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
  • Provisioning Profiles Directory Path:路径和上一步一致

打包任务


新建任务

点击左上角的新建按钮,选择 Freestyle project, 填写任务名称,点击确定。

任务基本配置

在首页点击刚才创建的任务进入任务详情页,再点击配置按钮进入任务配置页

先做好项目的一些基本配置:

忽略旧的构建

一般会选中这个,保持每次构建的独立性

给项目添加一些参数

可以在每次触发任务的时候填入一些参数,比如分支名、版本号等等

自定义工作空间

一般不需要,默认 Jenkins 会在 ~/.jenkins/workspace 下创建一个和任务名相同的文件夹作为工作空间

源码管理

Git、Subversion 等等

构建触发器

一些条件触发的自动构建

构建环境

构建相关的环境配置项

每个项目的配置稍有不同,这里就不细说了,大体参考下图:

任务构建配置

Xcode 任务有两种构建方式可供选择:

  • 使用 Xcode 插件:构建 > 添加构建步骤 > Xcode

这种方式如果 Xcode 有相关的更新变化,就要依赖于 Jenkins Xcode 插件的更新,所以不太推荐这种构建方式,这里就不多做介绍了

  • 自己编写构建脚本:构建 > 添加构建步骤 > Execute shell

脚本的优势在于灵活度高、可定制

比如自动生成 Build Version、pod install、配置一些路径、上传 ipa 到 蒲公英、fir.im、TestFlight 等等

主要通过 xcodebuild 来构建,这里放一个 Demo

打包方式 project 和 workspace 二选一即可

#!/bin/sh

AUTO_BUILD_VERSION=$(date +"%Y%m%d%H%M")
INFO_PLIST_PATH=$WORKSPACE/jenkins/Info.plist
PROJECT_NAME=$WORKSPACE/jenkins.xcodeproj
# WORKSPACE_NAME=$WORKSPACE/jenkins.xcworkspace
TARGET_NAME=jenkins
CONFIGURATION_NAME=Release
ARCHIVE_PATH=$WORKSPACE/$TARGET_NAME.xcarchive
IPA_PATH=$WORKSPACE/IPA
EXPORT_OPTIONS_PATH=$WORKSPACE/ExportOptions.plist

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $AUTO_BUILD_VERSION" "$INFO_PLIST_PATH"
xcodebuild -project ${PROJECT_NAME} -scheme ${TARGET_NAME} -archivePath ${ARCHIVE_PATH} -configuration ${CONFIGURATION_NAME} clean archive
# pod install --project-directory=$WORKSPACE
# xcodebuild -workspace ${WORKSPACE_NAME} -scheme ${TARGET_NAME} -archivePath ${ARCHIVE_PATH} -configuration ${CONFIGURATION_NAME} clean archive
xcodebuild -exportArchive -archivePath ${ARCHIVE_PATH} -exportPath ${IPA_PATH} -exportOptionsPlist ${EXPORT_OPTIONS_PATH} -allowProvisioningUpdates

ExportOptions.plist 的获取方式

其实 Xcode 手动打包导出的文件里就包含了该文件,直接拿过来用就好了

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>compileBitcode</key>
  <false/>
  <key>destination</key>
  <string>export</string>
  <key>method</key>
  <string>enterprise</string>
  <key>provisioningProfiles</key>
  <dict>
    <key>app.bundle.id</key>
    <string>Provisioning Profile Name</string>
  </dict>
  <key>signingCertificate</key>
  <string>iPhone Distribution</string>
  <key>signingStyle</key>
  <string>manual</string>
  <key>stripSwiftSymbols</key>
  <true/>
  <key>teamID</key>
  <string>XXXXXXXXXX</string>
  <key>thinning</key>
  <string>&lt;none&gt;</string>
</dict>
</plist>

还有一种方式就是利用 build + PackageApplication 打包,不过这种方式因为 PackageApplication 被弃用了,所以有坑,除非特殊情况不推荐使用

TARGET_NAME=XXX
WORKSPACE_NAME=XXX.xcworkspace
CONFIGURATION_NAME=Release
CODE_SIGN_IDENTITY="iPhone Distribution: XXX Co., Ltd."
IPA_PATH=$WORKSPACE/IPA
xcodebuild -workspace ${WORKSPACE_NAME}  -scheme ${TARGET_NAME} -sdk iphoneos -configuration ${CONFIGURATION_NAME} PBXBuildsContinueAfterErrors=NO CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}" SYMROOT="${WORKSPACE}" clean build

if [ ! -d $IPA_PATH ]; then
  mkdir -p $IPA_PATH
fi

xcrun -sdk iphoneos PackageApplication -v "$WORKSPACE/Debug-iphoneos/XXX.app" -o "$IPA_PATH/XXX.ipa"
  • 坑在此,如果使用 PackageApplication 的话会提示
xcrun: error: unable to find utility "PackageApplication", not a developer tool or in PATH
  • 原因:在 Xcode 8.3 的版本 PackageApplication 被弃用了
  • 解决办法:将 Xcode 8.2.1 的 PackageApplication 拷贝过来使用
# 我这里准备了一个 Xcode 8.2.1 的 PackageApplication 的备份
# https://gist.github.com/Dwarven/b75f8a8c593d5e1faff51e6d4e179542
# 将此文件保存为 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication
# 并添加执行权限 chmod +x
# 下边是两条命令,可直接使用
sudo curl -fsSL https://gist.github.com/Dwarven/b75f8a8c593d5e1faff51e6d4e179542/raw/8b1d6a2b1159223cd54d1ff276e854e333405b6c/PackageApplication -o /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication
sudo chmod +x /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication

如果需要上传到 TestFlight 的话 可以使用 altool

/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool --validate-app -f /Path/To/Your/IPA/xxx.ipa -t osx|ios|appletvos -u itunesconnectUserName -p itunesconnectPassword --output-format xml
/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool --upload-app -f /Path/To/Your/IPA/xxx.ipa -t osx|ios|appletvos -u itunesconnectUserName -p itunesconnectPassword --output-format xml

Xcode 11 移除了 Application Loader,可直接使用 xcrun altool

xcrun altool --validate-app -f /Path/To/Your/IPA/xxx.ipa -t osx|ios|appletvos -u itunesconnectUserName -p itunesconnectPassword --output-format xml
xcrun altool --upload-app -f /Path/To/Your/IPA/xxx.ipa -t osx|ios|appletvos -u itunesconnectUserName -p itunesconnectPassword --output-format xml

任务构建后操作

在构建完成后按自己需要添加一些处理操作,比如发送通知邮件、做一些Git操作等等

都是一些已经集成好的功能,如不满足需求,Jenkins 拥有强大的插件库,可以通过寻找合适的插件解决,实在不行可以在上一步的最后添加自己编写的 shell 脚本解决

End~!

Comments