2017年4月18日 星期二

[iOS]從零開始寫個自動打包IPA腳本

[iOS]從零開始寫個自動打包IPA腳本

背景
新項目這邊每次版本移交,
給測試都是直接xcode挨個拿手機安裝的流程。
一次兩次還好,天天這麼搞,而且每次一大波手機扔過來,瞬間覺得,自己好像是個疫苗注射員似的,順時給每個手機打一針。
biu!
NEXT ... 
BIU!
NEXT ... 
BIU!
NEXT ... 
... 
😢 
... 
於是..我崩潰了..
好吧,
是在是看不下去了,於是準備寫個腳本以後自己打包,測試直接下載就好了。

思路

那麼怎麼實現這麼一個事情呢?
其實上一家公司,做個類似的東西,雖然不是我做的,但是大體流程還是知道的

  1. 寫個bash腳本,執行自動打包iOS版本,到指定的目錄(有條件的公司,可以自己搭個小服務器,這樣誰都可以隨時隨地的打包)
  2. 將打包好的文件上傳到fir.im(當然上傳到自己公司的服務器或者任何地方都行,只是fir.im我一直用,覺得比較方便)
  3. 開發一個內部使用的類似APPStore,上面放著自己公司的所有APP,每次有更新的時候,測試童鞋直接通過這個自己下載新APP就可以了

開始做

準備背景知識

其實當我們Xcode點擊了構建或者運行comand + R之後,Xcode自己執行的命令是xcodebuild這條命令。
然後,編譯好之後,怎么生產ipa包?
用xcrun命令
話不多說,先上手:
打開終端,cd到你的工程位置,然後先試一下xcodebuild命令,
//xcrun
chengpoleness@polen xcodebuild_iPA $ xcrun --version
xcrun version 29.

//xcodebuild
chengpoleness@polen ios (develop) $ xcodebuild -version
Xcode 7.3
Build version 7D175
看上去很簡單,大概了解了一下,就開始用:
chengpoleness@polen ios (develop) $ xcodebuild
2016-05-02 13:05:04.623 xcodebuild[1015:16272] [MT] PluginLoading: Required plug-in compatibility UUID ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/XcodeColors.xcplugin' not present in DVTPlugInCompatibilityUUIDs
2016-05-02 13:05:04.625 xcodebuild[1015:16272] [MT] PluginLoading: Required plug-in compatibility UUID ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/OMColorSense.xcplugin' not present in DVTPlugInCompatibilityUUIDs
=== BUILD TARGET xxx OF PROJECT xxx WITH THE DEFAULT CONFIGURATION (Release) ===

Check dependencies

Write auxiliary files
write-file /Users/chengpoleness/Documents/code/xxx/ios/build/xxx.build/Release-iphoneos/xxx.build/xxx.hmap
write-file /Users/chengpoleness/Documents/code/xxx/ios/build/xxx.build/Release-iphoneos/xxx.build/xxx-own-target-headers.hmap
write-file /Users/chengpoleness/Documents/code/xxx/ios/build/xxx.build/Release-iphoneos/xxx.build/Script-492B764475E022A63FB67F55.sh
接著滿屏的快速滾動,可以看到鐺依次在編輯各個文件,登錄大概十幾秒閃屏,然後失敗了!!!💔
ld: library not found for -lPods
clang: error: linker command failed with exit code 1 (use -v to see invocation)

** BUILD FAILED **


The following build commands failed:
    Ld build/xxx.build/Release-iphoneos/xxx.build/Objects-normal/armv7/xxx normal armv7
    Ld build/xxx.build/Release-iphoneos/xxx.build/Objects-normal/arm64/xxx normal arm64
(2 failures)
這個就是找不到pods了,
為什麼會這樣?
自己直接用Xcode親自產品檔案了一遍,okey啊,沒出現任何問題!
那是為什麼呢?
各種goole,stackoveflow,但是基本說的是不要只打開項目,而是打開工作區,其實這個是針對直接用xcode進行編譯或者打包的時候,出現問題的解決方案,
然後嘗試了其他解決方案,如ONLY_ACTIVE_ARCHS = NO等,都無效。
最終在下面這篇文章裡,找到了靈感:
iOS集成測試與Appium
其實解決方案很簡單,執行xcodebuild需要指定你所需要對應的工作空間和方案
所以命令如下:
 xcodebuild -workspace /Users/chengpoleness/Documents/code/xxx/ios/xxx.xcworkspace -scheme xxx
