前文
持续构建一直是比较热门的话题,通过持续集成可以自动编译、打包、签名项目,配合单元测试可以实现持续集成+自动化测试。本文在结合CI的基础上,通过fir-cli的发布命令,完成了持续集成+自动化部署。让工程师从重复而又枯燥的手动打包完全解放出来,让工程师能更加专注于代码本身,最大限度的减少误操作风险,降低修复错误代码的成本,大幅提高工作效率。
围绕Travis-CI持续构建的核心文件.travis.yml,根据实操所需,涉及到每个具体的点时再进一步讲解相关配置,避免前期做一些零散、关联性不强的操作产生混乱,基于操作流程再进一步细分到对应的配置点,由上而下,由里到外的了解Travis-CI的运行步骤及可能遇到的问题的解决办法。
Travis-CI
如果你的项目是托管在Github上,那么Travis-CI是做持续集成非常好的选择。在Ruby的世界里,Travis-CI已久负盛名,从2013年4月起,Travis-CI也相继支持iOS、Android、Java平台。
本文主要讲解在iOS线上实际项目中集成Travis-CI,不仅包括项目的编译、打包、签名,还包括将应用部署到fir.im测试平台上。
与Github集成
对于公有Github仓库,注册一个Travis公有账号,对于私有Github仓库,需要注册一个Travis专业版账号。
登录成功后,需要为项目开启Travis支持,导航到属性页面,该页面列出来所有Github项目,如果github账号有加入到其它组织,该组织授权的项目也能看到。注意,如果伺候创建了一个新的仓库,要使用Sync按钮进行同步,Travis只会偶尔更新你的项目列表。
打开上图开关就可以为你的项目添加Travis服务,把按钮置为绿色就行,这样Travis这边的配置就完成了。然后去 Github 关联的 Repo 中,找到 Settings - Webhooks&Services 中添加 Webhook 即可,不需要填信息,直接 test 就能通过,如下图所示:
按照上述步骤,就成功将Travis-CI和Github关联起来了。
配置Travis-CI
Travis-CI通过配置文件.travis.yml来工作,文件存放在项目的根目录下,下面我们开始配置这个文件。注意,文件名前面有个点,在mac系统里会如果没显示隐藏文件在建完该文件后找不到该文件,命令行执行如下命令即可显示所有隐藏文件,将命令中true改为false执行即隐藏。
defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder
简单配置
对于iOS项目,Travis CI支持Objective-C、Swift语言进行部署。Travis 编译器运行在虚拟机环境下。该编译器已经利用 Ruby,Homebrew,CocoaPods,xctool 和一些默认的编译脚本进行过预配置。如本地调试时出现相关command not found,下载对应的环境即可,此处不做介绍。
本文默认所有密码为123456, 防止密码过多导致读者操作出现失误,密码也不通过 travis encrypt"KEY_PASSWORD={password}"--add 命令进行加密,涉及到密码相关的都当做环境变量放在Travis里当前项目的setting -- Environment Variables中,读者后续根据自己实际情况进行调整。
Travis-CI构建流程
Travis手动构建命令流程如下,词如其意,如下所有命令都是在debug调试模式下手动执行调用的,在yml文件里执行的流程命令全部要去除命令前面的“travis_run_”,如before_install、before_script等,命令解释如下:
1.travis_run_before_install:极早的流程步骤,安装一些需要的环境,如fir平台的fir-cli插件等
2.travis_run_before_script:在编译、打包前需要执行的一些操作,如导入相关证书、描述文件等
3.travis_run_script:编译、打包、签名等操作执行的阶段,其中签名是最耗时的步骤
4.travis_run_after_success:将打包好的ipa上传到三方平台等操作,如pgy、fir等
5.travis_run_after_script:清理操作,删除临时keychain、描述文件等
6.travis_run_after_deploy:收尾的一些操作,暂未用到
一个完整的.travis.yml文件如下,图中的命令与上面手动构建命令对应,其中注释了的脚本自动忽略:
scripts目录结构
Travis涉及到的根路径下的文件就两个,一个是yml文件,另一个就是scripts文件夹,scripts也是手动创建的,除yml文件以外的所有Travis相关配置都放在这个文件里面,该文件夹包括两个子文件夹,certs、profile、四个shell脚本(evn.sh脚本没用,忽略)、两个plist文件,script文件夹结构如下图:
配置详解
yml文件里,before_install里执行一些所需要的环境的安装,接下来到before_script,里面执行了一个add_key.sh的脚本,脚本里ENCRYPTION_SECRET、KEY_PASSWORD俩个是环境变量,在Travis里当前项目的setting -- Environment Variables下配置,SCHEME_SANDBOX是当前scheme名称,可以运行xcodebuild -list命令查看当前的scheme、target、configurations,TRAVIS_BRANCH是Travis的常量,用于获取当前所在的git分支。
add_key.sh脚本大致逻辑是,解密cer、p12文件 -- 创建临时keychain -- 解锁临时keychain -- 设置keychain失效时间 -- 将解密后的cer、p12导进keychain -- 遍历profile下所有的文件解密,并重命名为该文件的UUID -- 将解密后重命名为UUID后的名字移动到Travis虚拟机环境的描述文件目录 -- 指定codesigning的临时keychain。
add_key.sh内容如下:
#!/usr/bin/env bash
SHELL_DIR=$(cd"$(dirname "$0")"; pwd)
pushd ${SHELL_DIR}
SCHEME_SANDBOX=Sandbox
KEYCHAIN_PASSWORD=travis
#echo "*** $TRAVIS_BRANCH"
openssl aes-256-cbc -k $ENCRYPTION_SECRET -incerts/${SCHEME_SANDBOX}.cer.enc -d -a -out certs/${SCHEME_SANDBOX}.cer
openssl aes-256-cbc -k $ENCRYPTION_SECRET -incerts/${SCHEME_SANDBOX}.p12.enc -d -a -out certs/${SCHEME_SANDBOX}.p12
security -v create-keychain -p ${KEYCHAIN_PASSWORD} ios-build.keychain
security -v default-keychain -s ios-build.keychain
security -v unlock-keychain -p ${KEYCHAIN_PASSWORD} ios-build.keychain
security -v set-keychain-settings -t864000-lu ~/Library/Keychains/ios-build.keychain
security -v import certs/${SCHEME_SANDBOX}.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security -v import certs/${SCHEME_SANDBOX}.p12 -k ~/Library/Keychains/ios-build.keychain -P"${KEY_PASSWORD}"-T /usr/bin/codesign
security -v set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ${KEYCHAIN_PASSWORD} ios-build.keychain
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
forfileinprofile/*.mobileprovision.enc;do
provision_file=${file/.enc/}
openssl aes-256-cbc -k $ENCRYPTION_SECRET -in$file -d -a -out ${provision_file}
final_file=`grep UUID -A1 -a "$provision_file" | grep -io "[-A-F0-9]\{36\}"`
echo"$file -> $final_file"
mv -f $provision_file ~/Library/MobileDevice/Provisioning\ Profiles/${final_file}.mobileprovision
done
security -v find-identity -p codesigning ~/Library/Keychains/ios-build.keychain
security -v list-keychains
popd
上面脚本涉及到需要的文件有:开发者账号的cer文件,p12文件,开发账号的profile描述文件。
证书和描述文件
如果xcode是自动管理证书,需要读者手动去创建证书和profile描述文件,如果有用到推送,需要再创建推送的证书和描述文件,后续的配置里用到的描述文件只能是手动创建的,用自动创建的会报错。
创建完证书和描述文件后分别安装,双击即可,然后去系统搜索里输入keychain打开钥匙串,找到刚才导入的开发者证书,分别导出cer文件和p12文件,p12文件的密码跟Travis上setting里环境变量里的KEY_PASSWORD的切记保持一致。push证书不用导出,生成push描述文件时继续选择push的证书就行。
接着将cer、p12文件放在scripts目录下的certs自目录下,两个描述文件文件放在script目录下的profile目录下,单击其中一个描述文件,按space空格键预览该描述文件,在预览里的第一组信息里有一个UUID,将该描述文件的名字改为UUID的值,当然此处手动输入容易出错,我们也可以打开一个文件夹,command+shift+G前往文件夹,输入/Users/YOURUSERNAME/Library/MobileDevice/Provisioning Profiles前往,其中YOURUSERNAME为你的用户名,进去后可以暂时移除所有描述文件,再双击手动创建的描述文件,双击后Provisioning Profiles文件夹会出现已经命名成UUID的描述文件,另外一个描述文件亦如此,到此,两个描述文件的UUID名都改好,再将之前暂时移除的描述文件放Provisioning Profiles里。
加密证书与描述文件
命令行到项目根目录下,执行如下命令分别加密上面说的四个文件(其中加密的密码跟setting -- Environment Variables下配置的解密时的密码切记一致,很容易被忽视),如加密cer文件,命令如下(注意:如下是一整条命令,简书会自动换成两行):
openssl aes-256-cbc -k "123456" -in scripts/certs/Debug.cer -out scripts/certs/Debug.cer.enc -a
四个文件分别加密,记得改文件的父级目录与文件名及后缀,加密完后四个未加密的文件删除即可。
至此,add-key脚本相关设置全部完毕。
script阶段
before_script阶段之后到script阶段,执行一个travis.sh的脚本,主要就是打archive包和ipa包,在执行travis脚本时会加上travis_wait的命令,是由于travis运行时日志log不能超过4M,如果超过了会导致travis被迫停止,解决方案有两个,第一个是每次输出五百行日志,第二个方案是让xcodebuild保持静默运行,但是静默运行时如果十分钟没有任何日志输出,travis又会报错停止,所以用第二种方案解决travis日志log的4M限制时加上travis_wait的命令,放心,正常项目的运行时间都会超过十分钟,所以4M限制基本上都需要解决,travis.sh内容如下:
#!/usr/bin/env bash
SHELL_DIR=$(cd"$(dirname "$0")"; pwd)
BUILD_PATH=${SHELL_DIR}/../build/
echo "*** $TRAVIS_BRANCH"
SANDBOX=Sandbox
SCHEME_SANDBOX=HOLLA-dev
SCHEME_RELEASE=HOLLA
SANDBOX_IPA_ARCHIVE_PATH=${BUILD_PATH}/${APPNAME}${SANDBOX}
RELEASE_IPA_ARCHIVE_PATH=${BUILD_PATH}/${APPNAME}${SCHEME_RELEASE}
xcodebuild archive -workspace ${SHELL_DIR}/../${APPNAME}.xcworkspace -scheme ${SCHEME_SANDBOX} -configuration Debug -derivedDataPath ${BUILD_PATH} -archivePath ${SANDBOX_IPA_ARCHIVE_PATH}.xcarchive -quiet
xcodebuild -exportArchive -archivePath ${SANDBOX_IPA_ARCHIVE_PATH}.xcarchive -exportPath ${SANDBOX_IPA_ARCHIVE_PATH} -exportOptionsPlist ${SHELL_DIR}/exportOptions-developer.plist -quiet
xcodebuild archive -workspace ${SHELL_DIR}/../${APPNAME}.xcworkspace -scheme ${SCHEME_RELEASE} -configuration Release -derivedDataPath ${BUILD_PATH} -archivePath ${RELEASE_IPA_ARCHIVE_PATH}.xcarchive -quiet
xcodebuild -exportArchive -archivePath ${RELEASE_IPA_ARCHIVE_PATH}.xcarchive -exportPath ${RELEASE_IPA_ARCHIVE_PATH} -exportOptionsPlist ${SHELL_DIR}/exportOptions-developer.plist -quiet
echo "*** end exportArchive"
控制台log 4M限制解决方案
1.输出五百行日志方案,将上面打包命令放在###################中间即可,此处优点是能看到一些运行时信息,缺点是脚本内容显得有些多,不太简洁,代码如下:
#!/bin/bash
# Abort on Error
## from http://stackoverflow.com/questions/26082444/how-to-work-around-travis-cis-4mb-output-limit
set -e
export PING_SLEEP=30s
export WORKDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export BUILD_OUTPUT=$WORKDIR/build.out
touch $BUILD_OUTPUT
dump_output() {
echo Tailing the last 500 lines of output:
tail -500 $BUILD_OUTPUT
}
error_handler() {
echo ERROR: An error was encountered with the build.
dump_output
kill $PING_LOOP_PID
exit 1
}
# If an error occurs, run our error handler to output a tail of the build
trap 'error_handler' ERR
# Set up a repeating loop to send some output to Travis.
bash -c "while true; do echo \$(date) - building ...; sleep $PING_SLEEP; done" &
PING_LOOP_PID=$!
### 在此处放xcdoebuild打包及签名命令 ###
### 在此处放xcdoebuild打包及签名命令 ###
# The build finished without returning an error so dump a tail of the output
dump_output
# nicely terminate the ping output loop
kill $PING_LOOP_PID
2.运行打包命令时加上-quiet参数即可,此处优点是脚本非常简洁明了,弊端就是运行时完全看不到输出,不能实时查看当前运行信息。
travis.sh详解
由于我实际需求是同时打debug、release包,所以脚本内会执行两边打archive包和打ipa包的命令,此处特别注意坑点,archive的输出目录,必须加上.xcarchive后缀,而ipa的输出目录就随意,加不加.ipa都行,但是如果加了,再后续的上传ipa文件时切忌目录上要加上.ipa。核心命令如下:
xcodebuild archive -workspace ${SHELL_DIR}/../${APPNAME}.xcworkspace -scheme ${SCHEME_SANDBOX} -configuration Debug -derivedDataPath ${BUILD_PATH} -archivePath ${SANDBOX_IPA_ARCHIVE_PATH}.xcarchive -quiet
xcodebuild -exportArchive -archivePath ${SANDBOX_IPA_ARCHIVE_PATH}.xcarchive -exportPath ${SANDBOX_IPA_ARCHIVE_PATH} -exportOptionsPlist ${SHELL_DIR}/exportOptions-developer.plist -quiet
第一句是打archive包命令,第二句是打ipa包命令并签名。
archive打包
项目里用的CocoaPods,所以编译用的-workspace命令,如果读者项目里没用CocoaPods,编译用-project命令,不过都得注意目录,找对xcodeproj或xcworkspace。如上,APPNAME为项目名称,作者定义成了环境变量在yml文件。
理论上xcodebuild可以指定target和scheme,但是通过实践,指定target会报错,通过xcodebuild -list命令可以查看当前的scheme,打archive包时指定的scheme必须是xcodebuild -list里面存在的scheme,-configuration也必须是xcodebuild -list里存在的configurations,-archivePath后面跟的是archive包的存放目录,path中必须包含.xcarchive,如果前面定义的path上没有指定,就一定加上此后缀。
ipa打包并签名
此命令包括打包与签名,-archivePath后的目录与archive时路径一直,-exportPath为ipa存放目录,无特殊后缀要求,-exportOptionsPlist后跟导出包的相关配置。
exportOptions-developer.plist导出ipa参数详解
导出ipa需要的一些配置,主要是签名相关的配置,配置文件如下:
贴不上xml,只能贴图了,文本会贴在回复里,图中teamID需要改成开发证书的teamID,signingCertificate改成开发者证书名字,provisioningProfiles下面配俩profile文件下已经加密好的俩profile文件名和对应的Bundle Identifier,至此,exportOptionsPlist配置完毕,script阶段结束。
after_success阶段
进入after_success阶段,执行sign-and-upload.sh脚本,此脚本目前只有upload操作,sign放在了script阶段,该脚本代码如下:
#!/bin/sh
SHELL_DIR=$(cd"$(dirname "$0")"; pwd)
BUILD_PATH=${SHELL_DIR}/../build/
msg=$(git log-1--pretty=%s)
SANDBOX=Sandbox
SCHEME_RELEASE=HOLLA
SCHEME_SANDBOX=HOLLA-dev
echo "commit msg: ${msg}"
if [ ! -z "$FIR_APP_TOKEN" ]; then
echo""
echo"***************************"
echo"* Uploading to Fir.im *"
echo"***************************"
echo""
fir p ${BUILD_PATH}/${APPNAME}${SANDBOX}/${SCHEME_SANDBOX}.ipa -T $FIR_APP_TOKEN -c"${SCHEME_SANDBOX} - $msg"
fir p ${BUILD_PATH}/${APPNAME}${SCHEME_RELEASE}/${SCHEME_RELEASE}.ipa -T $FIR_APP_TOKEN -c"${SCHEME_RELEASE} - $msg"
fi
如上,先判断FIR_APP_TOKEN是不是空,即在环境变量里有没有设置该变量,如果没有设置,没设置?赶紧的、麻利的去设置撒!设置了下一步就是上传ipa包到fir.im平台,fir后参数p表示ipa的路径,切记与travis.sh里打ipa包的路径一致,文件名就是打包是设置的scheme名称,-T参数后面是fir上的API TOKEN,在fir平台的右上角的个人信息下面,-c是自定义发布时的changelog,项目需求是拿最后一次提交的commit信息当做changelog,所以加上了msg,即git log-1--pretty=%s,至此after_success阶段结束。
after_script阶段
进入after_script阶段,ipa包已经上传到fir平台,此阶段执行remove-key.sh脚本,做一些清理的操作,代码如下:
#!/usr/bin/env bash
SHELL_DIR=$(cd"$(dirname "$0")"; pwd)
pushd ${SHELL_DIR}
SANDBOX=Sandbox
security delete-keychain ios-build.keychain
rm -f ~/Library/MobileDevice/Provisioning\ Profiles/${SANDBOX}.mobileprovision
popd
清除临时keychain及描述文件。此步骤结束,Travis-CI持续构建结束。
关于debug
由于Travis配置相关的步骤及细节点稍多,所以难免出现运行失败的情况,这时debug就该出场了,在项目右侧,Restart build下面:
debug后控制台会和run一样打印部分信息,直到出现ssh xxxxx,如下图所示:
直接复制ssh 这行到命令行回车,即进入debug模式,手动输入👆上面travis_run_开头的命令进行调试,注意每阶段命令的执行的顺序,出问题仔细查看错误信息。
部分错误及解决办法
1.there are no accounts registered with Xcode....
xcode没登录Apple账号,登录即可,不过貌似会经常出现账号被挤掉的情况,只要提示该错,去xcode登录即可。
2.no signing certification ios development found...
出现此错误需确保teamID无误,再此前提下就是导入的证书跟 === BUILD TARGET上面提示的Release不匹配,将archive后面的configuration设置为对应的configuration 选项。
如果如上更改后,还是报no signing certification ios development found...,但是上面的=== BUILD TARGET没有了,在teamID无误的前提下说明就是cer证书导入的有问题,确认本机keychain里的证书,重新导入+加密,操作流程在本文前部。
3.Code signing is required for product type 'App Extension' in SDK 'iOS 11.3'
第二个问题解决了第三个问题没出现过。
出现如上错误首先确认在此之前的log信息里无其他错误,比如在add-key.sh脚本里没有打印如下图的信息,如果有,说明add-key.sh这步就有问题,没找到证书,如果出现1 identites found,说明add-key.sh里无误,再提示archive not found ad path....那就说明是路径错误,仔细检查文件存放位置。
出现此错误是因为控制台如果10min没有日志输出才出现,跟日志的4M限制相关,在travis.sh脚本前叫上travis_wait命令即可,参考文章前面4M限制处理小节。
相关总结
1.只要出现code signing相关的错误信息,基本上就是证书配置问题。
2.前面流程不报错,当前步骤出现file not found错误,基本上就是路径问题。
3.xcodebuild打出来的包release包并不一定是release.ipa,是根据打包时设置的scheme名字命名的,可以debug到输出目录查看。
4.如果开启了代理,log日志有时不会自动刷新,需要手动刷新。
写在最后
Travis-CI持续构建是一个综合性较强的技术实践,需要读者掌握对应平台的相关概念,如开发证书、开发描述文件、推送描述文件、xcode打包流程等,还需掌握shell脚本的基础语法与操作,vim的相关操作。
另,个人免费账号run是有次数限制的,单个项目满100后就不能run了。
网友评论