前言
如今涉及到編譯打包的工做主要是如下兩個:html
- 提交測試版本給測試同事
- 提交App Store審覈
兩個流程分別是:java
- 修改證書和配置文件,而後「Product -> Archive」編譯打包,以後在自動彈出的 「Organizer」 中進行選擇,根據須要導出 ad hoc enterprise 類型的 ipa 包。等待導出以後再提交到Fir上,等Fir提交完成就須要告知測試同事。整個流程下來通常都要半個多小時,並且須要人工監守操做。
- 第二個也是差很少,打包完以後須要操做幾個步驟而後上傳到App Store,上傳時間較長,並且中間可能會有錯誤須要處理。上傳後等待蘋果處理二進制包,蘋果處理後上去選擇構建包,點擊提交審覈。
因此研究下自動化編譯打包,提升下效率,減小人工操做成本。python
主要有兩種實現途徑,AppleScript和Shell腳本,
AppleScript
沒怎麼研究,網上說是很強大的腳本語言。ios下面主要講Shell腳本的實現,網上也有人實現了並託管在
github
上,能夠參考下。gitShell腳本涉及的工具
主要是如下幾個工具:web
- xcodebuild
- xcrun
- altool(提交到App Store使用)
- fir-cli(上傳到fir時使用)
- Python的smtplib(以前已經寫過python的發郵件了,因此就直接用沒有用Shell寫。)
- PlistBuddy
xcodebuild和xcrun
xcodebuild
和xcrun
都是來自Command Line Tools
,Xcode自帶,若是沒有能夠經過如下命令安裝:shell
1
xcode-select --install
或者在下面的連接下載安裝:swift
安裝完可在如下路徑看到這兩個工具:
/Applications/Xcode.app/Contents/Developer/usr/bin/
- xcodebuild
主要是用來編譯,打包成Archive和導出ipa包。
能夠執行
xcodebuild -help
查看,主要展現了幾種用法、一些可選項,最後是比較重要的exportOptionsPlist文件的一些可選key,這個文件在後面導出ipa包會用到。主要下面三個查看的命令比較重要:
1
2
3-showsdks display a compact list of the installed SDKs
-showBuildSettings display a list of build settings and values
-list lists the targets and configurations in a project, or the schemes in a workspace後面兩個須要在Xcode的project或者workspace目錄下才能用。
- xcrun
1
xcrun -h
主要是打包,看網上比較可能是用這個工具打包各類渠道包。
altool
這個工具在網上搜索幾乎沒有什麼結果,大概國內直接用命令行工具提交App Store的比較少。後來在StackOverflow上才找到相關的文檔:
https://itunesconnect.apple.com/docs/UsingApplicationLoader.pdf
在上面的文檔第38頁講述瞭如何使用altool上傳二進制文件。
這個工具其實是ApplicationLoader,打開Xcode-左上角Xcode-Open Developer Tool-Application Loader 可看到。有個「交付您的應用」操做,網上看到有人是直接用這個工具上傳的。
altool的路徑是:
/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool
使用時會提示下面的錯誤:
1
2
3altool[] *** Error: Exception while launching iTunesTransporter:
Transporter not found at path: /usr/local/itms/bin/iTMSTransporter.
You should reinstall the application.創建個軟連接可解決(相似於Windows的快捷方式):
1
ln -s /Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/itms /usr/local/itms
fir-cli
安裝時會提示各類權限不容許,能夠執行下面命令:
1
2echo 'gem: --bindir /usr/local/bin' >> ~/.gemrc
sudo 'gem install fir-clifir有提供Android Studio、Eclipse、gradle插件,能夠看下。
這是它的github地址,其中講到有對?
xcodebuild
?原生指令進行了封裝。PlistBuddy
Plist在Mac OSX系統中起着舉足輕重的做用,系統和程序使用Plist文件來存儲本身的安裝/配置/屬性等信息。而PlistBuddy是Mac裏一個用於命令行下讀寫plist文件的工具,在/usr/libexec/下。能夠經過它讀取或修改plist文件的內容。
這裏我僅經過它來獲取內部版本號、外部版本號。在一些文章中見過用來修改plist文件的信息來導出出不一樣須要的包。
一些概念的區別
Workspace、Project、Scheme、Target的區別。
下面是官方文檔:
下面從上往下大概說下,具體看文檔比較好:
- Workspace
Workspace
是最大的集合,能夠包含多個Project
,能夠管理不一樣的Project
之間的關係。Workspace
是以xcworkspace
的文件形式存在的。(這點和Project
一致)。Workspace
的存在是爲了解決原來僅有Project
的時候不一樣的Project
之間的引用和調用困難的問題。同時,一個Workspace
的Project
共用一個編譯路徑。好比使用CocoaPod、或者使用其餘開發庫/框架。
- Project
Project
是一個倉庫,包含編譯一個或多個product
所需的文件、資源和信息,保持和聚合這些元素間的關係。(每一個Target
能指定本身的Build Settings
來覆蓋Project
的)
- Source code, including header files and implementation files
- Libraries and frameworks, internal and external
- Resource files
- Image files
- Interface Builder (nib) files
- Scheme
Scheme
包含了一些要構建的Scheme,一些構建時用到的設置,一些要運行的測試。同時只能有一個Scheme
是有效的。
- Target
Target
是對應了具體一個想要構建的Product
,包含了一些構建這個Product
所需的配置和文件(build settings
和build phases
)。一個Project
能夠包含多個Target
。具體實現
看起來有兩種實現方法:
- 網上能夠查到的文章,大多數都是用
xcodebuild
和xcrun
實現的,好比:
1
2xcodebuild -workspace XXX -scheme XXX -configuration Release
xcrun -sdk iphoneos PackageApplication -v "/XXX/XXX.app" -o "/XXX/XXX"這些文章都是相對比早期的,大多數用於打包不一樣渠道包。
- 另外一種是
xcodebuild
的archive
和-exportArchive
,只有一兩篇文章是用這個,並且也過期了,由於如今最新是須要用-exportOptionsPlist
這個選項。我用的是第二種,並用上
-exportOptionsPlist
選項,後面我會簡單給下這兩種的結果比較。腳本流程是:
- 準備兩個
Plist
文件,用於導出不一樣ipa
包時使用。- 獲取命令行參數,區分上傳到
Fir
仍是App Store
- 清理構建目錄
- 編譯打包
- 導出包
- 上傳到
Fir
或者驗證並上傳到App Store
- 發郵件通知
準備Plist文件
根據
xcodebuild -help
提供的可選key能夠知道,compileBitcode
、embedOnDemandResourcesAssetPacksInBundle
、iCloudContainerEnvironment
、manifest
、onDemandResourcesAssetPacksBaseURL
、thinning
這幾個key用於非App Store
導出的;uploadBitcode
、uploadSymbols
用於App Store
導出;method
、teamID
共用。method的可選值爲:
app-store, package, ad-hoc, enterprise, development, and developer-id
因此我建了兩個文件:
AppStoreExportOptions.plist
、AdHocExportOptions.plist
。AppStoreExportOptions.plist:method=app-store,uploadBitcode=YES,uploadSymbols=YES
AdHocExportOptions.plist:method=ad-hoc,compileBitcode=NO
獲取命令行參數
用
Shell
內置的getopts
命令,這屬於Shell的範疇就很少講了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23if [ $# -lt 1 ];then
echo "Error! Should enter the archive type (AdHoc or AppStore)."
echo ""
exit 2
fi
while getopts 't:' optname
do
case "$optname" in
t)
if [ ${OPTARG} != "AdHoc" ] && [ ${OPTARG} != "AppStore" ];then
echo "invalid parameter of $OPTARG"
echo ""
exit 1
fi
type=${OPTARG}
;;
*)
echo "Error! Unknown error while processing options"
echo ""
exit 2
;;
esac
done清理構建目錄
就如在Xcode操做「Product -> Clean」。
1
2
3log_path="/XXX/XXX"
configuration="Release"
xcodebuild clean -configuration "$configuration" -alltargets >> $log_pathlog_path是一個文檔路徑,只是用來記錄命令的輸出,由於都打在終端會不少,另外也方便後面分析。後面的命令也是如此。這裏面帶的選項能夠根據須要參考
xcodebuild -help
的信息。編譯打包成Archive
就如在Xcode操做「Product -> Archive」
1
2
3
4
5
6
7
8
9
10workspaceName="XXX.xcworkspace"
scheme="XXX"
configurationBuildDir="XXX/build"
codeSignIdentity="iPhone Distribution: XXX, Ltd. (xxxxxxxxxx)"
adHocProvisioningProfile="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
appStoreProvisioningProfile="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
configuration="Release"
archivePath="/xxx/XXX.xcarchive"
xcodebuild archive -workspace "$workspaceName" -scheme "$scheme" -configuration "$configuration" -archivePath "$archivePath" CONFIGURATION_BUILD_DIR="$configurationBuildDir" CODE_SIGN_IDENTITY="$codeSignIdentity" PROVISIONING_PROFILE="$provisioningProfile" >> $log_path這裏的
CONFIGURATION_BUILD_DIR
是中間文件生成的路徑,能夠不指定;CODE_SIGN_IDENTITY
是證書名(在對應TARGETS
的Build Settings
中選擇完Code Sinning
,再點擊選擇Other...
,就能夠獲得這串東西);PROVISIONING_PROFILE
是配置文件(獲取方法同CODE_SIGN_IDENTITY,格式通常是xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
)。還能夠添加其餘參數,不設置的都是默認使用項目Build Settings裏面的配置,包括CODE_SIGN_IDENTITY
和PROVISIONING_PROFILE
。若是是workspace就用
-workspace
,就像編譯帶有CocoaPods
的項目,若是是普通項目則用-project
。執行完會生成一個.xcarchive文件和build文件夾以下:
1
2
3
4
5
6
7
8
9
10.xcarchive
build文件夾
|------.a
|------.app
|------.app.dSYM
|------.swiftmodule文件夾
|------arm.swiftdoc
|------arm.swiftmodule
|------arm64.swiftdoc
|------arm64.swiftmodule將Archive導出
1
xcodebuild -exportArchive -archivePath "$archivePath" -exportOptionsPlist "$exportOptionsPlist" -exportPath "/XXX/XXX" >> $log_path
其中
$exportOptionsPlist
是對應使用的Plist的完整路徑(包括文件名)。而後就會在指定的
exportPath
路徑下生成.ipa文件。上傳到Fir
1
2
3firApiToken="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ipaPath="/xxx/xxx.ipa"
fir publish "$ipaPath" -T "$firApiToken" >> $log_path
firApiToken
在登陸Fir後,右上角-API token看到。驗證並上傳到App Store
1
2
3altoolPath="/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool"
${altoolPath} --validate-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml >>
${altoolPath} --upload-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml在上面的PDF文檔第38頁講明瞭用法和各個可選項,具體能夠看下PDF。須要說明的是,生成的結果是xml打印在終端,能夠保存到文檔再解析出key來判斷是否成功,目前這步還沒作。
這是成功的結果:
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<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>os-version</key>
<string>10.11.2</string>
<key>success-message</key>
<string>No errors validating archive at /XXX/XXX.ipa</string>
<key>tool-version</key>
<string>1.1.902</string>
<key>xcode-versions</key>
<array>
<dict>
<key>path</key>
<string>/Applications/Xcode.app</string>
<key>version.plist</key>
<dict>
<key>BuildVersion</key>
<string>7</string>
<key>CFBundleShortVersionString</key>
<string>7.2</string>
<key>CFBundleVersion</key>
<string>9548</string>
<key>ProductBuildVersion</key>
<string>7C68</string>
<key>ProjectName</key>
<string>IDEFrameworks</string>
<key>SourceVersion</key>
<string>9548000000000000</string>
</dict>
</dict>
</array>
</dict>
</plist>這是失敗的結果(找不到iTMSTransporter的狀況,用前面說的ln -s解決):
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<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>os-version</key>
<string>10.11.2</string>
<key>product-errors</key>
<array>
<dict>
<key>code</key>
<integer>-10001</integer>
<key>message</key>
<string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application.</string>
<key>userInfo</key>
<dict>
<key>MZUnderlyingException</key>
<string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application.</string>
<key>NSLocalizedDescription</key>
<string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application.</string>
<key>NSLocalizedFailureReason</key>
<string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application.</string>
</dict>
</dict>
</array>
<key>tool-version</key>
<string>1.1.902</string>
<key>xcode-versions</key>
<array>
<dict>
<key>path</key>
<string>/Applications/Xcode.app</string>
<key>version.plist</key>
<dict>
<key>BuildVersion</key>
<string>7</string>
<key>CFBundleShortVersionString</key>
<string>7.2</string>
<key>CFBundleVersion</key>
<string>9548</string>
<key>ProductBuildVersion</key>
<string>7C68</string>
<key>ProjectName</key>
<string>IDEFrameworks</string>
<key>SourceVersion</key>
<string>9548000000000000</string>
</dict>
</dict>
</array>
</dict>
</plist>可見,成功會有個
success-message
的key,而失敗會有product-errors
的key。郵件通知相關同事
發郵件時可能會想帶上當前版本的一些信息,如版本號、內部版本號等,能夠用PlistBuddy實現讀取甚至修改Plist文件。
1
2
3appInfoPlistPath="`pwd`/xxx/xxx-Info.plist"
bundleShortVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" ${appInfoPlistPath})
bundleVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" ${appInfoPlistPath})以後即是發郵件:
1
python sendEmail.py "測試版本 iOS ${bundleShortVersion}(${bundleVersion})上傳成功" "趕忙下載體驗吧!http://fir.im/meijia"
或者
1
python sendEmail.py "正式版本 iOS ${bundleShortVersion}(${bundleVersion})提交成功" "iOS ${bundleShortVersion} 提交成功!"
python主要用smtplib,網上的文章大多都是舊的,特別是講到SSL時特別複雜,其實具體看下smtplib的接口文檔就能夠實現了。另外有可能出現標題、內容亂碼的現象。整合了下面的連接解決了:
下面是實現了SSL Smtp登陸的。
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#!/usr/bin/env python3
#coding: utf-8
# sendEmail title content
import sys
import smtplib
from email.mime.text import MIMEText
from email.header import Header
sender = 'xxxxxx@qq.com;'
receiver = 'xxx@qq.com;'
smtpserver = 'smtp.qq.com'
#smtpserver = 'smtp.exmail.qq.com'
username = sender
password = 'xxxxxx'
def send_mail(title, content):
try:
msg = MIMEText(content,'plain','utf-8')
if not isinstance(title,unicode):
title = unicode(title, 'utf-8')
msg['Subject'] = title
msg['From'] = sender
msg['To'] = receiver
msg["Accept-Language"]="zh-CN"
msg["Accept-Charset"]="ISO-8859-1,utf-8"
smtp = smtplib.SMTP_SSL(smtpserver,465)
smtp.login(username, password)
smtp.sendmail(sender, receiver, msg.as_string())
smtp.quit()
return True
except Exception, e:
print str(e)
return False
if send_mail(sys.argv[1], sys.argv[2]):
print "done!"
else:
print "failed!"能夠賦值給msg[‘CC’]實現抄送,通過測試,抄送的人過多會有一部分不成功,網上查了是這個庫的bug。發送多我的用分號,另外末尾也要用分號。
上傳符號表到Bugly
用於分析解決崩潰bug挺好用的,並且他們的客服也很及時。
發現他們的2.4.1版本有問題,反饋後他們給了2.4.3版本,經測試沒問題。
在Bugly官網下載符號表工具
設置settings.txt
調用命令
1
java -jar buglySymboliOS.jar -d -i $dSYM -u -id "xxxxxxxxx" -key "xxxxxxxxxxx" -package "com.xxx.xxx" -version "$version" -o "xxx.zip"
注意版本號之類的要設置對。
簡單例子
清理構建目錄:
1
xcodebuild clean -configuration Release -alltargets
歸檔(其餘參數不指定的話,默認用的是.xcworkspace或.xcodeproj文件裏的配置)
1
xcodebuild archive -workspace xxx.xcworkspace -scheme xxx -configuration Release -archivePath ./xxx.xcarchive
導出IPA
1
xcodebuild -exportArchive -archivePath ./xxx.xcarchive -exportOptionsPlist ./AdHocExportOptions.plist -exportPath ./
上傳FIR
1
fir publish ./xxx.ipa -T xxxxxx
提交AppStore
1
2/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool --validate-app -f ./xxx.ipa -u xxx -p xxx -t ios --output-format xml
/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool --upload-app -f ./xxx.ipa -u xxx -p xxx -t ios --output-format xml
發郵件
1
python sendEmail.py "郵件內容" "用戶名" "密碼"
上傳符號表
1
java -jar buglySymboliOS.jar -d -i $dSYM -u -id "xxxxxxxxx" -key "xxxxxxxxxxx" -package "com.xxx.xxx" -version "$version" -o "xxx.zip"
對比實驗
爲了瞭解一些區別,我作了幾個對比。我這裏定義下三種方式,方便下面說明。
- xcodebuild+xcrun(xcodebuild build和xcrun)
- 只用xcodebuild(archive和exportArchive),
- Xcode。
三種方式的對比
我使用xcodebuild+xcrun、僅xcodebuild、Xcode三種分別對相同代碼和配置進行操做,根據結果作比較:
- xcodebuild+xcrun
ipa:40.7MB,.app:93.3MB,編譯耗時:8m31s,打包耗時:15s。
- 僅xcodebuild
ipa:37.3MB,.app:74MB,.xcarchive:227.3MB,編譯耗時:8m24s,打包耗時:26s。
- Xcode
ipa:37.3MB,.app:74MB,.xcarchive:227.3MB,編譯耗時:8m40s,打包耗時:30s。
Xcode生成的
.xcarchive
文件能夠在如下路徑看到:/Users/double/Library/Developer/Xcode/Archives
能夠看出,僅使用xcodebuild的結果和使用Xcode編譯打包的結果是一致的,而且最終的ipa也能夠正常安裝使用。而第一種xcodebuild+xcrun的結果略大些,可是ipa也是能夠正常使用的。這時須要瞭解下他們的區別。
xcodebuild+xcrun和僅xcodebuild的比較
- 使用xcrun打包方式二產生的.xcarchive中的.app
打包生成的.ipa文件大小一樣爲37.3MB,與方式二使用Xcodebuild -exportArchive的結果一致!這樣說明:使用xcrun的打包方法是正常的,和xcodebuild -exportArchive的結果一致,並且.ipa包僅和.app有關。那麼說明,這兩種方式的不一樣僅在於xcodebuild build和xcodebuild archive之間的不一樣。
- 刪除.xcarchive中其餘文件而後
exportArchive
這時命令提示錯誤,可是上面咱們已經得出結論.ipa的生成只和.app有關,因此可能的緣由是,這個
exportArchive
命令會檢查.archive的完整性和正確性,防止生成的.archive不完整或者是僞造的。下面作個實驗看下。命令到底作了什麼
根據命令運行時輸出的內容,看下中間作了什麼
- xcrun -sdk iphoneos PackageApplication -v xxx.app -o xxx.ipa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Packaging application: '/xxx/xxx.app'
Arguments: output=/xxx/xxx.ipa verbose=1
Environment variables:
SDKROOT = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.2.sdk
......
SHELL = /bin/bash
Output directory: '/xxx/xxx.ipa'
Temporary Directory: '/var/folders/21/6s9bb23j0s1343pm7ltnlgpm0000gn/T/taOIiK9AyK' (will NOT be deleted on exit when verbose set)
+ /bin/cp -Rp /xxx/xxx.app /var/folders/21/6s9bb23j0s1343pm7ltnlgpm0000gn/T/taOIiK9AyK/Payload
Program /bin/cp returned 0 : []
### Checking original app
+ /usr/bin/codesign --verify -vvvv /xxx/xxx.app
Program /usr/bin/codesign returned 0 : [/xxx/xxx.app: valid on disk
/xxx/xxx.xcarchive/Products/Applications/xxx.app: satisfies its Designated Requirement
]
Done checking the original app
+ /usr/bin/zip --symlinks --verbose --recurse-paths /Users/double/Desktop/1.ipa .
Program /usr/bin/zip returned 0 : [ adding: Payload/ (in=0) (out=0) (stored 0%)
adding: Payload/xxx.app/ (in=0) (out=0) (stored 0%)
......主要檢查了環境變量,而後驗證簽名,而後壓縮(看到了嗎,竟然是/usr/bin/zip),後面adding的基本都是.nib和.png等的壓縮。看起來.archive只是一種壓縮形式,包含了.app、.dSYM、.plist和其餘一些文件。
這裏的
codesign
工具就是簽名相關的,能夠查看說明:
1
2
3
4
5SYNOPSIS
codesign -s identity [-i identifier] [-r requirements] [-fv] [path ...]
codesign -v [-R requirement] [-v] [path|pid ...]
codesign -d [-v] [path|pid ...]
codesign -h [-v] [pid ...]-s是簽名,-v是驗證。因此能夠在.app生成後再簽名。
- xcodebuild clean
清理工做,根據參數刪除指定的workplace、target、configuration(release或debug) 的中間文件,都是工程目錄下的build文件夾。
- xcodebuild archive
下面是裏面主要的步驟:
- Create product structure 建立.app文件
- CompileC 編譯文件(clang編譯,指定了編譯的SDK版本和指令集)
- Ld
- CreateUniversalBinary (lipo)
- CompileStoryboard (ibtool )
- CompileAssetCatalog (actool )
- ProcessInfoPlistFile (builtin-infoPlistUtility )
- GenerateDSYMFile (dsymutil )
- LinkStoryboards(ibtool )
- Strip
- ProcessProductPackaging (builtin-productPackagingUtility )
- CodeSign (codesign –force –sign)
- Validate (builtin-validationUtility )
總結
呼呼寫了這麼多,終於到總結部分了。這個過程學到了不少東西,腳本成果確實方便了不少,減小了編譯打包過程當中人工監守、人工操做的成本,而且測試和提交到appStore的包都驗證過可用。