Travis-CI持续构建生产实践

前文


持续构建一直是比较热门的话题,通过持续集成可以自动编译、打包、签名项目,配合单元测试可以实现持续集成+自动化测试。本文在结合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了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容