執行前,先查看下列列表,這個可以知道xcodebuild命令下對應的參數需要填寫的內容
chengpoleness@polen ios (develop) $ xcodebuild -list
2016-05-02 15:24:26.656 xcodebuild[16535:154176] [MT] PluginLoading: Required plug-in compatibility UUID ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/XcodeColors.xcplugin' not present in DVTPlugInCompatibilityUUIDs
2016-05-02 15:24:26.661 xcodebuild[16535:154176] [MT] PluginLoading: Required plug-in compatibility UUID ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/OMColorSense.xcplugin' not present in DVTPlugInCompatibilityUUIDs
Information about project "xxx":
    Targets:
        xxx
        xxxTests

    Build Configurations:
        Debug
        Release

    If no build configuration is specified and -scheme is not passed then "Release" is used.

    Schemes:
        xxx
然後,成功了,如下:
chengpoleness@polen ios (develop) $ xcodebuild -workspace /Users/chengpoleness/Documents/code/xxx/ios/xxx.xcworkspace -scheme xxx
2016-05-02 15:26:29.627 xcodebuild[16552:156501] [MT] PluginLoading: Required plug-in compatibility UUID ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/XcodeColors.xcplugin' not present in DVTPlugInCompatibilityUUIDs
2016-05-02 15:26:29.629 xcodebuild[16552:156501] [MT] PluginLoading: Required plug-in compatibility UUID ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/OMColorSense.xcplugin' not present in DVTPlugInCompatibilityUUIDs
=== BUILD TARGET Pods OF PROJECT Pods WITH CONFIGURATION Debug ===

Check dependencies

=== BUILD TARGET xxx OF PROJECT xxx WITH CONFIGURATION Debug ===

Check dependencies

PhaseScriptExecution Check\ Pods\ Manifest.lock /Users/chengpoleness/Library/Developer/Xcode/DerivedData/xxx-djwnuzytnxhekrhjhypwxdqrxtet/Build/Intermediates/xxx.build/Debug-iphoneos/xxx.build/Script-492B764475E022A63FB67F55.sh

...
...
...
Validate /Users/chengpoleness/Library/Developer/Xcode/DerivedData/xxx-djwnuzytnxhekrhjhypwxdqrxtet/Build/Products/Debug-iphoneos/xxx.app
    cd /Users/chengpoleness/Documents/code/xxx/ios
    export PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/Users/chengpoleness/.rvm/gems/ruby-2.0.0-p598/bin:/Users/chengpoleness/.rvm/gems/ruby-2.0.0-p598@global/bin:/Users/chengpoleness/.rvm/rubies/ruby-2.0.0-p598/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/chengpoleness/.rvm/bin:/usr/local/marven/apache-maven-3.3.9/bin:/Users/chengpoleness/Library/Android/sdk/platform-tools:/Users/chengpoleness/Library/Android/sdk/tools"
    export PRODUCT_TYPE=com.apple.product-type.application
    builtin-validationUtility /Users/chengpoleness/Library/Developer/Xcode/DerivedData/xxx-djwnuzytnxhekrhjhypwxdqrxtet/Build/Products/Debug-iphoneos/xxx.app

** BUILD SUCCEEDED **

寫腳本

(挑重點地說)大致流程就是:
2.1先git指令,拉到最新的分支
這個很簡單,沒什麼好說的了。
git clean -df
git reset --hard
git fetch
git checkout $BRANCHNAME
git pull --rebase origin $BRANCHNAME
echo "即将打包的分支log如下:"
git log -5
pod update --verbose --no-repo-update
不過這幾個指令,帶有刪除性,比如git clean -df,git reset這種,所以用的時候要謹慎,如果你把腳本放在自己的工程目錄下,就可能一不小心腳本就被刪除掉了(親歷者說...😢)
所以最終版本改成了
# git update
git checkout $BRANCHNAME
if [ $? -ne 0 ]; then
    exit 1
fi

git pull 
#pod update --verbose --no-repo-update
if [ $? -ne 0 ]; then
     exit 1
fi
只保留了checkout和pull,pod沒有用,是因為我們這邊除了googleMap其他全是自造輪子,所以...並不需要😄。
2.2 xcodebuild進行編譯
xcodebuild \
-workspace $SORCEPATH/xxx.xcworkspace \
-scheme $SCHEMENAMEPLQ \
-configuration Debug \
CODE_SIGN_IDENTITY="iPhone Developer: xxxx(T8TXPM5FC7)"  \
PROVISIONING_PROFILE="5110ff0a-b845-475e-a4xxxxxxxxxx" \
clean \
build \
-derivedDataPath $IPAPATH/$BRANCHNAME/$DATE
備註:
我這邊因為是研發階段,所以打包是調試包,如果是要打發包,配置中修改成發布,以及其他對應的修改下就行。

