前言 在之前的两篇 iOS持续化集成之Jenkins (一) 和 iOS持续化集成之Jenkins (二) 中介绍了 Jenkins 环境搭建以及配合插件实现了自动化打包分发 APP,但是我觉得用起来还是不够爽,所以就有了这么一篇利用 Jenkins+shell+python
实现更加自由的持续化集成自动化
这一块不是重点,我这里只是举个例子,大家根据自己的需求来定制,比如 tage、Debug、Release
等等,就算在不同项目简单修改 shell 脚本达到目的也是 ok 的 选项参数,选项框里填写选项,每个选项换行填写,作用是在下面构建的过程变得比较灵活,例如下图,特别说明下取值为 ${名称}
,例如 ${Archive}
#前期准备 这里呢,为了便于复用到其他项目,也为了后面Python 读取SVN日志,建议大家的做法是在项目的根目录建立一个目录用于存放 shell ,Python脚本以及项目需要用到的等等,比如
ad-hoc是用来到处ipa的配置文件,这个文件你可以用xcode手动打包导出一次,在导出的目录中 ExportOptions.plist
这个配置文件就是我们要的, AutoPackage.sh
打包脚本, SVNLog.py
读取Jenkins日志用的,代码在下文
xcode里面的证书管理必须为手动管理,然后分别选择debug,release证书,证书到苹果开发者网站生成并安装到电脑中
步骤:构建->增加构建步骤->shell
那么 shell 脚本怎么写呢,不废话直接上代码,在代码中注释,比较清晰,我这里用到${Archive}可选参数取值,大家可以按照自己的需求稍加修改
export LANG="en_US.UTF-8" ####################参数、环境变量定义######################### #工程项目路径 projectPath="$(pwd)" #工程项目名称 projectName="xxxx" #工程项目打包模式 buildConfiguration="Release" #IPA配置文件 exportOptionsPlist="${projectPath}/Package/${Archive}.plist" #证书 ADHOCCODE_SIGN_IDENTITY="iPhone Distribution: xxxx" DEVELOPMENT_TEAM="跟在iPhone Distribution:xxxx后面括号里面的值" #描述文件 Main_Provisioning_Profile="xxxx-xxxx-xxxx-xxxx-xxxxx" Extension_Provisioning_Profile="xxxx-xxxx-xxxx-xxxx-xxxxx" #build文件路径 buildPath="${projectPath}/build" #发布文件路径 releasePath="${projectPath}/build/Release-iphoneos" #archive保存路径 archivePath="${projectPath}/archive" archiveName="${projectName}.xcarchive" archiveFilePath="${archivePath}/${archiveName}" #ipa保存路径 ipaPath="${projectPath}/ipa" #log日志路径 logfilePath="${projectPath}/ChangeLog" #先删除存在的文件目录 rm -rdf "$buildPath" rm -rdf "$archivePath" rm -rdf "$ipaPath" rm -rdf "${logfilePath}" #再创建新的文件目录 mkdir "$buildPath" mkdir "$releasePath" mkdir "$archivePath" mkdir "$ipaPath" touch "${logfilePath}" echo "***********************参数、环境变量***********************" echo "当前目录路径-------->${projectPath}" echo '打包模式:'$buildConfiguration echo '工程目录:'$projectPath echo '工程名称:'$projectName echo '安装包路径 '$archiveFilePath echo '/n' echo "***********************开始build archive app文件***********************" #打包的命令 xcodebuild -workspace "${projectPath}/${projectName}.xcworkspace" -scheme "$projectName" -configuration ${buildConfiguration} -archivePath "${archiveFilePath}" CONFIGURATION_BUILD_DIR="${releasePath}" DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" CODE_SIGN_IDENTITY="${ADHOCCODE_SIGN_IDENTITY}" APP_PROFILE="${Main_Provisioning_Profile}" EXTENSION_PROFILE="${Extension_Provisioning_Profile}" clean archive EXCODE=$? if [ "$EXCODE" == "0" ]; then echo "O.K" else echo "***********************编译失败********************************" exit 1 fi #导出ipa文件 xcodebuild -exportArchive -archivePath ${archiveFilePath} -exportPath ${ipaPath} -exportOptionsPlist $exportOptionsPlist echo "***********************结束build archive app文件***********************" echo "***********************设置包名称信息***********************" #app文件存放位置和命名 appPath="${archiveFilePath}/Products/Applications" appFile="${appPath}/${projectName}.app" #app文件中Info.plist文件路径 appInfoPlistPath=$appFile/Info.plist #取版本号 version=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" ${appInfoPlistPath}) #取Build号 buildNo=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" ${appInfoPlistPath}) #取bundle id bundleId=$(/usr/libexec/PlistBuddy -c "print CFBundleIdentifier" ${appInfoPlistPath}) #取应用名称 appName=$(/usr/libexec/PlistBuddy -c "print CFBundleDisplayName" ${appInfoPlistPath}) #包编译类型(ad-hoc,enterprise...) buildMethod=$(/usr/libexec/PlistBuddy -c "print method" ${exportOptionsPlist}) #打包的时间 date=$(date +%Y%m%d%H%M) #判断放ipa包的目录是否存在 destinationPath="{这里填上你最后想保存的路径目录}/${buildMethod}/${projectName}/${version}" if [ ! -d "$destinationPath" ]; then mkdir -p "$destinationPath" fi ipaFile="${projectName}_${buildMethod}_${version}(${date}).ipa" dSYMFile="${projectName}_${buildMethod}_${version}(${date}).app.dSYM" ipaFilePath="${destinationPath}/${ipaFile}" dSYMFilePath="${destinationPath}/${dSYMFile}" #将ipa跟dSYM移动到指定目录下 mv -f "${releasePath}/${projectName}.ipa" $ipaFilePath mv -f "${releasePath}/${projectName}.app.dSYM" $dSYMFilePath echo "** 安装包最终存放路径--->${ipaFilePath} **" echo "*************************开始上传到fir**************************" fir login "fir 登录的 token" fir me if [ ! -f "$logfileDir" ]; then fir publish ${ipaFilePath} -c "无更新记录" else fir publish ${ipaFilePath} -c ${logfileDir} fi echo "*************************结束上传到fir**************************" echo "*************************开始上传到蒲公英**************************" curl -F "file=${ipaFilePath}" / -F "updateDescription=${logfileDir}" / -F "uKey=蒲公英账户中心 userkey" / -F "_api_key=蒲公英账户中心 apikey" / https://www.pgyer.com/apiv1/app/upload echo "*************************结束上传到蒲公英**************************" #移除日志文件 rm -rdf "${logfileDir}" exit 复制代码
提示,fir 和 蒲公英 需要先要安装环境,具体查阅 fir 官网官方文档 , 蒲公英官网文档
其实呢,在Jenkins 构建后都会在Jenkins所安装的目录的jobs生成对应项目的编译相关文件,其中就包括了 svn 日志
那我们就去读取这个文件并保存到我们指定的目录去,然后在上传到蒲公英等第三方托管平台,这个在上面 shell 脚本的末端写了,下面,就直接上 Python 代码
from xml.dom.minidom import parse import xml.dom.minidom,sys,os # 相关目录 numbulindline = open('/Users/Shared/Jenkins/jobs/项目名称/nextBuildNumber','r').readline() needNumbulindline = int(numbulindline)-1 xmlPath = '/Users/Shared/Jenkins/jobs/项目名称)/builds/%d/changelog.xml'%needNumbulindline #保存的文件名称 txtPath = 'ChangeLog' #文件写入编码 reload(sys) sys.setdefaultencoding('utf8') #写入log到txt def text_write(text): #保存的路径 logPath = "./%s"%txtPath file = open(logPath,'a') file.write(text) file.close() #获取xml节点值方法 def get_xmlnode(node, name): return node.getElementsByTagName(name) if node else [] try: DOMTree = xml.dom.minidom.parse("%s"%xmlPath) collection = DOMTree.documentElement logentry = collection.getElementsByTagName("logentry") text_write("=============================/n") for index in range(len(logentry)): print "==========log日志写入中=========" logentrysub = logentry[index] author = get_xmlnode(logentrysub,'author')[0].firstChild.nodeValue date = get_xmlnode(logentrysub,'date')[0].firstChild.nodeValue [0:10] msgdom = get_xmlnode(logentrysub,'msg')[0].firstChild if msgdom != None: msg = msgdom.data else: msg = "空" text_write(author+" "+date+"/n"+msg+"/n/n") text_write("=============================") print "==========log日志写入完成=========" print "==========log日志内容=========" f = open("./%s"%txtPath,'r') lines = f.readlines() for line in lines: print line f.close() except Exception,e: print "==========xml文件不合法==========%s"%e 复制代码
写在最后,如果你不想用这种方式,你也可以使用 第三方工具比如 fastlane
,也是拥有比较成熟的打包方案,相关具体用法这里就不做阐述了,自行百度。