http://www.tuicool.com/articles/JR3eIrA 時間 2016-07-12 02:14:05 zackzheng 原文 http://zackzheng.info/2015/12/27/2015-12-27-an-automated-script-for-building-archiving-submission-sending-emails/ 主題 Xcode Shell 現在涉及到編譯打包的工作主要是以下兩個:
提交測試版本給測試同事 提交App Store審核 兩個流程分別是:
修改證書和配置文件,然后「PRoduct -> Archive」編譯打包,之后在自動彈出的 「Organizer」 中進行選擇,根據需要導出 ad hoc enterprise 類型的 ipa 包。等待導出之后再提交到Fir上,等Fir提交完成就需要告知測試同事。整個流程下來一般都要半個多小時,而且需要人工監守操作。 第二個也是差不多,打包完之后需要操作幾個步驟然后上傳到App Store,上傳時間較長,而且中間可能會有錯誤需要處理。上傳后等待蘋果處理二進制包,蘋果處理后上去選擇構建包,點擊提交審核。 所以研究下自動化編譯打包,提高下效率,減少人工操作成本。
主要有兩種實現途徑,AppleScript和Shell腳本, AppleScript 沒怎么研究,網上說是很強大的腳本語言。
下面主要講Shell腳本的實現,網上也有人實現了并托管在 github 上,可以參考下。
https://github.com/webfrogs/xcode_shell
Shell腳本涉及的工具
主要是以下幾個工具:
xcodebuild xcrun altool(提交到App Store使用) fir-cli(上傳到fir時使用) Python的smtplib(之前已經寫過python的發郵件了,所以就直接用沒有用Shell寫。) PlistBuddy xcodebuild和xcrun
xcodebuild 和 xcrun 都是來自 Command Line Tools ,Xcode自帶,如果沒有可以通過以下命令安裝:
xcode-select –install 或者在下面的鏈接下載安裝:
https://developer.apple.com/downloads/
安裝完可在以下路徑看到這兩個工具:
/applications/Xcode.app/Contents/Developer/usr/bin/
xcodebuild 主要是用來編譯,打包成Archive和導出ipa包。
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html
可以執行 xcodebuild -help 查看,主要展示了幾種用法、一些可選項,最后是比較重要的exportOptionsPlist文件的一些可選key,這個文件在后面導出ipa包會用到。
主要下面三個查看的命令比較重要:
-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 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
使用時會提示下面的錯誤:
altool[] * Error: Exception while launching iTunesTransporter: Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application. 建立個軟鏈接可解決(類似于Windows的快捷方式):
ln -s /Applications/Xcode.app/Contents/Applications/Application/ Loader.app/Contents/itms /usr/local/itms fir-cli
安裝時會提示各種權限不允許,可以執行下面命令:
echo ‘gem: –bindir /usr/local/bin’ >> ~/.gemrc sudo ‘gem install fir-cli fir有提供Android Studio、Eclipse、gradle插件,可以看下。
http://fir.im/tools
這是它的github地址,其中講到有對? xcodebuild ?原生指令進行了封裝。
https://github.com/FIRHQ/fir-cli/blob/master/README.md
PlistBuddy
Plist在Mac OSX系統中起著舉足輕重的作用,系統和程序使用Plist文件來存儲自己的安裝/配置/屬性等信息。而PlistBuddy是Mac里一個用于命令行下讀寫plist文件的工具,在/usr/libexec/下??梢酝ㄟ^它讀取或修改plist文件的內容。
這里我僅通過它來獲取內部版本號、外部版本號。在一些文章中見過用來修改plist文件的信息來導出出不同需要的包。
一些概念的區別
Workspace、Project、Scheme、Target的區別。
下面是官方文檔:
https://developer.apple.com/library/ios/featuredarticles/XcodeConcepts/Concept-Targets.html#//apple_ref/doc/uid/TP40009328-CH4-SW1
下面從上往下大概說下,具體看文檔比較好:
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 實現的,比如: xcodebuild -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的范疇就不多講了:
if [ # -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 
就如在Xcode操作「Product -> Clean」。
log_path=”/XXX/XXX” configuration=”Release” xcodebuild clean -configuration “
編譯打包成Archive
就如在Xcode操作「Product -> Archive」
workspaceName=”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 “
如果是workspace就用 -workspace ,就像編譯帶有 CocoaPods 的項目,如果是普通項目則用 -project 。
執行完會生成一個.xcarchive文件和build文件夾如下:
.xcarchive build文件夾 |——.a |——.app |——.app.dSYM |——.swiftmodule文件夾 |——arm.swiftdoc |——arm.swiftmodule |——arm64.swiftdoc |——arm64.swiftmodule 將Archive導出
xcodebuild -exportArchive -archivePath “
然后就會在指定的 exportPath 路徑下生成.ipa文件。
上傳到Fir
firApiToken=”xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx” ipaPath=”/xxx/xxx.ipa” fir publish “
驗證并上傳到App Store
altoolPath=”/Applications/Xcode.app/Contents/Applications/Application/ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool” 
這是成功的結果:
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’
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 Trueexcept Exception, e: print str(e) return Falseif send_mail(sys.argv[1], sys.argv[2]): print “done!” else: print “failed!” 可以賦值給msg[‘CC’]實現抄送,經過測試,抄送的人過多會有一部分不成功,網上查了是這個庫的bug。發送多個人用分號,另外末尾也要用分號。
簡單例子
清理構建目錄:
xcodebuild clean -configuration Release -alltargets 歸檔(其他參數不指定的話,默認用的是.xcworkspace或.xcodeproj文件里的配置)
xcodebuild archive -workspace xxx.xcworkspace -scheme xxx -configuration Release -archivePath ./xxx.xcarchive 導出IPA
xcodebuild -exportArchive -archivePath ./xxx.xcarchive -exportOptionsPlist ./AdHocExportOptions.plist -exportPath ./ 上傳FIR
fir publish ./xxx.ipa -T xxxxxx 提交AppStore
/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 發郵件
python sendEmail.py “郵件內容” “用戶名” “密碼” 對比實驗
為了了解一些區別,我做了幾個對比。我這里定義下三種方式,方便下面說明。
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 Packaging 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 : []
這里的 codesign 工具就是簽名相關的,可以查看說明:
SYNOPSIS 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的包都驗證過可用。
新聞熱點
疑難解答