2.3成功後xcrun打包成ipa包

xcrun -sdk iphoneos PackageApplication \
        -v $IPAPATH/Build/Products/Debug-iphoneos/$SCHEMENAME.app \
        -o $IPAPATH/$IPANAME
最終腳本如下:

屏幕快照稿04下午8.20.47.png

打包的文件命名我是按照時間來命名的,這樣也方便找,尤其應對於可能分分鐘就要突然來打包的測試團隊。

生成ipa包,上傳到fir.im

3.1 fir比較好,有命令可以直接上傳,但是需先安裝fir-cli
安裝過程如下:
chengpoleness@polen xcodebuild_iPA $ gem install fir-cli
Fetching: chunky_png-1.3.5.gem (100%)
Successfully installed chunky_png-1.3.5
Fetching: rqrcode-0.10.1.gem (100%)
Successfully installed rqrcode-0.10.1
...
...
        ______________        ________    ____
       / ____/  _/ __ \      / ____/ /   /  _/
      / /_   / // /_/ /_____/ /   / /    / /
     / __/ _/ // _, _/_____/ /___/ /____/ /
    /_/   /___/_/ |_|      \____/_____/___/
...
...
Installing ri documentation for thor-0.19.1
Parsing documentation for fir-cli-1.4.9
unable to convert "\xCE" from ASCII-8BIT to UTF-8 for lib/fir/util/parser/bin/pngcrush, skipping
Installing ri documentation for fir-cli-1.4.9
13 gems installed
安裝如果遇到問題,可以到這裡查看原因
3.2使用fir指令,上傳我們的ipa包
非常簡單
    fir login -T c525718a775b954882xxxxxxxx       # fir.im token
    fir publish $IPAPATH/Develop/xxx.ipa
3.3擴展
另外,也可以直接用fir指令,進行打包或者編譯,這個具體也沒研究,其實就對對xcodebuild做了個封裝,本質上還是執行那些指令,這裡不再詳述了。
chengpoleness@polen xcodebuild_iPA $ fir
Commands:
  fir build_apk BUILD_DIR                       # Build Android app (alias: `ba`).
  fir build_ipa BUILD_DIR [options] [settings]  # Build iOS app (alias: `bi`).
  fir help                                      # Describe available commands or one specific command (aliase...
  fir info APP_FILE_PATH                        # Show iOS/Android app info, support ipa/apk file (aliases: `...
  fir login                                     # Login fir.im (aliases: `l`).
  fir mapping MAPPING_FILE_PATH                 # Upload app mapping file to BugHD.com (aliases: `m`).
  fir me                                        # Show current user info if user is logined.
  fir publish APP_FILE_PATH                     # Publish iOS/Android app to fir.im, support ipa/apk file (al...
  fir upgrade                                   # Upgrade fir-cli and quit (aliases: `u`).
  fir version                                   # Show fir-cli version number and quit (aliases: `v`).

Options:
  -T, [--token=TOKEN]              # User's API Token at fir.im
  -L, [--logfile=LOGFILE]          # Path to writable logfile
  -V, [--verbose], [--no-verbose]  # Show verbose
                                   # Default: true
  -q, [--quiet], [--no-quiet]      # Silence commands
  -h, [--help], [--no-help]        # Show this help message and quit

測試一下

4.1執行.sh文件
chengpoleness@polen xcodebuild_iPA $ sh xcodebuild.sh
要是有比較懶的同學,直接把.sh文件,拖到命令行里面也行
4.2過程與結果
編譯過程的輸出信息太多,刪減了下,大概看一下流程:
chengpoleness@polen xcodebuild_iPA $ sh xcodebuild.sh 

HEAD is now at 30865e1 version 3.5.0, build 29
Already on 'develop'
...
...
=== CLEAN TARGET Pods OF PROJECT Pods WITH CONFIGURATION Debug ===
=== CLEAN TARGET xxx OF PROJECT xxx WITH CONFIGURATION Debug ===
...
...
** BUILD SUCCEEDED **

/* '以上是编译成功,接下来打包ipa'  */

