開(kāi)篇
天生的程序猿都愛(ài)偷懶,總想著機(jī)器能干的事情就不想手動(dòng)。
有一定經(jīng)驗(yàn)的iOS開(kāi)發(fā)從業(yè)人員都知道漫長(zhǎng)的、沒(méi)有技術(shù)含量的、反反復(fù)復(fù)的打包過(guò)程操作是多么無(wú)聊多么無(wú)聊多么無(wú)聊地寂寞啊。
所以有了這么一個(gè)一鍵打包自動(dòng)發(fā)布的想法,經(jīng)過(guò)一番探索與實(shí)踐,總尋得一條成神之路修煉。
Bash與Shell簡(jiǎn)介
Bash 是 Unix 系統(tǒng)和 Linux 系統(tǒng)的一種 Shell(命令行環(huán)境),是目前絕大多數(shù) Linux 發(fā)行版的默認(rèn) Shell。
首先,Shell 是一個(gè)程序,提供一個(gè)與用戶對(duì)話的環(huán)境。這個(gè)環(huán)境只有一個(gè)命令提示符,讓用戶從鍵盤(pán)輸入命令,所以又稱為命令行環(huán)境(command line interface,簡(jiǎn)寫(xiě)為 CLI)。Shell 接收到用戶輸入的命令,將命令送入操作系統(tǒng)執(zhí)行,并將結(jié)果返回給用戶。
其次,Shell 是一個(gè)命令解釋器,解釋用戶輸入的命令。它支持變量、條件判斷、循環(huán)操作等語(yǔ)法,所以用戶可以用 Shell 命令寫(xiě)出各種小程序,又稱為腳本(script)。這些腳本都通過(guò) Shell 的解釋執(zhí)行,而不通過(guò)編譯。
最后,Shell 是一個(gè)工具箱,提供了各種小工具,供用戶方便地使用操作系統(tǒng)的功能。
在iOS開(kāi)發(fā)中,我們通常編寫(xiě)一些自動(dòng)化的腳本文件,來(lái)提高我們的生產(chǎn)效率,其本質(zhì)就是通過(guò)Shell腳本對(duì)一些xcodebuild,xcode-select,xcrun等指令的封裝,還可以通過(guò)Shell進(jìn)行第三方庫(kù)的導(dǎo)入如Pod管理工具,自動(dòng)上傳dSym文件到分析平臺(tái)以及生成模板代碼文件??梢赃@么說(shuō):掌握Shell腳本編程作為iOS工程師封神必備技能。
Bash簡(jiǎn)單學(xué)習(xí)
此腳本涉及的知識(shí)點(diǎn)并不多,簡(jiǎn)單學(xué)習(xí)bash基本命令語(yǔ)法即可掌握此腳本
echo命令
read命令
條件判斷
變量
字符串操作
cd? rm? mv? open? mkdir 等文件夾相關(guān)操作命令
想學(xué)習(xí)更多的命令請(qǐng)前往Bash學(xué)習(xí)資料
準(zhǔn)備工作
必須是MacOS系統(tǒng)【非蘋(píng)果電腦飄過(guò)太平洋再回來(lái)吧】
項(xiàng)目工程在Xcode11以上版本能夠正常編譯無(wú)報(bào)錯(cuò)
開(kāi)發(fā)者相關(guān)證書(shū)已存在電腦keychain中
證書(shū)配置選擇自動(dòng)管理
項(xiàng)目工程配置確保準(zhǔn)確無(wú)誤,手動(dòng)能正常打包,發(fā)布驗(yàn)證正常下載安裝后。此時(shí)說(shuō)明你的項(xiàng)目工程各方面配置OK,可以使用腳本打包了,否則腳本同樣會(huì)顯示報(bào)錯(cuò)信息。
腳本重點(diǎn)知識(shí)
命令說(shuō)明以及坑點(diǎn)說(shuō)明,特別是上傳App Store的時(shí)候,老報(bào)錯(cuò)
xcodebuild、xcrun altool
Xcode打包和上傳App Store用到命令。
xcodebuild命令比較復(fù)雜,安裝Xcode就有,清理clear工程和構(gòu)
建archive文件,exportArchive導(dǎo)出ipa文件都用到此命令。
清理工程命令xcodebuild clean -workspace ${workspace_name}.xcworkspace \? ? ? ? ? ? ? ? -scheme ${scheme_name} \? ? ? ? ? ? ? ? -configuration ${build_configuration}
構(gòu)建工程命令xcodebuild archive -workspace ${workspace_name}.xcworkspace \? ? ? ? ? ? ? ? ? -scheme ${scheme_name} \? ? ? ? ? ? ? ? ? -configuration ${build_configuration} \? ? ? ? ? ? ? ? ? -archivePath ${export_archive_path}
導(dǎo)出IPA命令xcodebuild? -exportArchive \? ? ? ? ? ? -archivePath ${export_archive_path} \? ? ? ? ? ? -exportPath ${export_ipa_path} \? ? ? ? ? ? -exportOptionsPlist ${export_options_plist_path} \? ? ? ? ? ? -allowProvisioningUpdates
Xcode11之后驗(yàn)證應(yīng)用APP和上傳都是使用xcrun altool命令
驗(yàn)證ipaxcrun altool --validate-app -f $export_ipa_path/$ipa_name.ipa -t ios --apiKey xxx --apiIssuer xxx? --verbose
上傳ipa文件? xcrun altool --upload-app -f $export_ipa_path/$ipa_name.ipa -t ios --apiKey xxx --apiIssuer xxx --verbose
坑位地方特別注意
使用xcrun altool命令之前,先做好以下這兩步必要工作,否則老報(bào)錯(cuò)
開(kāi)發(fā)者賬號(hào)【持有人】登錄appstoreconnect管理后臺(tái),申請(qǐng)秘鑰。如圖1所示

