關於 iOS 批量打包的總結
如果你曾經試過做多 target 的項目,到了測試人員要測試包的時候,你就會明白什么叫“生不如死”。雖然 Xcode 打包很方便,但是當你機械重復打 N 次包的時候,就會覺得這純粹是浪費時間的工作。所以這時候自動化打包就顯得尤為重要(其實就算只有一個 target,就算使用 Xcode 打包很方便,也應該構建自動化打包,因為你可以節省大量時間)。
構建自動化打包腳本
xcodebuild
使用 xcodebuild -h
來看看 xcodebuild 到底是干啥的
1
2
3
4
5
6
7
8
9
|
Usage: xcodebuild [-project ] [[-target ]...|-alltargets] [-configuration ] [-arch ]... [-sdk [|]] [-showBuildSettings] [=]... []...
xcodebuild [-project ] -scheme [-destination ]... [-configuration ] [-arch ]... [-sdk [|]] [-showBuildSettings] [=]... []...
xcodebuild -workspace -scheme [-destination ]... [-configuration ] [-arch ]... [-sdk [|]] [-showBuildSettings] [=]... []...
xcodebuild -version [-sdk [|] [] ]
xcodebuild -list [[-project ]|[-workspace ]] [-json]
xcodebuild -showsdks
xcodebuild -exportArchive -archivePath -exportPath -exportOptionsPlist
xcodebuild -exportLocalizations -localizationPath -project [-exportLanguage ...]
xcodebuild -importLocalizations -localizationPath -project
|
這里我只截取了 usage 部分,option 部分太多沒有截取。
這里介紹幾條畢竟常用的命令
1. xcodebuild -list …
xcodebuild -list [[-project ]|[-workspace ]] [-json]
usage: 輸出 project 中的 targets 和 configurations,或者 workspace 中 schemes。-project
和 -workspace
是輸出指定內容,不輸入默認輸出當前目錄下。-json
是以 json 格式輸出。
example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ xcodebuild -list
Information about project "XX":
Targets:
XX
XXTests
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
XX
|
2. xcodebuild -project …
xcodebuild [-project ] [[-target ]...|-alltargets] [-configuration ] [-arch ]... [-sdk [|]] [-showBuildSettings] [=]... []...
usage:
-project
: 指定 project 名字,默認首個 project。
-target
: 指定對應的 target ,默認首個 target。
-configuration
: 選擇Debug 或 Release,默認 Release,當然如果你有自定義的配置的,就應該選你配置的,上面 -list
中有輸出。
-showBuildSettings
: 顯示工程的配置。
=
: 修改工程的配置文件。
buildaction ...
: 如下,默認為 build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Specify a build action (or actions) to perform on the target.
Available build actions are:
build Build the target in the build root (SYMROOT). This is
the default build action.
installsrc Copy the source of the project to the source root
(SRCROOT).
install Build the target and install it into the target's
installation directory in the distribution root
(DSTROOT).
clean Remove build products and intermediate files from the
build root (SYMROOT).
|
example:
$ xcodebuild -project 你的項目名字.xcodeproj -target 你的 target 名字 -configuration release
這行命令表示編譯 xx.xcodeproj 的 xx target。在 terminal 中會看到編譯過程,如果成功最后會輸出 ** BUILD SUCCEEDED **
。最后會在當前目錄下生成 build/Release-iphoneos/xx.app
$ xcodebuild -project 你的項目名字.xcodeproj -target 你的 target 名字 -configuration release -showBuildSettings
這行命令使用 -showBuildSettings
是不會 build 項目的,只是輸出工程的配置。這里輸出的的內容有(內容過多,只截取部分)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Build settings for action build and target XX:
ACTION = build
AD_HOC_CODE_SIGNING_ALLOWED = NO
ALTERNATE_GROUP = staff
ALTERNATE_MODE = u+w,go-w,a+rX
ALTERNATE_OWNER = TsuiYuenHong
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
ALWAYS_SEARCH_USER_PATHS = NO
ALWAYS_USE_SEPARATE_HEADERMAPS = NO
APPLE_INTERNAL_DEVELOPER_DIR = /AppleInternal/Developer
APPLE_INTERNAL_DIR = /AppleInternal
APPLE_INTERNAL_DOCUMENTATION_DIR = /AppleInternal/Documentation
APPLE_INTERNAL_LIBRARY_DIR = /AppleInternal/Library
APPLE_INTERNAL_TOOLS = /AppleInternal/Developer/Tools
APPLICATION_EXTENSION_API_ONLY = NO
APPLY_RULES_IN_COPY_FILES = NO
ARCHS = armv7 arm64
...
|
如果要修改配置文件,就直接最命令最后加上你要修改的內容。
例如在這行命令最后加上指定證書
$ xcodebuild -project 你的項目名字.xcodeproj -target 你的 target 名字 -configuration release PROVISIONING_PROFILE="你證書的id"
其中的字段是上面 -showBuildSettings
顯示的字段,也可以看官網介紹
3. xcodebuild -workspace …
xcodebuild -workspace -scheme [-destination ]... [-configuration ] [-arch ]... [-sdk [|]] [-showBuildSettings] [=]... []...
除了 workspace 和 scheme 之外其余選項都和上條命令相同。
-workspace
: 指定 workspace 名字,默認首個 workspace
-scheme
: 指定對應的 scheme ,默認首個 scheme
4 . xcodebuild -exportArchive …
這里順便介紹一下 archive 命令,因為在下面使用 PackageApplication 會出一個警告說推薦使用 -exportArchive
。所以我們就來嘗試一下使用 archive 來生成 app。
首先使用一下命令來生成 .xcarchive 文件xcodebuild archive -workspace xx.xcworkspace -scheme xx -archivePath xx.xcarchive
可以看出添加上 archive 命令和最后加入 -archivePath
生成archivePath的路徑即可。
然后該路徑下會生成一個 xx.archivePath,里面包括三個文件,xx.app.dsym文件(可用於bugly等監控bug的平台),info.plist(保存打包的一些信息),還有我們的 xx.app 文件。
其次使用 -exportArchive 生成 ipa 包
xcodebuild -exportArchive -archivePath xx.xcarchive -exportPath xx -exportFormat ipa
-archivePath
: xx.archivePath 的路徑
-exportPath
: 輸出路徑
-exportFormat
: 生成類型,這里選擇我們需要的 ipa
這樣就利用我們的 xcodebuild 命令來生成 ipa 包
xcrun
這里也使用 xcrun 來生成 ipa 包即可
xcrun -sdk iphoneos PackageApplication build/Release-iphoneos/xx.app -o ~/Desktop/xx.ipa
但是,在 macos10.12 和 Xcode8 的環境下會出現一個警告
warning: PackageApplication is deprecated, use xcodebuild -exportArchive instead.
說明 PackageApplication 已經被棄用了。
不過其實這一步可以幾乎等價於將 xx.app 放入一個 payload 的文件夾下然后壓縮文件夾為 xx.ipa,當然這樣做缺失一些信息,不過並不影響程序的運行。
初步小結
綜上,我們有兩種方法來生成我們需要的 ipa 包。
- 使用 xcodebuild 命令來編譯我們的項目生成 app,然后再用 xcrun 將 app 轉 ipa。
- 使用 xcodebuild archive 命令來直接生成我們需要的 ipa。
雖然現在網上幾乎都是使用 xcodebuild + xcrun 來來生成 ipa 包,不過既然官方說 PackageApplication is deprecated
,那還是推薦使用第二種方法,一步到位。
自動化打包正式開始
這里從我工作室的一個項目切入,這個項目需要最終生成 18 個 ipa 包,但是他們幾乎是共用一套代碼的,不同的地方在於bundleName/bundleDisplayName/bundleid 等,以及一些資源文件的不同,例如 icon 等。所以可想而知如果選擇手動打包的痛苦,並且當你打包到一半發現某個地方錯了要重新打包 ……
這里說一下自動化打包1.0解決思路:
- 使用命令
defaults write
來修改項目中的 plist 文件,來達到修改 bundleName/bundleDisplayName/bundleid… 的目的。 - 使用命令
cp
來替換資源文件。 - 使用
xcodebuild -workspace ..
編譯出 app 包。 - 使用
xcrun ...
生成 ipa 。
這是我最開始想到的思路,最終運行時間大概為每個包2.5m(時間主要浪費在編譯),然后一套下來也要半個多小時。雖然比起手動打快了不少,但還是太慢了。畢竟自動化的目的不僅僅是自動,還要速度。
既然問題出在編譯上,那我的思路就往編譯一次多次使用這個方向上面思考。然后想到了既然只是資源文件和plist的不同,沒有涉及到代碼的更換(不過這個項目后期不同 app 會執行不同一套代碼,不過也有解決辦法),這里就出現了自動化打包2.0的版本。
- 使用
xcodebuild -workspace ..
編譯出 app 包。 - 使用命令
defaults write
來修改項目中的 plist 文件,來達到修改 bundleName/bundleDisplayName/bundleid… 的目的。 - 使用命令
cp
來替換資源文件。 - 重簽名
codesign -f -s "iPhone Distribution: xx co., LTD" --entitlements $Entitlements $ipaPath/Payload/YouXiaoYun.app
- 使用
xcrun ...
生成 ipa 。
和1.0大致相似,不過並不是每次生成 ipa 都需要編譯一次。而是編譯一次,然后直接修改 app 下內容,不過這里會出現簽名錯誤的問題,因為在編譯的最后會用證書幫 app 簽名,如果你直接替換資源然后就生成 ipa 的話會導致 ipa 無法安裝。
那這時候神奇的重簽名技術就出來(重簽名用在正途上的真少見…hhhh,關於重簽名的文章 google 一下就會很多),使用 codesign 命令就可以幫修改過資源的 app 重簽名。
最終使用2.0的時間基本是在5-6分鍾左右。果然能機器完成的工作絕對不要手動完成,從半天到30分鍾到最后的6分鍾,節省下來的時間可以讓你學習到更多。
上面說到如果不同 app 間會用到不同的代碼。例如 app A 里面的 title 叫 A 部門,app B 里面 title 又叫 B 部門,這樣就不會通過命令行直接修改到代碼,不過我想到的是維護一個 plist 文件,plist 文件可以這樣設計的,每個不同 app 的 bundleName 都設置字典的鍵,然后字典下就可以是你自定義的內容。然后每次啟動 app 就根據 bundleName 來尋找對應的字典,然后 title 就賦值為 plist 下 title 的值。如果不同代碼就根據 code1 里面的值來 switch 不同的代碼。
最終代碼
以下是完整的腳本文件,部分信息需要自己替換。
以下腳本適用於一次打 N 個包,適用情況:
- 可以替換 bundle 信息
- 替換音頻圖片資源
- 可以執行不同代碼
- 生成相應的plist文件
- 上傳到蒲公英分發平台
當然也可以打一個包,適當刪除某些代碼即可。
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
|
# 1.Configuration Info
# 項目路徑 需修改
projectDir="你的項目路徑"
# 打包生成路徑 需修改
ipaPath="ipa生成路徑"
# 圖標路徑 需修改
iconPath="~/Desktop/icon"
# Provisioning Profile 需修改 查看本地配置文件
PROVISIONING_PROFILE="xxxxxxx-xxxx-4bfa-a696-0ec7391b24d8"
############# 重簽名需要文件
# 以下文件需放在 ipaPath 路徑下
Entitlements=$ipaPath/entitlements.plist
#############
# 版本號
bundleVersion="2.0.0"
# 選擇打包序號 多選則以空格隔開 如("1" "2" "3")
appPackNum=("1 2")
# 蒲公英分發參數 不分發可忽略 默認不分發 下面的兩個KEY是默認測試的網址對應KEY
ISUPLOAD=0
USERKEY="xxx"
APIKEY="xxx"
# ---------------------------可選 如果需要替換 app 的 icon --------------------------------- #
# 配置App信息數組 格式:"AppName(和工程中appInfo.Plist對應)" "icon"
#Schemes:
# 1.app1 app1Icon
# 2.app2 app2Icon
# 3.app3 app3Icon
# --------------------------------------------------------------------------------------- #
# 打包個數
appPackNumLength=${#appPackNum[*]}
appInfos=(
"app1" "app1Icon" "xxxx"
"app2" "app2Icon" "xxxx"
"app3" "app3Icon" "xxxx"
)
appInfosLength=${#appInfos[*]}
# Scheme Name
schemeName="xx"
# Code Sign ID
CODE_SIGN_IDENTITY="xx co., LTD"
# 生成 APP 路徑
buildDir="build/Release-iphoneos"
# 開始時間
beginTime=`date +%s`
# 創建打包目錄
mkdir ${ipaPath}/AllPack
# 本地存放全部 IPA 的路徑
allIPAPackPath="${ipaPath}/allPack"
# 清除緩存
rm -rf $projectDir/$buildDir
# Build 生成 APP
xcodebuild -workspace ${projectDir}/xx.xcworkspace -scheme ${schemeName} -configuration Release clean -sdk iphoneos build CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}" PROVISIONING_PROFILE="${PROVISIONING_PROFILE}" SYMROOT="${projectDir}/build"
if [[ $? = 0 ]]; then
echo "\033[31m 編譯成功\n \033[0m"
else
echo "\033[31m 編譯失敗\n \033[0m"
fi
# 先創建 payload 文件夾
mkdir ~/Desktop/Payload
# 移動編譯生成的 app 到桌面的 Payload 文件夾下
cp -Rf ${projectDir}/${buildDir}/${schemeName}.app $ipaPath/Payload
# 以下二選一
# 1.----全部打包----
#for (( i=0; i $plist_path
itemsassetskindsoftware-packageurlhttps://xxxxxxxxxxxx/$appDownloadName.ipakinddisplay-imageurlhttps://xxxxxxxxxxxx/${appIconName}.pngkindfull-size-imageurlhttps://xxxxxxxxxxxx/${appIconName}.pngmetadatabundle-identifier你的bundidbundle-version$bundleVersionkindsoftwaretitle$appDownloadName
EOF
# 移動
mv ${ipaPath}/$appDownloadName.ipa ${allIPAPackPath}/$appName
# 6.上傳蒲公英分發平台
if [[ $ISUPLOAD = 1 ]]; then
echo "正在上傳蒲公英..."
curl -F "file=@$allIPAPackPath/$appName/$appDownloadName.ipa" -F "uKey=$USERKEY" -F "_api_key=$APIKEY" http://www.pgyer.com/apiv1/app/upload
fi
done
# 清除無關文件
rm -rf $ipaPath/Payload
# 結束時間
endTime=`date +%s`
echo -e "打包時間$[ endTime - beginTime ]秒"
|