如果你曾經試過做多 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 ]秒" |
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式