圖1開(kāi)發(fā)者秘鑰信息
命令中的xxx 就是指上圖紅色??的兩個(gè)ID
將申請(qǐng)得到的秘鑰下載下來(lái)【不要修改文件名--不要修改文件名--不要修改文件名】,放到/Users/xxx/private_keys文件夾中【xxx是你當(dāng)前電腦用戶;? 如果沒(méi)有private_keys此文件夾,就創(chuàng)建命名private_keys的文件夾?!?。如圖2所示

圖2文件目錄
命令后面的參數(shù)請(qǐng)看詳細(xì)的文檔說(shuō)明
fir-cli? 上傳fir用到
沒(méi)有安裝此命令的請(qǐng)先安裝,安裝方法很簡(jiǎn)單,在終端執(zhí)行命令
sudo gem install fir-cli即可
安裝好之后,發(fā)布IPA文件到fir就兩行命令,是不是很簡(jiǎn)單
fir login -T xxxxx? ? ? # xxxxx 是你 fir.im token? fir publish ffffff? ? ? # ffffffffff 是你上傳的ipa包路徑
蒲公英平臺(tái)上傳
發(fā)布到蒲公英平臺(tái)就簡(jiǎn)單多了,據(jù)官方文檔提供的一行命令即可。 請(qǐng)根據(jù)開(kāi)發(fā)者自己的賬號(hào),將其中的 uKey 和 _api_key 的值替換為相應(yīng)的值。
curl-F"file=@xxxxxxx.ipa"-F"uKey=xxx"-F"_api_key=xxx"https://www.pgyer.com/apiv2/app/upload
完整腳本
附上完整的腳本代碼,請(qǐng)?jiān)敿?xì)看注釋的內(nèi)容
YHAutoPackageScript.sh如下
#!/bin/sh# 使用方法:# step1: 將該腳本放在工程的根目錄下(跟.xcworkspace文件or .xcodeproj文件同目錄)# step2: 根據(jù)情況修改下面的參數(shù)# step3: 打開(kāi)終端,執(zhí)行腳本。(輸入sh,然后將腳本文件拉到終端,會(huì)生成文件路徑,然后enter就可)# =============項(xiàng)目自定義部分(自定義好下列參數(shù)后再執(zhí)行該腳本)=================== ## 是否編譯工作空間 (例:若是用Cocopods管理的.xcworkspace項(xiàng)目,賦值true;用Xcode默認(rèn)創(chuàng)建的.xcodeproj,賦值false)is_workspace="true"# .xcworkspace的名字,如果is_workspace為true,則必須填。否則可不填workspace_name="XXX"? ? #XXX 就是你.xcworkspace工程的名稱# .xcodeproj的名字,如果is_workspace為false,則必須填。否則可不填project_name="XXX"? ? ? ? ? #XXX 就是你.xcodeproj工程的名稱# 指定項(xiàng)目的scheme名稱(也就是工程的target名稱),必填#scheme_name="xxx"? ? ? #xxx 就是你工程里面某個(gè)target的scheme名稱 echo "-------------------------輸入target的scheme名稱-----------------------------"echo "\033[36;1m請(qǐng)輸入您的項(xiàng)目target的scheme名稱(大小寫(xiě)區(qū)分,務(wù)必和工程中的配置一致,按回車(chē)即可) \033[0m"# 讀取用戶輸入并存到變量里read targetNamesleep 0.5scheme_name="$targetName"echo "\033[33;1m您的項(xiàng)目target的scheme名稱為=${scheme_name} "# 指定要打包編譯的方式 : Release,Debug。一般用Release。必填build_configuration="Release"# method,打包的方式。方式分別為 development, ad-hoc, app-store, enterprise 。必填#method="ad-hoc"echo "-------------------------選擇打包方式-----------------------------"echo "\033[36;1m請(qǐng)選擇打包方式(輸入序號(hào),按回車(chē)即可) \033[0m"echo "\033[33;1m1. AdHoc? ? ? \033[0m"echo "\033[33;1m2. AppStore? ? \033[0m"echo "\033[33;1m3. Enterprise? \033[0m"echo "\033[33;1m4. Development \033[0m"# 讀取用戶輸入并存到變量里read parametersleep 0.5packMethod="$parameter"# 判讀用戶是否有輸入if [ -n "$packMethod" ]then? ? if [ "$packMethod" = "1" ] ; then? ? ? ? method="ad-hoc"? ? elif [ "$packMethod" = "2" ] ; then? ? ? ? method="app-store"? ? elif [ "$packMethod" = "3" ] ; then? ? ? ? method="enterprise"? ? elif [ "$packMethod" = "4" ] ; then? ? ? ? method="development"? ? else? ? ? ? echo "輸入的參數(shù)無(wú)效!!!"? ? ? ? exit 1? ? fifiecho "\033[33;1m打包方式method=${method} "#? 下面兩個(gè)參數(shù)只是在手動(dòng)指定Pofile文件的時(shí)候用到,如果使用Xcode自動(dòng)管理Profile,直接留空就好# (跟method對(duì)應(yīng)的)mobileprovision文件名,需要先雙擊安裝.mobileprovision文件.手動(dòng)管理Profile時(shí)必填mobileprovision_name=""# 項(xiàng)目的bundleID,手動(dòng)管理Profile時(shí)必填bundle_identifier=""echo "--------------------腳本配置參數(shù)檢查--------------------"echo "\033[33;1mis_workspace=${is_workspace} "echo "workspace_name=${workspace_name}"echo "project_name=${project_name}"echo "scheme_name=${scheme_name}"echo "build_configuration=${build_configuration}"echo "bundle_identifier=${bundle_identifier}"echo "method=${method}"echo "mobileprovision_name=${mobileprovision_name} \033[0m"echo "-------------------------選擇發(fā)布平臺(tái)-----------------------------"echo "\033[36;1m請(qǐng)選擇發(fā)布平臺(tái)(輸入序號(hào),按回車(chē)即可) \033[0m"echo "\033[33;1m1. AppStore? ? ? \033[0m"echo "\033[33;1m2. 蒲公英pgyer? ? \033[0m"echo "\033[33;1m3. fir平臺(tái)? \033[0m"echo "\033[33;1m4. 不發(fā)布 \033[0m"# 讀取用戶輸入并存到變量里read uploadTypeParametersleep 0.5uploadType="$uploadTypeParameter"# 是否上傳分發(fā)平臺(tái)(fir)is_uploadfir="true"# firTokenfir_token="xxxxxx"? ? # xxxxxx? 就是你fir平臺(tái)上tokenupload_token=$fir_token# 蒲公英上傳# 執(zhí)行 curl -F "file=@xxxxxxx.ipa" -F "uKey=xxx" -F "_api_key=xxx" https://www.pgyer.com/apiv2/app/upload? ? ? 請(qǐng)根據(jù)開(kāi)發(fā)者自己的賬號(hào),將其中的 uKey 和 _api_key 的值替換為相應(yīng)的值。# =======================腳本的一些固定參數(shù)定義(無(wú)特殊情況不用修改)====================== ## 獲取當(dāng)前腳本所在目錄script_dir="$( cd "$( dirname "$0"? )" && pwd? )"# 工程根目錄project_dir=$script_dir# 時(shí)間DATE=`date '+%Y%m%d_%H%M%S'`# 指定輸出導(dǎo)出文件夾路徑export_path="$project_dir/Package/$scheme_name-$DATE"# 指定輸出歸檔文件路徑export_archive_path="$export_path/$scheme_name.xcarchive"# 指定輸出ipa文件夾路徑export_ipa_path="$export_path"# 指定輸出ipa名稱ipa_name="${scheme_name}_${DATE}"# 指定導(dǎo)出ipa包需要用到的plist配置文件的路徑export_options_plist_path="$project_dir/ExportOptions.plist"echo "--------------------腳本固定參數(shù)檢查--------------------"echo "\033[33;1mproject_dir=${project_dir}"echo "DATE=${DATE}"echo "export_path=${export_path}"echo "export_archive_path=${export_archive_path}"echo "export_ipa_path=${export_ipa_path}"echo "export_options_plist_path=${export_options_plist_path}"echo "ipa_name=${ipa_name} \033[0m"# =======================自動(dòng)打包部分(無(wú)特殊情況不用修改)====================== #echo "------------------------------------------------------"echo "\033[32m開(kāi)始構(gòu)建項(xiàng)目? \033[0m"# 進(jìn)入項(xiàng)目工程目錄cd ${project_dir}# 指定輸出文件目錄不存在則創(chuàng)建if [ -d "$export_path" ] ; then? ? echo $export_pathelse? ? mkdir -pv $export_pathfi# 判斷編譯的項(xiàng)目類型是workspace還是projectif $is_workspace ; then# 編譯前清理工程xcodebuild clean -workspace ${workspace_name}.xcworkspace \? ? ? ? ? ? ? ? -scheme ${scheme_name} \? ? ? ? ? ? ? ? -configuration ${build_configuration}xcodebuild archive -workspace ${workspace_name}.xcworkspace \? ? ? ? ? ? ? ? ? -scheme ${scheme_name} \? ? ? ? ? ? ? ? ? -configuration ${build_configuration} \? ? ? ? ? ? ? ? ? -archivePath ${export_archive_path}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}fi#? 檢查是否構(gòu)建成功#? xcarchive 實(shí)際是一個(gè)文件夾不是一個(gè)文件所以使用 -d 判斷if [ -d "$export_archive_path" ] ; then? ? echo "\033[32;1m項(xiàng)目構(gòu)建成功 ?? ?? ??? \033[0m"else? ? echo "\033[31;1m項(xiàng)目構(gòu)建失敗 ?? ?? ??? \033[0m"? ? exit 1fiecho "------------------------------------------------------"echo "\033[32m開(kāi)始導(dǎo)出ipa文件 \033[0m"# 先刪除export_options_plist文件if [ -f "$export_options_plist_path" ] ; then? ? #echo "${export_options_plist_path}文件存在,進(jìn)行刪除"? ? rm -f $export_options_plist_pathfi# 根據(jù)參數(shù)生成export_options_plist文件/usr/libexec/PlistBuddy -c? "Add :method String ${method}"? $export_options_plist_path#/usr/libexec/PlistBuddy -c? "Add :provisioningProfiles:"? $export_options_plist_path#/usr/libexec/PlistBuddy -c? "Add :provisioningProfiles:${bundle_identifier} String ${mobileprovision_name}"? $export_options_plist_path/usr/libexec/PlistBuddy -c? "Add :signingStyle String automatic"? $export_options_plist_path/usr/libexec/PlistBuddy -c? "Add :destination String export"? $export_options_plist_path/usr/libexec/PlistBuddy -c? "Add :compileBitcode bool false"? $export_options_plist_path? ? #如果您的工程是開(kāi)啟Bitcode的話,請(qǐng)把false改為trueecho "\033[31;1mexport_options_plist文件: ${export_options_plist_path}? ? ? \033[0m"xcodebuild? -exportArchive \? ? ? ? ? ? -archivePath ${export_archive_path} \? ? ? ? ? ? -exportPath ${export_ipa_path} \? ? ? ? ? ? -exportOptionsPlist ${export_options_plist_path} \? ? ? ? ? ? -allowProvisioningUpdates# 檢查ipa文件是否存在if [ -f "$export_ipa_path/$scheme_name.ipa" ] ; then? ? echo "\033[32;1mexportArchive ipa包成功,準(zhǔn)備進(jìn)行重命名\033[0m"else? ? echo "\033[31;1mexportArchive ipa包失敗 ?? ?? ??? ? \033[0m"? ? exit 1fi# 修改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[32;1m導(dǎo)出 ${ipa_name}.ipa 包成功 ??? ??? ??? \033[0m"? ? open $export_pathelse? ? echo "\033[31;1m導(dǎo)出 ${ipa_name}.ipa 包失敗 ?? ?? ??? ? \033[0m"? ? exit 1fi# 刪除export_options_plist文件(中間文件)if [ -f "$export_options_plist_path" ] ; then? ? #echo "${export_options_plist_path}文件存在,準(zhǔn)備刪除"? ? rm -f $export_options_plist_pathfi# 輸出打包總用時(shí)echo "\033[36;1m使用AutoPackageScript打包總用時(shí): ${SECONDS}s \033[0m"if [ "$uploadType" = "1" ] ; then? echo "App Store發(fā)布!!!"? xcrun altool --validate-app -f $export_ipa_path/$ipa_name.ipa -t ios --apiKey xxx --apiIssuer xxx? --verbose? ? ? ? xcrun altool --upload-app -f $export_ipa_path/$ipa_name.ipa -t ios --apiKey xxx --apiIssuer xxx --verbose? # xxx 就是你App Store管理后臺(tái)的秘鑰和apiKey信息elif [ "$uploadType" = "2" ] ; then? ? curl -F "file=@$export_ipa_path/$ipa_name.ipa" -F "uKey=xxx" -F "_api_key=xxx" https://www.pgyer.com/apiv2/app/uploadelif [ "$uploadType" = "3" ] ; then? fir login -T $upload_token? ? ? # fir.im token? fir publish $export_ipa_path/$ipa_name.ipaelse? ? echo "不發(fā)布!!!"? ? exit 0fiexit 0
使用方法
把腳本代碼復(fù)制保存為XXXX.sh文件,并修改里面的XXX對(duì)應(yīng)你項(xiàng)目工程或者是換成你自己的信息
把XXXX.sh文件放在和你項(xiàng)目xxx.xcodeproj文件同一個(gè)文件夾下面
打開(kāi)終端進(jìn)入你當(dāng)前工程xxx.xcodeproj所在文件夾
執(zhí)行命令sh ./XXXX.sh
根據(jù)提示輸入target的scheme名稱、打包方式、發(fā)布平臺(tái)
作者:Lewis海
鏈接:http://m.itdecent.cn/p/4436fc383c0b
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。