...
Packaging application: '/Users/chengpoleness/Documents/code/xxx/buildiPA/develop/20160502_1801/Build/Products/Debug-iphoneos/xxx.app'
Arguments: output=/Users/chengpoleness/Documents/code/xxx/buildiPA/develop/20160502_1801/1xxx_20160502_1801.ipa  verbose=1  
Environment variables:
_system_name = OSX
...
### Checking original app
...
Done checking the original app
...
  adding: Payload/xxxx.app/xxxViewGpsOverlay.nib    (in=4889) (out=2502) (deflated 49%)
total bytes=53793797, compressed=23588024 -> 56% savings
]
Results at 'xxxxxxxx/Documents/code/xxx/buildiPA/develop/20160502_1801/xxx_20160502_1801.ipa' 

/* '以上是打包成功,接下来是上传fir.im' */

I, [2016-05-02T18:03:56.614082 #47670]  INFO -- : Login succeed, current  user's email: xxxx
I, [2016-05-02T18:03:56.614215 #47670]  INFO -- : 
I, [2016-05-02T18:03:57.766434 #47674]  INFO -- : Publishing app via chenglong<chenglong@squarevalleytech.com>.......
I, [2016-05-02T18:03:57.766570 #47674]  INFO -- : ✈ -------------------------------------------- ✈
I, [2016-05-02T18:03:59.813783 #47674]  INFO -- : Fetching com.osmgolf.xxx@fir.im uploading info......
I, [2016-05-02T18:03:59.813868 #47674]  INFO -- : Uploading app: xxx-3.5.0(Build 29)
I, [2016-05-02T18:04:00.562896 #47674]  INFO -- : Uploading app icon......
I, [2016-05-02T18:04:00.563029 #47674]  INFO -- : Converting app's icon......
I, [2016-05-02T18:04:01.715439 #47674]  INFO -- : Uploading app binary......
I, [2016-05-02T18:04:16.091891 #47674]  INFO -- : Updating devices info......
I, [2016-05-02T18:04:16.244710 #47674]  INFO -- : Updating app info......
I, [2016-05-02T18:04:16.514628 #47674]  INFO -- : ✈ -------------------------------------------- ✈
I, [2016-05-02T18:04:16.514862 #47674]  INFO -- : Fetch app info from fir.im
I, [2016-05-02T18:04:17.019304 #47674]  INFO -- : Published succeed: http://fir.im/xxxx
I, [2016-05-02T18:04:17.019409 #47674]  INFO -- : 

恭喜!!!上传fir.im成功!
OK,測試通過。

5.開發內部版“AppStore”

這個就這裡
不詳了了,因為太簡單。寫個tableview,把自己的app的鏈接放上去,每次有新版本了,通過這個App就可以下載,之前做過截圖如下:
至此,自動打包IPA的相關工作開發完成,可以投入使用了...

6擴展-Debug / Release

6.1背景:
目前的腳本打包都是默認打包包的,但是如果我打版包包怎麼辦?如果我調試包不同網絡域名想切換怎麼辦?
6.2調試/釋放
Okey,我們依次來解決這些問題:
其實很簡單,xcodebuild指令有個個配置,我們使用這個決定是打Debug還是發布
那個打出來的調試和發布有什麼區別呢?
這個相當於編譯的時候是按照調試模式或者發布模式來打包,從編譯角度來說,區別挺大的,比如Debug會在某些堆棧環節預留更多的空間(我之前就遇到過同樣的代碼,但是Debug沒問題,發布會有bug的情況,如果不知道這個的話,很難查出來或者理解這些bug,具體細節有空專門細說吧,這裡
不詳了了)所以,解決方案:定義一個CONGRUATION,根據傳入的參數,決定打Debug還是打發行
# $1表示传入的第一个参数,启动脚本传入Debug或者Release就可以
CONGRUATION=$1

# 在xcodebuild和xrun的地方都使用$CONGRUATION即可
xcodebuild \
-workspace $SOURCEPATH/xxx.xcworkspace \
-scheme $SCHEMENAME \
-configuration $CONGRUATION \
-derivedDataPath $IPAPATH \


xcrun -sdk iphoneos PackageApplication \
        -v $IPAPATH/Build/Products/$CONGRUATION-iphoneos/$SCHEMENAME.app \
        -o $IPAPATH/$IPANAME
6.2不同的主機域名切換
這個其實解決方案很多:
方案A:
代碼裡寫個調試頁面,裡面設置可以切換不同的域名即可,
以前公司的APP就是這麼做的,因為我們測量環境非常多,比如:
QA01,QA02,QA03 ... QA10 ... 
所以直接在APP裡就可以進行切換不同域名。
polen提醒:
1>切換後一定要讓用戶退出,避免發生不必要的邏輯混亂
2>需要設置個宏開關,確保發布版本用戶100%走線上域名,Debug版本可以隨意加各種邏輯
判斷務必避免如果代碼不建壯,發生線上用戶誤入測試環境的悲劇(當然,這個肯定屬於bug了,嚴格是不應該發生的了,但是難免意外,沒有東西是100%絕對的,這裡只是提醒.. 。)
方案B:
如果你們的測試環境沒那麼多,可能就2-3個,可以在打包的時候,通過腳本決定用哪個環境,具體怎麼做:
CTO說怎麼樣能讓腳本給代碼傳個值進入,然後代碼在編譯的時候根據這個值決定用哪個測試環境。
思考了很多方法,包括使用腳本去改代(硬改,用sed進入.m文件修改,但這樣太危險,而且很不友好)
後來學習到使用PREPROCESSOR_DEFINITIONS,就是xcode在打包前會先從一個配置中讀取一些配置信息,這些在xcode-building裡面可以直接設置,自然也可以通過xcodebuild指定穿進去,傳入的就是GCC_PREPROCESSOR_DEFINITIONS 
所以就是:
設定一個SERVER_TYPE宏定義,如果#ifdef SERVER_TYPE的話,使用傳入的參數,否則使用代碼自定義的邏輯
# $serverType是启动脚本传入的参数值
if [ $CONGRUATION = Debug ]; then
    PREPROCESSOR_DEFINITIONS="COCOAPODS=1 SERVER_TYPE=$serverType DEBUG=1"
else
    PREPROCESSOR_DEFINITIONS="COCOAPODS=1 SERVER_TYPE=$serverType"
fi

xcodebuild \
-workspace $SOURCEPATH/xxx.xcworkspace \
-scheme $SCHEMENAME \
GCC_PREPROCESSOR_DEFINITIONS="$PREPROCESSOR_DEFINITIONS" \
-configuration $CONGRUATION \
-derivedDataPath $IPAPATH \
具體代碼中如何使用:
//HOST_TYPE决定最终的域名
#ifdef SERVER_TYPE
#define HOST_TYPE SERVER_TYPE
#else
#ifdef DEBUG
  #define HOST_TYPE HOST_TYPE_CANARY
  #else
  #define HOST_TYPE HOST_TYPE_PRODUCTION
  #endif
#endif
okey,以上就解決了Debug和Release相關的問題

遇到一些疑難雜症

br>
7.1 SDK不匹配導致編譯失敗
7.1.1背景
下午(2016.8.2)正在忙,突然測試同學跑過來,大喊:“不好了,不好了...” 
我以為要說颱風來了,(做好了可以下班的準備..😄)
結果,來了句:
“打包腳本失敗了,失敗了!!” 
我擦,啥情況,用了幾個月了好好的,突然就失敗了...
7.1.1原因
後來確認下來是這麼個情況,iOS10發布了,之前一直拖延,最近不是很忙了,於是有同事下載了xcode8-beta3和iOS10的安裝配置文件。
然而iOS10的SDK更新,導致我們有個代碼邏輯無法實現,於是修了一下,然後繼續交給測試同學打包測試,結果就打包失敗了,我看了下失敗原因:
error: 
      property 'firstAnchor' not found on object of type 'NSLayoutConstraint *'
      object_setIvar(constraint.firstAnchor, referenceItem, view);

屏幕快照2016-08-02下午5.44.54.png

然後查了下代碼,代碼邏輯應該是沒問題的。而找不到這個屬性是因為這個代碼使用的iOS10的SDK,用舊版本自然是找不到,然後問了下測試,直接用xcode編譯可以那麼
分析下來就應該是路徑的問題了,
去她位於上看了下,(因為我自己沒裝xcode8,所以自己沒法調試)一看發現問題了,她目前是兩個xcode都在,但一個叫Xcode,一個叫Xcode-beta,腳本跑起來的時候,SDK肯定還跑得是以前的xcode的啊,自然編不過,於是改了一下名字,把原來的Xcode改為Xcode7,新的Xcode-beta改為Xcode。
然後,問題解決!😄

如果後續腳本有擴展,我會繼續更新此文。

注意:有些地方應該是iOS,而非ios,懶得改了,就這樣吧,嗯。

沒有留言:

張貼留言

編程的智慧

編程的智慧 作者   正義的花生   關注 2015.11.22 15:28*  字數17768  閱讀94218  評論0  喜歡2660 編程是一種創造性的工作,是一門藝術。 精通任何一門藝術,都需要很多的練習和領悟,所以這裡提出的“智慧”,並不是號稱...