前言 Apple提供的常规打包方式主要是由 Xcode
支持的,下面展开来聊聊
Xcode打包 Xcode的打包主要分为两步:
Archive编译归档 Archive
主要是对 target
进行 编译、归档
,生成 .xcarchive
文件。可在 Xcode -> Window -> Organizer -> Archives
中查看
这里所说的 归档
,主要就 对项目源码进行编译后,再将编译生成的各种文件、资源、记录统一封装到一个文件中
,便于管理和回溯。
随机选择一个 .xcarchive
文件,点击 show in Finder
,可以看到一个 .xcarchive
后缀的文件
这个 .xcarchive
文件包含了 应用、符号表信息以及其他的资源
,可以右键 -> 显示包内容进行查看,主要包含以下文件:
BCSymbolMaps:Xcode对BitCode符号表进行混淆(Symbol Hiding)后生成的对照表,和dSYM文件会一一对应
dSYMs:存储此次编译的符号表(debug symbols),用来符号化解析崩溃堆栈
info.plist:项目target配置文件
Products:存储此次编译生成的的 App
包(.app)。虽然这个文件包含了 App
运行需要的可执行文件以及其它资源,但是和最终用户下载的版本会有所不同。后续的 export
操作会对其进行进一步处理。
SCMBlueprint:如果 Xcode
打开了版本管理(Preferences -> Source Control -> Enable Source Control),SCMBlueprint
文件夹会存储此次编译的版本控制信息,包括使用的 git
版本、仓库、分支等。如果需要回溯此次编译的源码版本,可以在这个文件中找到对应的信息。
SwiftSupport:在 Target
的 Build Settings
中打开了ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES
,此次编译使用的 Swift
版本对应的标准库文件(.dylib)会被放到这个文件夹中。发布App时,也会被复制到ipa bundle中。在iOS 12.2及以上,swift的ABI趋于稳定,已经不用再自带链接库了,因此ipa包节省了一定的体积
Export打包分发 Export
主要是的对生成的 .xcarchive
文件进行进一步的处理,生成不同渠道的ipa包,进行分发,Organize -> Archive文件 -> Distribute App
这里对应4种分发渠道:App Store、Ad Hoc、Enterprise和Development
,然后一步步往下操作即可。
以上4中渠道对应的打包 method
分别是 app-store、ad-hoc、enterprise、development
,可以在导出的文件夹中 ExportOptions.plist
文件看到对应参数,如下所示
最终Export导出的文件夹中主要包含以下4种文件:
DistributionSummary.plist:包含ipa所支持的架构、bitcode、证书、embeddedBinaries(非系统的动态库相关)、entitlements(apns环境、application-identifier等)、profile等相关信息
ExportOptions.plist:ipa包导出的 配置文件
,主要包含包导出方式、签名方式、App Developer team的id等
项目名.ipa:应用的 ipa
包,包含了App所需的签名、二进制包、资源等
Packaging.log:打包相关的日志
如果想要查看ipa中的内容,可以将 .ipa
改成 .zip
,然后解压,也可使用命令解压zip -0 -y -r myAppName.ipa Payload/
,其中是 .app
的文件,查看其包内容,主要包含以下几部分:
MachO可执行文件
:具体的介绍可查看 iOS逆向 12:Mach-O文件(上) 和 iOS逆向 12:Mach-O文件(下)
签名文件
:App
的签名信息会被放到 _CodeSignature
文件夹中。
info.plist
:存储 App
主要信息的 plist
文件也会被一并打包到 ipa
中。
entitlements
:App包含的相关权限,在这个文件中通过 xml
的格式将这些授权记录下来,例如Push notifications、App Group等
App Plugins
:如果App实现了 App
Extension扩展,扩展的包会以 .appex
的后缀存储在 PlugIns
文件夹中,随着App的安装一起安装到用户手机上
链接库:App
运行所需要的各种链接库会被放入 Frameworks
文件夹。
资源文件:App
运行需要的各种资源文件也是 ipa
体积的大头。常见的有各种多媒体资源:图片、音视频
xib
文件:.nib .storyboard
各种打包的资源: .bundle
其它类型的资源:字体、数据库、证书等等
除了 Xcode
自带的工具 application loader
上传ipa,还可以通过 Transporter
上传。Transporter
可以以简单轻松的方式将内容交付到 Apple
,提供如下功能:
只需要将ipa包拖放到 Transporter
中即可开始使用
同时验证和上传多个文件以快速交付
查看交付进度(警告、错误和交付日志),以便快速修复交付问题
查看历史交付记录(包含日期、时间)
Transporter
应用现已上架 Mac App Store
,你需要下载并安装,如下图:
登录你的Apple ID,将导出的 .ipa 包拖进来即可:
所以,综上所述,通过 Xcode
提供的打包方式 比较繁琐
,需要人为操作许多步骤,那么我们是否可以通过其他的方式进行简化呢?答案是当然可以呀。
简化打包操作的方式的主要有两种:
这里我们主要介绍 Shell打包脚本
的方式,主要分为以下几步:
配置打包相关的名称:.xcworkspace的名字、scheme名称、编译方式、打包方式、profile文件、bundleID等
配置打包的相关路径:导出路径、archive文件路径、ipa文件路径、ExportOptions.plist文件路径
清理工程:通过 xcodebuild clean
清理
编译:通过 xcodebuild archive编译、归档工程
导出ipa:通过 xcodebuild
命令将 .xcarchive
导出为 ipa
文件
校验并上传AppStore:如果是AppStore的包,通过 xcrun altool
校验并上传 AppStore
使用方式:进入 podfile
所在目录,在终端执行 .sh
文件,例如:sh xxxx.sh
具体的shell脚本如下(注:使用时,需要自行填充其中的xxxxx所在的脚本代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 #!/bin/bash # 前提:enterprise打包时,需要切换到 企业账户 及 BundleID # 使用方法: # step1: 将该脚本放在工程的根目录下(跟.xcworkspace文件or .xcodeproj文件同目录) # step2: 根据情况修改下面的参数 # step3: 打开终端,执行脚本。(输入sh,然后将脚本文件拉到终端,会生成文件路径,然后enter就可) echo "-----------开始执行脚本-----------" # =============项目自定义部分(自定义好下列参数后再执行该脚本)=================== # echo "请选择打包方式 ? [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 "请选择打包方式 ? [ 1:enterprise_debug 2:enterprise_release 3:ad_hoc 4:app_store]" read number done #-----------脚本配置信息----------- # .xcworkspace的名字,必填 workspace_name="xxxxx" # 指定项目的scheme名称(也就是工程的target名称),必填 scheme_name="xxxxx" # 指定要打包编译的方式 : Release,Debug。一般用Release。必填 build_configuration="Release" # method,打包的方式。方式分别为 development, ad-hoc 。必填 method="enterprise" # 下面两个参数只是在手动指定Pofile文件的时候用到,如果使用Xcode自动管理Profile,直接留空就好 # (跟method对应的)mobileprovision文件名,需要先双击安装.mobileprovision文件.手动管理Profile时必填 # mobileprovision_name="9d8c7290-4345-4ebf-82d4-a74cab2ea40b" mobileprovision_name="" # 项目的bundleID,手动管理Profile时必填 bundle_identifier="" # if [[ $number == 1 ]]; then # bundle_identifier="com.mi.global.sho" # else # bundle_identifier="com.mi.global.shop" # fi # 每次编译后是否Build自动加1, # 可以修改该常量的值,以决定编译后还是打包后Build自动加1 # # 0: 每次打包后Build自动加1 # # 1: 每次编译后Build自动加1 DEBUG_ENVIRONMENT_SYMBOL=0 # 根据选项配置不同的包 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 "--------------------脚本配置参数检查--------------------" 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" # =======================脚本的一些固定参数定义(无特殊情况不用修改)====================== # # 获取当前脚本所在目录 script_dir="$( cd "$( dirname "$0" )" && pwd )" # 工程根目录 project_dir=$script_dir # 指定输出导出文件夹路径 export_path="$project_dir/Build" # 指定输出归档文件路径 export_archive_path="$export_path/$scheme_name.xcarchive" # 指定输出ipa文件夹路径 export_ipa_path="$export_path/" # 指定导出ipa包需要用到的plist配置文件的路径 export_options_plist_path="$project_dir/ExportOptions.plist" echo "--------------------脚本固定参数检查--------------------" 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" # =======================自动打包部分(无特殊情况不用修改)====================== # echo "------------------------------------------------------" echo "\033[32m开始构建项目 \033[0m" # 进入项目工程目录 cd ${project_dir} # 指定输出文件目录不存在则创建 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} # 检查是否构建成功 # xcarchive 实际是一个文件夹不是一个文件所以使用 -d 判断 if [ -d "$export_archive_path" ] ; then echo "\033[32;1m项目构建成功 🚀 🚀 🚀 \033[0m" else echo "\033[31;1m项目构建失败 😢 😢 😢 \033[0m" exit 1 fi echo "------------------------------------------------------" echo "\033[32m开始导出ipa文件 \033[0m" # 先删除export_options_plist文件 if [ -f "$export_options_plist_path" ] ; then #echo "${export_options_plist_path}文件存在,进行删除" rm -f $export_options_plist_path fi # 根据参数生成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导出 ${scheme_name}.ipa 包成功 🎉 🎉 🎉 \033[0m" open $export_path else echo "\033[31;1m导出 ${scheme_name}.ipa 包失败 😢 😢 😢 \033[0m" exit 1 fi # 删除export_options_plist文件(中间文件) if [ -f "$export_options_plist_path" ] ; then #echo "${export_options_plist_path}文件存在,准备删除" rm -f $export_options_plist_path fi # 输出打包总用时 echo "\033[36;1m使用AutoPackageScript打包总用时: ${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上传固定参数检查--------------------" echo "ipa_path = ${ipa_path}" echo "api_key = ${api_key}" echo "issuer_id = ${issuer_id}" # 校验 + 上传 方式1 # # 校验指令 # 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校验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校验IPA失败😢 😢 😢 \033[0m" # echo "------------------------------------------------------" # fi # 校验 + 上传 方式2 # 验证 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校验IPA失败😢 😢 😢 \033[0m" echo "------------------------------------------------------" else echo "\033[32m校验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
这里额外补充下 xcodebuild、xcrun
命令的知识:
xcodebuild
用于编译xcode中的projects和workspaces,可通过man xcodebuild查看文档
常见的命令格式如下:
1 2 3 xcodebuild clean -workspace [xcworkspace路径] \ -scheme [scheme名称] \ -configuration [编译方式Release或Debug]
1 2 3 4 xcodebuild archive -workspace [xcworkspace路径] \ -scheme [scheme名称]\ -configuration [编译方式Release或Debug] \ -archivePath [.xcarchive文件路径]
1 2 3 4 5 xcodebuild -exportArchive \ -archivePath [.xcarchive文件路径] \ -exportPath [.ipa文件路径] \ -exportOptionsPlist [ExportOptions.plist文件路径] \ -allowProvisioningUpdates
xcrun 用于 运行或定位开发工具以及属性
,也可以通过 man xcrun
查看具体文档
常见的命令格式如下:
1 xcrun altool --validate-app -f [.ipa的路径] -t ios --apiKey [api_key] --apiIssuer [issuer id] --verbose
1 xcrun altool --upload-app -f [.ipa的路径] -t ios --apiKey [api_key] --apiIssuer [issuer id] --verbose
其中上传所需的 api key
和 issuer id
需要在 AppStore Connect
中配置和获取。使用具有账户管理权限的账号登陆 App Store Connect
选择 用户与访问 >密钥
查询信息: issuser和apiKey(密钥 ID)
,如下所示,
并将 公钥
下载到本地,并将下载好的 p8文件
保存到需要放到一个固定目录下:
1 2 3 4 5 // P8文件放入以下文件中 ./private_keys ~/private_keys ~/.private_keys ~/.appstoreconnect/private_keys
除了使用 xcrun altool
上传ipa,我们还可以使用 Transpoter
,但相对来说,Shell脚本更方便,实现了本地自动化打包上传。
综上所述,shell脚本打包整体流程如下: