Apple提供的常規(guī)打包方式主要是由Xcode支持的逐抑,下面展開來聊聊
Xcode打包
Xcode的打包主要分為兩步:
- Archive:對(duì)target進(jìn)行編譯褐墅、歸檔绢涡,生成.xcarchive 文件
- Export:對(duì)生成的.xcarchive 文件進(jìn)行進(jìn)一步的處理抡蛙,生成不同渠道的ipa包卿拴,進(jìn)行分發(fā)
Archive編譯歸檔
Archive主要是對(duì)target進(jìn)行編譯蹈矮、歸檔揪漩,生成.xcarchive 文件“D欤可在Xcode -> Window -> Organizer -> Archives中查看
這里所說的歸檔赵颅,主要就對(duì)項(xiàng)目源碼進(jìn)行編譯后虽另,再將編譯生成的各種文件暂刘、資源、記錄統(tǒng)一封裝到一個(gè)文件中捂刺,便于管理和回溯谣拣。
隨機(jī)選擇一個(gè).xcarchive文件,點(diǎn)擊show in Finder族展,可以看到一個(gè).xcarchive 后綴的文件
這個(gè).xcarchive文件包含了應(yīng)用森缠、符號(hào)表信息以及其他的資源,可以右鍵 -> 顯示包內(nèi)容進(jìn)行查看仪缸,主要包含以下文件:
BCSymbolMaps:Xcode 對(duì) BitCode 符號(hào)表進(jìn)行混淆(Symbol Hiding)后生成的對(duì)照表贵涵,和 dSYM 文件會(huì)一一對(duì)應(yīng)
dSYMs:存儲(chǔ)此次編譯的符號(hào)表(debug symbols),用來符號(hào)化解析崩潰堆棧
info.plist: 項(xiàng)目target的配置文件
Products:存儲(chǔ)此次編譯生成的的 App 包(.app)恰画。雖然這個(gè)文件包含了App 運(yùn)行需要的可執(zhí)行文件以及其它資源宾茂,但是和最終用戶下載的版本會(huì)有所不同。后續(xù)的 export 操作會(huì)對(duì)其進(jìn)行進(jìn)一步處理拴还。
SCMBlueprint:如果 Xcode 打開了版本管理(Preferences -> Source Control -> Enable Source Control)跨晴,SCMBlueprint 文件夾會(huì)存儲(chǔ)此次編譯的版本控制信息,包括使用的 git 版本片林、倉(cāng)庫(kù)端盆、分支等怀骤。如果需要回溯此次編譯的源碼版本,可以在這個(gè)文件中找到對(duì)應(yīng)的信息焕妙。
SwiftSupport:在 Target 的 Build Settings 中打開了ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES蒋伦,此次編譯使用的 Swift 版本對(duì)應(yīng)的標(biāo)準(zhǔn)庫(kù)文件(.dylib)會(huì)被放到這個(gè)文件夾中。發(fā)布App時(shí)焚鹊,也會(huì)被復(fù)制到ipa bundle中凉敲。在iOS 12.2及以上,swift的ABI趨于穩(wěn)定寺旺,已經(jīng)不用再自帶鏈接庫(kù)了爷抓,因此ipa包節(jié)省了一定的體積
Export打包分發(fā)
Export主要是的對(duì)生成的.xcarchive 文件進(jìn)行進(jìn)一步的處理,生成不同渠道的ipa包阻塑,進(jìn)行分發(fā)蓝撇,Organize -> Archive文件 -> Distribute App
這里對(duì)應(yīng)4種分發(fā)渠道:AppStore、Ad Hoc陈莽、Enterprise和Development渤昌,然后一步步往下操作即可。
以上4中渠道對(duì)應(yīng)的打包method分別是app-store走搁、ad-hoc独柑、enterprise、development私植,可以在導(dǎo)出的文件夾中ExportOptions.plist文件看到對(duì)應(yīng)參數(shù)忌栅,如下所示
最終Export導(dǎo)出的文件夾中主要包含以下4種文件
DistributionSummary.plist:包含ipa所支持的架構(gòu)、bitcode曲稼、證書索绪、embeddedBinaries(非系統(tǒng)的動(dòng)態(tài)庫(kù)相關(guān))、entitlements(apns環(huán)境贫悄、application-identifier等)瑞驱、profile等相關(guān)信息
ExportOptions.plist:ipa包導(dǎo)出的配置文件,主要包含包導(dǎo)出方式窄坦、簽名方式唤反、App Developer team的id等
MIStore.ipa:應(yīng)用的ipa包,包含了App所需的簽名鸭津、二進(jìn)制包彤侍、資源等
Packaging.log:打包相關(guān)的日志
如果想要查看ipa中的內(nèi)容,可以將.ipa改成.zip曙博,然后解壓拥刻,也可使用命令解壓
zip -0 -y -r myAppName.ipa Payload/,其中是.app的文件父泳,查看其包內(nèi)容般哼,主要包含以下幾部分
- MachO可執(zhí)行文件:具體的介紹可查看iOS逆向 12:Mach-O文件(上)和iOS逆向 12:Mach-O文件(下)
- 簽名文件:App 的簽名信息會(huì)被放到 _CodeSignature 文件夾中吴汪。
- info.plist:存儲(chǔ) App 主要信息的 plist 文件也會(huì)被一并打包到 ipa 中。
- entitlements:App包含的相關(guān)權(quán)限蒸眠,在這個(gè)文件中通過 xml 的格式將這些授權(quán)記錄下來漾橙,例如Push notifications、App Group等
- App Plugins:如果App實(shí)現(xiàn)了 App Extension擴(kuò)展楞卡,擴(kuò)展的包會(huì)以 .appex 的后綴存儲(chǔ)在 PlugIns 文件夾中霜运,隨著App的安裝一起安裝到用戶手機(jī)上
- 鏈接庫(kù):App 運(yùn)行所需要的各種鏈接庫(kù)會(huì)被放入 Frameworks 文件夾。
-
資源文件:App 運(yùn)行需要的各種資源文件也是 ipa 體積的大頭蒋腮。常見的有
- 各種多媒體資源:圖片淘捡、音視頻
- xib 文件:.nib.storyboardc
- 各種打包的資源 .bundle
- 其它類型的資源:字體、數(shù)據(jù)庫(kù)池摧、證書等等
除了Xcode自帶的工具application loader上傳ipa焦除,還可以通過Transporter上傳。Transporter可以以簡(jiǎn)單輕松的方式將內(nèi)容交付到Apple作彤,提供如下功能:
- 只需要將ipa包拖放到Transporter中即可開始使用
- 同時(shí)驗(yàn)證和上傳多個(gè)文件以快速交付
- 查看交付進(jìn)度(警告膘魄、錯(cuò)誤和交付日志),以便快速修復(fù)交付問題
- 查看歷史交付記錄(包含日期、時(shí)間)
所以,綜上所述咬扇,通過Xcode提供的打包方式比較繁瑣,需要人為操作許多步驟鬼佣,那么我們是否可以通過其他的方式進(jìn)行簡(jiǎn)化呢?答案是當(dāng)然可以呀。
簡(jiǎn)化打包操作的方式的主要有兩種
- Shell打包腳本
- Jenkins部署自動(dòng)打包
這里我們主要介紹Shell打包腳本的方式,主要分為以下幾步
- 配置打包相關(guān)的名稱:.xcworkspace的名字逻杖、scheme名稱、編譯方式思瘟、打包方式、profile文件闻伶、bundleID等
- 配置打包的相關(guān)路徑:導(dǎo)出路徑滨攻、archive文件路徑、ipa文件路徑蓝翰、ExportOptions.plist文件路徑
- 清理工程:通過xcodebuild clean清理
- 編譯:通過xcodebuild archive編譯光绕、歸檔工程
- 導(dǎo)出ipa:通過xcodebuild命令將.xcarchive導(dǎo)出為ipa文件
- 校驗(yàn)并上傳AppStore:如果是AppStore的包,通過xcrun altool校驗(yàn)并上傳AppStore
使用方式:進(jìn)入podfile所在目錄畜份,在終端執(zhí)行.sh文件诞帐,例如:sh xxxx.sh
具體的shell腳本如下(注:使用時(shí),需要自行填充其中的xxxxx所在的腳本代碼)
#!/bin/bash
# 前提:enterprise打包時(shí)爆雹,需要切換到 企業(yè)賬戶 及 BundleID
# 使用方法:
# step1: 將該腳本放在工程的根目錄下(跟.xcworkspace文件or .xcodeproj文件同目錄)
# step2: 根據(jù)情況修改下面的參數(shù)
# step3: 打開終端停蕉,執(zhí)行腳本愕鼓。(輸入sh,然后將腳本文件拉到終端慧起,會(huì)生成文件路徑菇晃,然后enter就可)
echo "-----------開始執(zhí)行腳本-----------"
# =============項(xiàng)目自定義部分(自定義好下列參數(shù)后再執(zhí)行該腳本)=================== #
echo "請(qǐng)選擇打包方式 ? [1:enterprise_debug 2:enterprise_release 3:ad_hoc 4:app_store]"
read number
while([[ $number != 1 ]] && [[ $number != 2 ]] && [[ $number != 3 ]] && [[ $number != 4 ]])
do
echo "Error! Should enter 1 or 2 or 3 or 4"
echo "請(qǐng)選擇打包方式 ? [ 1:enterprise_debug 2:enterprise_release 3:ad_hoc 4:app_store]"
read number
done
#-----------腳本配置信息-----------
# .xcworkspace的名字,必填
workspace_name="xxxxx"
# 指定項(xiàng)目的scheme名稱(也就是工程的target名稱)蚓挤,必填
scheme_name="xxxxx"
# 指定要打包編譯的方式 : Release,Debug磺送。一般用Release。必填
build_configuration="Release"
# method灿意,打包的方式估灿。方式分別為 development, ad-hoc 。必填
method="enterprise"
# 下面兩個(gè)參數(shù)只是在手動(dòng)指定Pofile文件的時(shí)候用到缤剧,如果使用Xcode自動(dòng)管理Profile,直接留空就好
# (跟method對(duì)應(yīng)的)mobileprovision文件名甲捏,需要先雙擊安裝.mobileprovision文件.手動(dòng)管理Profile時(shí)必填
# mobileprovision_name="9d8c7290-4345-4ebf-82d4-a74cab2ea40b"
mobileprovision_name=""
# 項(xiàng)目的bundleID,手動(dòng)管理Profile時(shí)必填
bundle_identifier=""
# if [[ $number == 1 ]]; then
# bundle_identifier="com.mi.global.sho"
# else
# bundle_identifier="com.mi.global.shop"
# fi
# 每次編譯后是否Build自動(dòng)加1,
# 可以修改該常量的值,以決定編譯后還是打包后Build自動(dòng)加1
# # 0: 每次打包后Build自動(dòng)加1
# # 1: 每次編譯后Build自動(dòng)加1
DEBUG_ENVIRONMENT_SYMBOL=0
# 根據(jù)選項(xiàng)配置不同的包
if [ $number == 1 ];then
build_configuration="Debug"
method="enterprise"
DEBUG_ENVIRONMENT_SYMBOL=1
elif [[ $number == 2 ]]; then
build_configuration="Release"
method="enterprise"
DEBUG_ENVIRONMENT_SYMBOL=1
elif [[ $number == 3 ]]; then
build_configuration="Release"
method="ad-hoc"
DEBUG_ENVIRONMENT_SYMBOL=1
else
build_configuration="Release"
method="app-store"
DEBUG_ENVIRONMENT_SYMBOL=1
fi
echo "--------------------腳本配置參數(shù)檢查--------------------"
echo "\033[33;1mworkspace_name = ${workspace_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"
# =======================腳本的一些固定參數(shù)定義(無特殊情況不用修改)====================== #
# 獲取當(dāng)前腳本所在目錄
script_dir="$( cd "$( dirname "$0" )" && pwd )"
# 工程根目錄
project_dir=$script_dir
# 指定輸出導(dǎo)出文件夾路徑
export_path="$project_dir/Build"
# 指定輸出歸檔文件路徑
export_archive_path="$export_path/$scheme_name.xcarchive"
# 指定輸出ipa文件夾路徑
export_ipa_path="$export_path/"
# 指定導(dǎo)出ipa包需要用到的plist配置文件的路徑
export_options_plist_path="$project_dir/ExportOptions.plist"
echo "--------------------腳本固定參數(shù)檢查--------------------"
echo "\033[33;1mproject_dir = ${project_dir}"
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}\033[0m"
# =======================自動(dòng)打包部分(無特殊情況不用修改)====================== #
echo "------------------------------------------------------"
echo "\033[32m開始構(gòu)建項(xiàng)目 \033[0m"
# 進(jìn)入項(xiàng)目工程目錄
cd ${project_dir}
# 指定輸出文件目錄不存在則創(chuàng)建
if [ -d "$export_path" ];
then rm -rf "$export_path"
fi
/usr/bin/xcrun xcodebuild -UseNewBuildSystem=YES -xcconfig InnerXcconfig/innerInner/tt.xcconfig
# 編譯前清理工程
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}
# 檢查是否構(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 1
fi
echo "------------------------------------------------------"
echo "\033[32m開始導(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_path
fi
# 根據(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 :compileBitcode bool NO" $export_options_plist_path
xcodebuild -exportArchive \
-archivePath ${export_archive_path} \
-exportPath ${export_ipa_path} \
-exportOptionsPlist ${export_options_plist_path} \
-allowProvisioningUpdates
# 檢查文件是否存在
if [ -f "$export_ipa_path/$scheme_name.ipa" ] ; then
echo "\033[32;1m導(dǎo)出 ${scheme_name}.ipa 包成功 ?? ?? ?? \033[0m"
open $export_path
else
echo "\033[31;1m導(dǎo)出 ${scheme_name}.ipa 包失敗 ?? ?? ?? \033[0m"
exit 1
fi
# 刪除export_options_plist文件(中間文件)
if [ -f "$export_options_plist_path" ] ; then
#echo "${export_options_plist_path}文件存在司顿,準(zhǔn)備刪除"
rm -f $export_options_plist_path
fi
# 輸出打包總用時(shí)
echo "\033[36;1m使用AutoPackageScript打包總用時(shí): ${SECONDS}s \033[0m"
echo "------------------------------------------------------"
# AppStore上傳到xxx
if [ $number == 4 ];then
# 將包上傳AppStore
ipa_path="$export_ipa_path/$scheme_name.ipa"
# 上傳AppStore的密鑰ID、Issuer ID
api_key="xxxxx"
issuer_id="xxxxx"
echo "--------------------AppStore上傳固定參數(shù)檢查--------------------"
echo "ipa_path = ${ipa_path}"
echo "api_key = ${api_key}"
echo "issuer_id = ${issuer_id}"
# 校驗(yàn) + 上傳 方式1
# # 校驗(yàn)指令
# cnt0=`xcrun altool --validate-app -f ${ipa_path} -t ios --apiKey ${api_key} --apiIssuer ${issuer_id} --verbose`
# echo $cnt0
# cnt=`echo $cnt0 | grep “No errors validating archive” | wc -l`
# if [ $cnt = 1 ] ; then
# echo "\033[32;1m校驗(yàn)IPA成功?? ?? ?? \033[0m"
# echo "------------------------------------------------------"
# cnt0=`xcrun altool --upload-app -f ${ipa_path} -t ios --apiKey ${api_key} --apiIssuer ${issuer_id} --verbose"`
# echo $cnt0
# cnt=`echo $cnt0 | grep “No errors uploading” | wc -l`
# if [ $cnt = 1 ] ; then
# echo "\033[32;1m上傳IPA成功?? ?? ?? \033[0m"
# echo "------------------------------------------------------"
# else
# echo "\033[32;1m上傳IPA失敗?? ?? ?? \033[0m"
# echo "------------------------------------------------------"
# fi
# else
# echo "\033[32;1m校驗(yàn)IPA失敗?? ?? ?? \033[0m"
# echo "------------------------------------------------------"
# fi
# 校驗(yàn) + 上傳 方式2
# 驗(yàn)證
validate="xcrun altool --validate-app -f ${ipa_path} -t ios --apiKey ${api_key} --apiIssuer ${issuer_id} --verbose"
echo "running validate cmd" $validate
validateApp="$($validate)"
if [ -z "$validateApp" ]; then
echo "\033[32m校驗(yàn)IPA失敗?? ?? ?? \033[0m"
echo "------------------------------------------------------"
else
echo "\033[32m校驗(yàn)IPA成功?? ?? ?? \033[0m"
echo "------------------------------------------------------"
# 上傳
upload="xcrun altool --upload-app -f ${ipa_path} -t ios --apiKey ${api_key} --apiIssuer ${issuer_id} --verbose"
echo "running upload cmd" $upload
uploadApp="$($upload)"
echo uploadApp
if [ -z "$uploadApp" ]; then
echo "\033[32m傳IPA失敗?? ?? ?? \033[0m"
echo "------------------------------------------------------"
else
echo "\033[32m上傳IPA成功?? ?? ?? \033[0m"
echo "------------------------------------------------------"
fi
fi
fi
exit 0
這里額外補(bǔ)充下xcodebuild兄纺、xcrun命令的知識(shí):
xcodebuild
用于編譯xcode中的projects和workspaces,可通過man xcodebuild查看文檔
常見的命令格式如下:
- 清理工程:
xcodebuild clean -workspace [xcworkspace路徑] \
-scheme [scheme名稱] \
-configuration [編譯方式Release或Debug]
- 編譯工程
xcodebuild archive -workspace [xcworkspace路徑] \
-scheme [scheme名稱]\
-configuration [編譯方式Release或Debug] \
-archivePath [.xcarchive文件路徑]
- 導(dǎo)出ipa
xcodebuild -exportArchive \
-archivePath [.xcarchive文件路徑] \
-exportPath [.ipa文件路徑] \
-exportOptionsPlist [ExportOptions.plist文件路徑] \
-allowProvisioningUpdates
xcrun
用于運(yùn)行或定位開發(fā)工具以及屬性大溜,也可以通過man xcrun查看具體文檔
常見的命令格式如下:
- 校驗(yàn)ipa
xcrun altool --validate-app -f [.ipa的路徑] -t ios --apiKey [api_key] --apiIssuer [issuer id] --verbose
- 上傳ipa到AppStore
xcrun altool --upload-app -f [.ipa的路徑] -t ios --apiKey [api_key] --apiIssuer [issuer id] --verbose
其中上傳所需的api key 和 issuer id需要在AppStore Connect中配置和獲取。使用具有賬戶管理權(quán)限的賬號(hào)登陸App Store Connect 選擇 用戶與訪問 >密鑰 查詢信息:issuser和apiKey(密鑰 ID)估脆,如下所示钦奋,
并將公鑰下載到本地,并將下載好的p8文件保存到需要放到一個(gè)固定目錄下
P8文件放入以下文件中
./private_keys
~/private_keys
~/.private_keys
~/.appstoreconnect/private_keys
除了使用xcrun altool上傳ipa疙赠,我們還可以使用Transpoter付材,但相對(duì)來說,Shell腳本更方便圃阳,實(shí)現(xiàn)了本地自動(dòng)化打包上傳厌衔。
綜上所述,shell腳本打包整體流程如下