前言
在持续集成领域,Jenkins绝对是一个神兵利器,它的插件库非常丰富,我们可以使用它代替执行很多工作中常见的繁琐重复的工作,减少我们在开发之外的不必要的时间花费,从而提高我们的开发效率。在iOS开发中,最常见的就是自动化打包。
我在最初开始接触使用Jenkins的过程中,参考了大量的Jenkins的安装使用教程,发现各有优势,但其中也有很多坑和不合理的地方。通过自己大量的使用、研究和优化,总结了这篇优化版的自动化打包教程。
在安装和使用Jenkins的过程中,很多步骤都会有几种方式实现,本文只详细讲解个人认为比较好的方式。
安装Jenkins
Jenkins的安装分为两类,一种是安装包安装,一种是使用Homebrew安装,推荐使用Homebrew进行安装。
安装前请确保系统已经安装了Homebrew,否则请前往Homebrew官网按照提示自行安装。
打开终端安装Jenkins
brew install jenkins
下面安装完成后日常使用经常会用到的命令
brew services start jenkins // 启动Jenkins
brew services restart jenkins // 重启Jenkins
brew services stop jenkins // 停用Jenkins
上述命令执行后Jenkins都是在后台运行,如果不想使用后台运行Jenkins服务,可以直接在终端输入jenkins
以日志的方式运行。
Jenkins默认会使用8080端口,可以使用以下命令自己更换(不建议)
default write /Library/Preferences/org.jenkins-ci httpPort xxxx // xxxx为更换后的端口号
配置Jenkins
初始化配置
用浏览器打开http://localhost:8080
,首次进入需要输入密码以解锁Jenkins
使用cat
命令按页面上的提示的路径在终端输入以下命令在终端打印密码
cat /Users/用户名/.jenkins/secrets/initialAdminPassword
输入密码登录成功后点击Install suggested plugins按钮安装插件,等待安装完成。
部分插件可能会安装失败,可以选择现点Retry重新安装,也可以选择继续,后面再来安装这些安装失败的插件。
点继续之后会进入到创建用户页面,建议在这里创建用户。如果不创建每次进入都要输入初始密码,比较麻烦。
点击保存会显示以下界面,点击Start using jenkins按钮进入到管理页面。
配置iOS自动化打包
插件安装
自动化打包需要安装以下插件
Keychains and Provisioning Profiles Management
CocoaPods Jenkins Integration
Build Timeout
SSH Agent Plugin
WorkSpace Cleanup Plugin
选择Manage Jenkins->Manage Plugins->已安装
,在右上角过滤里面搜索,查看插件是否已安装,如果没有需要在可选插件里搜索并安装
如果想打包完成后自动发送邮件,需要安装Email Extension Plugin
插件。
如果想将Jenkins的显示语言更改为中文,需要安装Localization: Chinese (Simplified)
插件,重启之后生效。
环境变量配置
iOS自动化打包需要使用到cocoapods
、XCPretty
、xcbuild
等工具,如果想直接在脚本中使用不带路径的相关命令,就需要先添加全局变量。
打开Manage Jenkins->Configure System
页面滚动到全局属性一栏,添加以下几个键值对,前面三个为全局语言编码,最后一个为全局路径,它的值可以通过在终端输入$PATH
获取到
证书和描述文件配置
接下来我们需要上传钥匙串和描述文件,在上传之前需要一些准备工作。
打开电脑的钥匙串访问
应用,进入后鼠标悬停在登录
按钮上方会自动显示登录钥匙串的路径
打开终端,输入以下命令将钥匙串从上图所示的路径中复制出来存放到桌面并将文件后缀里的-db
去掉(Jenkins上传钥匙串时文件必须以.keychain结尾)。这样,钥匙串就准备好了。
cp /Users/用户名/Library/Keychains/login.keychain-db ~/Desktop/login.keychain
描述文件如果有就用现成的,如果没有,就上开发者网站生成和下载一个,具体流程如果不了解可以自行上网搜索,这里就赘述了。(注意:描述文件上传前先将iOS工程的证书管理设置为手动模式,将描述文件导入进入看一下是否能用,避免后面发现不能用来回折腾)
接下来就是正式上传。
打开Manage Jenkins->Keychains and Provisioning Profiles Management
点击选择文件
,选择刚准备好的login.keychain
,然后点Upload
钥匙串上传完成成显示如下
点击下方的Add Code Signing Identity
将项目相关的证书名称添加进去,这个名称点击该证书在钥匙串应用上方就会有显示,复制粘贴即可
添加完成后效果如下
接下来用同样的方法上传证书的描述文件,并在Provisioning Profiles Directory Path
一栏填写描述文件地址/Users/用户名/Library/MobileDevice/Provisioning Profiles
,完成后如下图所示
点击保存,证书和描述文件就完成了。
新建和配置自动化打包任务
在Jenkins首页点击新建Item
为任务起一个名称,选择下面的Freestyle project
,然后点确定
这时候会自动跳转到项目配置界面,在这个界面需要配置三个地方:
- 源码管理
- 构建环境里的
Mobile Provisioning Profiles
- 构建里的运行脚本
首先是源码管理部分,这里有两种方式进行管理,一种是Git,一种是Subversion,这里主要讲解Git方式。
在Repository URL
栏填入项目源码的仓库地址,在下方Credentials
栏的凭据选择默认是空的,选择添加,添加完凭据就可以选择了。指定分支栏填入相应分支,默认为master
分支。
构建环境部分配置比较简单,勾选Mobile Provisioning Profiles
选项并选择之前上传的描述文件即可。
接下来就是最重要的构建脚本了。
在添加脚本以前,需要先把打包需要的ExportOptions.plist
准备好,这个文件需要自己手动打包一次,在导出IPA
文件后在与.ipa
同在的文件夹中可以找到。可以把它放在源码中,也可以放在电脑的某个位置,这个路径在下面的打包脚本中需要用到。(注意:如果后期修改了打包方式、证书或者描述文件,一定要同步修改该plist文件对应的值,否则打包会报错)
准备好.plist
文件之后就可以添加脚本了。
在构建
中的增加构建步骤
里选择Execute shell
添加构建脚本。
在脚本输入框添加以下内容,这个脚本是打包脚本,这个版本已经优化并适配最新版本的XCode 11
,使用时需要将里面的xxx替换为你自己项目的名称或用户名
cd $WORKSPACE
# 计时
SECONDS=0
# 是否编译工作空间 (例:若是用Cocopods管理的.xcworkspace项目,赋值true;用Xcode默认创建的.xcodeproj,赋值false)
is_workspace="true"
# 指定项目的scheme名称
# (注意: 因为shell定义变量时,=号两边不能留空格,若scheme_name与info_plist_name有空格,脚本运行会失败)
scheme_name="xxx"
# 工程中Target对应的配置plist文件名称, Xcode默认的配置文件为Info.plist
info_plist_name="Info"
# 指定要打包编译的方式 : Release,Debug...
build_configuration="Release"
# ===============================自动打包部分(如果Info.plist文件位置有变动需要修改"info_plist_path")============================= #
# 导出ipa所需要的plist文件路径,这个路径为之前存放ExportOptions.plist的路径,如果放在源码工程文件的根目录,直接填文件名即可。
ExportOptionsPlistPath="/Users/xxx/ExportOptions.plist"
# 获取项目名称
project_name=`find . -name *.xcodeproj | awk -F "[/.]" '{print $(NF-1)}'`
# 获取Info.plist路径,拿到版本号, 编译版本号, BundleID
info_plist_path="$project_name/$info_plist_name.plist"
bundle_version=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" $info_plist_path`
bundle_build_version=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" $info_plist_path`
bundle_identifier=`/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" $info_plist_path`
#时间戳
formattedDate=$(date "+%Y-%m-%d#%H:%M:%S")
# 指定输出ipa名称 : scheme_name + bundle_version
ipa_name="$scheme_name$formattedDate"
# 删除旧.xcarchive文件
rm -rf ~/Documents/IPA/$scheme_name-IPA/$scheme_name.xcarchive
# 指定输出ipa路径
export_path=~/Documents/IPA/$scheme_name/$ipa_name
# 指定输出归档文件地址
export_archive_path="$export_path/$ipa_name.xcarchive"
# 指定输出ipa地址
export_ipa_path="$export_path"
# 四种打包方式: AdHoc、AppStore、Enterprise和Development
method="AdHoc"
echo "\033[************************* 开始构建项目 *************************]\033"
# 指定输出文件目录不存在则创建
if [ -d "$export_path" ] ; then
echo $export_path
else
mkdir -pv $export_path
fi
# 判断编译的项目类型是workspace还是project
if $is_workspace ; then
# 安装第三方库
pod install --verbose --no-repo-update
# 编译前清理工程
xcodebuild clean -workspace ${project_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${build_configuration}
xcodebuild archive -workspace ${project_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${build_configuration} \
-archivePath ${export_archive_path} \
| xcpretty
else
# 编译前清理工程
xcodebuild clean -project ${project_name}.xcodeproj \
-scheme ${scheme_name} \
-configuration ${build_configuration}
xcodebuild archive -project ${project_name}.xcodeproj \
-scheme ${scheme_name} \
-configuration ${build_configuration} \
-archivePath ${export_archive_path} \
| xcpretty
fi
# 检查是否构建成功
# xcarchive 实际是一个文件夹不是一个文件所以使用 -d 判断
if [ -d "$export_archive_path" ] ; then
echo "\033[项目构建成功] \033"
else
echo "\033[项目构建失败] \033"
exit 1
fi
echo "\033[************************* 开始导出ipa文件 *************************]\033"
xcodebuild -exportArchive \
-archivePath ${export_archive_path} \
-exportPath ${export_ipa_path} \
-exportOptionsPlist ${ExportOptionsPlistPath} \
-allowProvisioningUpdates \
-allowProvisioningDeviceRegistration \
CODE_SIGN_IDENTITY="iPhone Distribution: xxx (xxx)"
# 修改ipa文件名称
mv $export_ipa_path/$scheme_name.ipa $export_ipa_path/$ipa_name.ipa
# 检查文件是否存在
if [ -f "$export_ipa_path/$ipa_name.ipa" ] ; then
echo "\033[导出 ${ipa_name}.ipa 包成功]\033"
open $export_path
else
echo "\033[导出 ${ipa_name}.ipa 包失败]\033"
exit 1
fi
# 输出打包总用时
echo "\033[打包总用时: ${SECONDS}s]\033"
接下来是上传脚本,将上传脚本添加到上面打包脚本末尾就可以在打包完成后自动上传。
上传到蒲公英的脚本如下,请自行替换蒲公英API Key
和User Key
:
# ============================上传到蒲公英部分(请自行替换蒲公英API Key和User Key)================================ #
echo "\033[************************* 上传到蒲公英 *************************]\033"
# open $export_path
# 蒲公英上的User Key
uKey="xxx"
# 蒲公英上的API Key
apiKey="xxx"
# 要上传的ipa文件路径
IPA_PATH="$export_ipa_path/$ipa_name.ipa"
# 执行上传至蒲公英的命令
echo "[************************* uploading *************************]"
curl -F "file=@${IPA_PATH}" -F "uKey=${uKey}" -F "_api_key=${apiKey}" http://www.pgyer.com/apiv1/app/upload
上传到fir
的脚本如下,请自行替换fir
的API Token
:
# ============================上传到fir.im部分(请自行替换fir的API Token)================================ #
# 执行上传至fir.im的命令
echo "[************************* uploading *************************]"
# fir.im上的API Token
API_TOKEN="xxx"
# 要上传的ipa文件路径
IPA_PATH="$export_ipa_path/$ipa_name.ipa"
#上传到fir
fir publish $IPA_PATH -T $API_TOKEN
点击保存并退出,在项目面板中点击Build Now
就会执行自动打包,点击下面的Build编号,进入构建面板后点击控制台输入
可以看到实时的日志,如果打包失败也可以看到失败原因。打包成功会显示如下: