前言
在之前的兩篇iOS持續(xù)化集成之Jenkins (一)和iOS持續(xù)化集成之Jenkins (二)中介紹了 Jenkins 環(huán)境搭建以及配合插件實現(xiàn)了自動化打包分發(fā) APP,但是我覺得用起來還是不夠爽,所以就有了這么一篇利用Jenkins+shell+python
實現(xiàn)更加自由的持續(xù)化集成自動化
1、Jenkins 可選參數(shù)配置
這一塊不是重點(diǎn),我這里只是舉個例子,大家根據(jù)自己的需求來定制,比如tage、Debug、Release
等等,就算在不同項目簡單修改 shell 腳本達(dá)到目的也是 ok 的
選項參數(shù),選項框里填寫選項,每個選項換行填寫,作用是在下面構(gòu)建的過程變得比較靈活,例如下圖,特別說明下取值為
${名稱}
,例如${Archive}
2、shell & Python
前期準(zhǔn)備
這里呢,為了便于復(fù)用到其他項目,也為了后面Python 讀取SVN日志,建議大家的做法是在項目的根目錄建立一個目錄用于存放 shell ,Python腳本以及項目需要用到的等等,比如
package目錄.pngad-hoc是用來到處ipa的配置文件,這個文件你可以用xcode手動打包導(dǎo)出一次,在導(dǎo)出的目錄中
ExportOptions.plist
這個配置文件就是我們要的,AutoPackage.sh
打包腳本,SVNLog.py
讀取Jenkins日志用的,代碼在下文重要注意點(diǎn)
xcode里面的證書管理必須為手動管理,然后分別選擇debug,release證書,證書到蘋果開發(fā)者網(wǎng)站生成并安裝到電腦中
2.1 shell
步驟:構(gòu)建->增加構(gòu)建步驟->shell
那么 shell 腳本怎么寫呢,不廢話直接上代碼,在代碼中注釋,比較清晰,我這里用到${Archive}可選參數(shù)取值,大家可以按照自己的需求稍加修改
export LANG="en_US.UTF-8"
####################參數(shù)、環(huán)境變量定義#########################
#工程項目路徑
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"
#發(fā)布文件路徑
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}"
#再創(chuàng)建新的文件目錄
mkdir "$buildPath"
mkdir "$releasePath"
mkdir "$archivePath"
mkdir "$ipaPath"
touch "${logfilePath}"
echo "***********************參數(shù)、環(huán)境變量***********************"
echo "當(dāng)前目錄路徑-------->${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
#導(dǎo)出ipa文件
xcodebuild -exportArchive -archivePath ${archiveFilePath} -exportPath ${ipaPath} -exportOptionsPlist $exportOptionsPlist
echo "***********************結(jié)束build archive app文件***********************"
echo "***********************設(shè)置包名稱信息***********************"
#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})
#取應(yīng)用名稱
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 "*************************結(jié)束上傳到fir**************************"
echo "*************************開始上傳到蒲公英**************************"
curl -F "file=${ipaFilePath}" \
-F "updateDescription=${logfileDir}" \
-F "uKey=蒲公英賬戶中心 userkey" \
-F "_api_key=蒲公英賬戶中心 apikey" \
https://www.pgyer.com/apiv1/app/upload
echo "*************************結(jié)束上傳到蒲公英**************************"
#移除日志文件
rm -rdf "${logfileDir}"
exit
提示,fir 和 蒲公英 需要先要安裝環(huán)境,具體查閱 fir 官網(wǎng)官方文檔,蒲公英官網(wǎng)文檔
python
其實呢,在Jenkins 構(gòu)建后都會在Jenkins所安裝的目錄的jobs生成對應(yīng)項目的編譯相關(guān)文件,其中就包括了 svn 日志
那我們就去讀取這個文件并保存到我們指定的目錄去,然后在上傳到蒲公英等第三方托管平臺,這個在上面 shell 腳本的末端寫了,下面,就直接上 Python 代碼
from xml.dom.minidom import parse
import xml.dom.minidom,sys,os
# 相關(guān)目錄
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節(jié)點(diǎn)值方法
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日志內(nèi)容========="
f = open("./%s"%txtPath,'r')
lines = f.readlines()
for line in lines:
print line
f.close()
except Exception,e:
print "==========xml文件不合法==========%s"%e
寫在最后,如果你不想用這種方式,你也可以使用
第三方工具比如fastlane
,也是擁有比較成熟的打包方案,相關(guān)具體用法這里就不做闡述了,自行百度。