iOS —— 兩套自動打包指令碼

Hsusue發表於2018-11-16

前言

專案每次更新要打十幾個包,廣發說傳承下來的自動打包指令碼突然不好使了,現在每次打包上傳都要弄到凌晨,以後改名叫稀發好了。看著他越來越禿的頭,我這父愛就藏匿不住,必須要幫他分擔(當然是被逼的)。

這個專案十幾個包,不同圖示、App名字,手動打包不但慢,而且重複枯燥的工作出錯概率也指數上升。所以如果你的專案也要打幾個包的話,花時間學習自動打包還是值得的。如果只用打一個包,雖然自動打包時間會快點,但學習成本還是在那,見仁見智了。

重點是,利用重簽名修改自定義的圖片、App名字等,不用重複編譯,能極大縮短打包時間。

證書知識

網上關於證書配置不乏好文章,一搜一大堆,但大多都沒有介紹分別有什麼用。

  • CSR:Certificate Singing Request,證書籤名請求檔案。

包含電腦的資訊。所以建立時不需要填任何和釋出等有關的資訊。

  • Certificates證書:釋出者證書。Apple Develop的ID 對某部電腦的授權證書。

電腦擁有這個證書後,有權對該Apple Developer的ID下所有App進行真機測試、打包、釋出。注意這裡,並未指定App,換句話說,和App無關。

包含電腦的資訊和Apple Developer的資訊

  • CSR和Certificates的聯絡

上面提到了Certificates包含了電腦的資訊,這個資訊來自於CSR。所以在建立Certificates時,需要提交CSR。

  • Certificates匯出p12檔案

上面提到,擁有此證書才有權做那些事。如果另一部電腦想釋出,也需要證書。如果又建立一個新證書也能解決,但一般一個開發者帳號建立一個釋出證書就夠了,而且蘋果對這證書數量有限制。這時候匯出p12檔案,相當於拷貝了一份證書(不佔蘋果限制數量)。給另一部電腦安裝後,另一部電腦就有權了。

到這裡,筆者就迷惑了CSR包含了電腦的資訊,然後又能拷貝給其他電腦用,有什麼用呢。希望大佬能解惑。


上面提到的與App無直接關係,以下才與App建立起聯絡。

  • App IDs

在該Apple Developer下注冊App。這裡要填Name和Bundle ID。這裡的Bundle ID將在Xcode中匹配證書。

之前聽李大神說,新建一個新專案,然後改為公司專案中的Bundle ID,App也能真機測試,就是因為通過Bundle ID來匹配。

  • Provisioning Profiles:PP檔案。.mobileprovision字尾。

包含appID,開發者證書。

建立時,開發版和釋出版有區別。前者要選裝置,後者不用。

建立下載後,Xcode就會自動匹配,當然也可以手動匹配。

  • 做專案時,如果不是你負責的,別人通常會發你.p12和.mobileprovision檔案。前者授權你的電腦Apple Developer的ID許可權,後者授權App許可權。

自動打包

本文采用的是xcodebuild。

說起自動打包就腦殼疼。一直對命令列有恐懼,只能一步一步來了。先了解命令列命令,然後新建一個專案熟悉一下。

xcodebuild 簡介

xcodebuild 是xcode提供的打包專案或者工程的命令。

輸入man xcodebuild可以檢視文件。

這裡引用大佬的總結。

iOS —— 兩套自動打包指令碼

輸入xcodebuild -h可以檢視幫助。

Usage: xcodebuild [-project <projectname>] [[-target <targetname>]...|-alltargets] [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...
       xcodebuild [-project <projectname>] -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...
       xcodebuild -workspace <workspacename> -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...
       xcodebuild -version [-sdk [<sdkfullpath>|<sdkname>] [<infoitem>] ]
       xcodebuild -list [[-project <projectname>]|[-workspace <workspacename>]] [-json]
       xcodebuild -showsdks
       xcodebuild -exportArchive -archivePath <xcarchivepath> -exportPath <destinationpath> -exportOptionsPlist <plistpath>
       xcodebuild -exportLocalizations -localizationPath <path> -project <projectname> [-exportLanguage <targetlanguage>...]
       xcodebuild -importLocalizations -localizationPath <path> -project <projectname>
複製程式碼

xcodebuild嘗試

大家這時候可以新建一個專案Test。然後在Xcode中,把公司專案(有證書)的Bundle Identifier複製過來,這時候你會發現證書匹配通過了。(如果你沒有證書,那看回證書知識,弄好再往下看)有了這個專案,就可以感受這些命令了。

iOS —— 兩套自動打包指令碼

打包方法一

筆者認為這方法不如方法二,而且蘋果也廢棄了其中一個工具。

先編譯出.app,然後轉成.ipa。

終端進專案資料夾後,先來試試第一條編譯命令。

xcodebuild -project Test.xcodeproj -target Test -configuration Release

iOS —— 兩套自動打包指令碼

不出意外,會列印以上文字,包括簽名身份和配置檔案。資料夾下有Test/build/Release-iphoneos/Test.app.dSYM

iOS —— 兩套自動打包指令碼

這時我們還要用xcrun命令把其匯出為ipa檔案,(但xcrun在Xcode8.3廢棄了,如果還要用看這篇 xcrun: error: unable to find utility "PackageApplication", not a developer tool or in PATH

處理好後,執行下面命令轉成ipa

xcrun -sdk iphoneos PackageApplication build/Release-iphoneos/Test.app -o ~/Desktop/Test.ipa
複製程式碼

打包方法二

先用archive,然後exprotArchive,類似於手動打包的介面步驟。

(先執行xcodebuild -list,記住Scheme)

終端進入專案資料夾後,執行xcodebuild archive -scheme Test -archivePath ~/Desktop/Test.xcarchive,注意這裡不打scheme會失敗。

筆者瞎猜以上和Xcode內打包對應著這個介面。

iOS —— 兩套自動打包指令碼

iOS —— 兩套自動打包指令碼

從上圖可看出,因為不同途徑釋出,所以要各自配置ExportOptions.plist。也就是用於xcodebuild -exportArchive -archivePath <xcarchivepath> -exportPath <destinationpath> -exportOptionsPlist <plistpath>中最後一個。

有兩種途徑配置。

  • 通過手動打包,複製過去。
  • 自己新建一個plist檔案,加入相關鍵值對。(前提是你知道怎麼加)。
ExportOptions.plist檔案中有以下欄位,配置如下:

method:字串,為打包的型別,分為app-store,ad-hoc,enterprise和development,根據自己實際打包情況填寫。

provisioningProfiles:字典,Xcode9需要,鍵值對為{bundleid:描述檔名},描述檔名最好使用其對應的UUID。

signingCertificate:證書型別,開發環境為iPhone Developer,生產環境為iPhone Distribution。

signingStyle:自動還是手動(manual與automatic),填寫manual即可。

stripSwiftSymbols:填寫為YES。

teamID:為開團隊ID,在鑰匙串中點選證書詳情可以檢視到。

uploadBitcode:為YES即可。

uploadSymbols:為YES即可。
複製程式碼

筆者還是建議手動打包,複製過去再做修改(以後其他專案只要稍微改動就能用,不用再次手動打包後複製)。因為通過圖形介面,能更好理解這些欄位的意義以及對應的操作。

筆者選取了Enterprise的截圖。其他因為筆者沒有證書,就靠各位嘗試了。

iOS —— 兩套自動打包指令碼

然後終端中接著執行

xcodebuild -exportArchive -archivePath ~/Desktop/Test.xcarchive -exportPath ~/Desktop/Test.api -exportOptionsPlist ./ExportOptions.plist
複製程式碼

iOS —— 兩套自動打包指令碼

xcodebuild學到這就會打包了,其他不常用的方法,網上學習資源少,筆者也不想探究了。下面進入打包指令碼。


自動打包指令碼

筆者參考師兄@Tsui_YuenHong的文章關於 iOS 批量打包的總結,自己設定了一份指令碼,用了蘋果建議的新方法以及補充了一些師兄指令碼的不足點。

先說明一下筆者的悲慘遭遇。筆者以前也打過包上傳App-Store,整個過程愉愉快快。但是這專案,一共有20多個包,更新一次版本經常動不動就打十多個包。這樣打下來,頭髮就沒了。由於程式碼基本是一樣的,所以利用重簽名,能極大程度縮短打包時間。

筆者的專案打包有以下需求。

簡單點說就是針對不同的使用者定製相應的自定義圖示、功能等。

  1. 可以替換 bundle 資訊
  2. 替換音訊圖片資源
  3. 可以執行不同程式碼
  4. 生成相應的plist檔案
  5. 上傳到蒲公英分發平臺

思路:

  1. 先編譯出.app檔案。
  2. 使用命令 defaults write 來修改專案中的 plist 檔案,來達到修改 bundleName/bundleDisplayName等鍵值對,實現自定義App名字、展示名字功能,並且生成相應的plist檔案。
  3. 使用cp命令來替換圖片資源。
  4. 重簽名。
  5. 匯出ipa。

筆者遇到的問題:

切換圖示的坑

先了解官方建議圖示尺寸。當然其他尺寸,App圖示也會接受,自動適應。

iOS —— 兩套自動打包指令碼

方案一(未解決)

iOS —— 兩套自動打包指令碼

  • 筆者嘗試用這種方案。原理就是用同名的圖片覆蓋包中的圖片,重簽名,達到修改圖示的目的。但是包內圖片覆蓋以後,安裝下來圖示卻沒有改變(可能要修改Info.plist中的Icon files (iOS 5)(然而這個欄位就是AppIcon???不知道底層還有什麼配置),筆者就不繼續嘗試了)。

方案二

iOS —— 兩套自動打包指令碼

  • 此方法要把圖示放在專案中,不要放在xcassets中。

iOS —— 兩套自動打包指令碼

  • AppIcon的名字一定要對應上!因為後面命令列cp時要全部替換,否則難以預測會出現什麼問題,就像下圖。(筆者的專案中,只弄了60@2x和60@3x的圖。)

    iOS —— 兩套自動打包指令碼

  • 打包出來後,AppIcon圖示在.app檔案目錄中,不在BundleResources中。

關於可以執行不同程式碼。

iOS —— 兩套自動打包指令碼

重簽名

建議大家先看這篇文章。 iOS應用程式的重簽名(打包)

弄出來entitlements.plist,重簽名要用到。


題外話

筆者重簽名還是遇到了坑,打包出來的軟體下載時進度條轉了一圈,但無法最終完成。查了一段時間,才知道原因是重簽名不成功。所以說指令碼要重簽名失敗必須終止。

iOS —— 兩套自動打包指令碼

附帶一篇iTunes降級文章,,PP助手好像沒更新還是什麼,只能用舊版iTunes來除錯吧,真是累啊。將 iTunes 降回 12.6.3 可下載應用安裝包版本教程【Windows | Mac】


指令碼

師兄的指令碼一(執行時間256s)比筆者的指令碼二(執行時間306s)快。

但指令碼一採用的是蘋果廢棄的方法,以後出問題就試試指令碼二吧。

兩種指令碼不同之處在於:指令碼一先出來.app,後出來.ipa;指令碼二先出來Archive包(包裡有.app),後出來.ipa。

  • 指令碼一

準備好Entitlements.plist(本文有獲取教程,Cmd+f搜"重簽名要用到")。並且確保本機能執行xcrun(本文Cmd+f搜"在Xcode8.3廢棄")。

編譯後用xcrun匯出ipa,最後重簽名。

指令碼在師兄的文章關於 iOS 批量打包的總結裡貼出來了。

筆者在用該指令碼時遇到圖示更換失敗的問題,和指令碼無關,與專案配置有關,解決方法看回上面。

但筆者認為該指令碼存在一些問題的。例如重簽名等操作失敗後未終止指令碼,導致筆者上傳到蒲公英後下載失敗一頭霧水。這問題很嚴重。不過只要適當判斷,加個exit就能解決。

  • 指令碼二

準備好Entitlements.plist(本文有獲取教程,Cmd+f搜"重簽名要用到")和ExportOptions.plistist``(本文有獲取教程,Cmd+f搜"有兩種途徑配置")。

先用archive,然後exprotArchive(類似於手動打包流程),最後重簽名。

下面貼出指令碼,注意裡面很多地方筆者用Project代替了。

# 1.Configuration Info

# 專案路徑 需修改
projectDir="你的專案路徑"

# 打包生成路徑 建議桌面
ipaPath="ipa生成路徑"

# 圖示路徑 需修改
iconPath="圖示路徑"

# 以下檔案用於重簽名生成ipa,需修改
Entitlements=$ipaPath/Entitlements.plist
ExportOptions=$ipaPath/ExportOptions.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" "Url中的AppName"(因為AppName為中文,此處用英文)
#Schemes:
#        1.app1 app1Icon app1UrlName
#        2.app2 app2Icon app2UrlName
#        3.app3 app3Icon app3UrlName

# --------------------------------------------------------------------------------------- #

# 打包個數
appPackNumLength=${#appPackNum[*]}

appInfos=(
          "app1" "app1Icon" "app1UrlName"
          "app2" "app2Icon" "app2UrlName"
          "app3" "app3Icon" "app3UrlName"
          )

appInfosLength=${#appInfos[*]}

# Scheme Name
schemeName="xx"

# 開始時間
beginTime=`date +%s`

# 生成 xcarchive 路徑,建議桌面
mkdir ${ipaPath}/Payload
rm -rf ${ipaPath}/Payload/*
buildDir="${ipaPath}/Payload"

# 使用的時候要關掉自動簽名 改為手動簽名
# Build 生成 xcarchive
xcodebuild archive -workspace ${projectDir}/YouXiaoYun.xcworkspace -scheme ${schemeName} -archivePath ${buildDir}/Project.xcarchive
#
if [[ $? = 0 ]]; then # $? 表示上一條命令返回值。如果上一條命令成功執行,返回0,否則返回1.
    echo "\033[31m 編譯成功\n \033[0m"
else
    echo "\033[31m 編譯失敗\n \033[0m"
    exit 0
fi

# 建立打包目錄
mkdir ${ipaPath}/AllPack

# 本地存放全部 IPA 的路徑
allIPAPackPath="${ipaPath}/allPack"

# 以下二選一
# 1.----全部打包----
#for (( i=0; i<appInfosLength; i+=3 )); do

# 2.----自定義打包----
for (( j=0; j<$appPackNumLength; j++)); do i=`expr ${appPackNum[$j]} - 1` i=`expr $i \* 3`

# App Bundle Name (CFBundleName)
appName=${appInfos[${i}]}

# App DisPlay Name
appDisplayName=${appInfos[${i}]}

# App Icon Name
appIconName=${appInfos[$i+1]}

# App Download Name
appDownloadName=${appInfos[$i+2]}

# 建立不同 app ipa 目錄
mkdir $allIPAPackPath/$appName
rm -rf $allIPAPackPath/$appName/*

echo "\033[31m appName:$appName appIconName:$appIconName appDownloadName:$appDownloadName\n \033[0m"

# 將對應的 icon 複製到需要修改的 app 的目錄下
cp -Rf $iconPath/$appName/* ${buildDir}/Project.xcarchive/Products/Applications/Project.app

if [[ $? = 0 ]]; then
 echo "\033[31m $appName 修改 icon 成功\033[0m"
else
 echo "\033[31m $appName 修改 icon 失敗\033[0m"
 exit 0
fi

# 修改 Plist
defaults write $ipaPath/Payload/xx.app/info.plist "CFBundleName" $appName
defaults write $ipaPath/Payload/xx.app/info.plist "CFBundleDisplayName" $appDisplayName

if [[ $? = 0 ]]; then
  echo "\033[31m $appName 修改 Plist 成功\033[0m"
else
  echo "\033[31m $appName 修改 Plist 失敗\033[0m"
  exit 0
fi

# 重簽名
xattr -cr ${buildDir}/Project.xcarchive/Products/Applications/Project.app
codesign -f -s "xx co., LTD" --entitlements $Entitlements ${buildDir}/Project.xcarchive/Products/Applications/Project.app
if [[ $? = 0 ]]; then
    echo "\033[31m $appName 重簽名成功\n \033[0m"
else
    echo "\033[31m $appName 重簽名失敗\n \033[0m"
    exit 0
fi

# 生成 ipa
xcodebuild -exportArchive -archivePath $ipaPath/Payload/Project.xcarchive -exportPath ${ipaPath}/$appDownloadName.ipa -exportOptionsPlist $ExportOptions

if [[ $? = 0 ]]; then
    echo "\033[31m \n $appName 生成 IPA 成功 \n\n\n\n\n\033[0m"
else
    echo "\033[31m \n $appName 生成 IPA 失敗 \n\n\n\n\n\033[0m"
    exit 0
fi

# 建立 Plist
plist_path=$allIPAPackPath/$appName/$appDownloadName.plist

cat << EOF > $plist_path
<?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>items</key>
    <array>
        <dict>
            <key>assets</key>
            <array>
                <dict>
                    <key>kind</key>
                    <string>software-package</string>
                    <key>url</key>
                    <string>https://xxxxxxxxxxxx/$appDownloadName.ipa</string>
                </dict>
                <dict>
                    <key>kind</key>
                    <string>display-image</string>
                    <key>url</key>
                    <string>https://xxxxxxxxxxxx/${appIconName}.png</string>
                </dict>
                <dict>
                    <key>kind</key>
                    <string>full-size-image</string>
                    <key>url</key>
                    <string>https://xxxxxxxxxxxx/${appIconName}.png</string>
                </dict>
            </array>
            <key>metadata</key>
            <dict>
                <key>bundle-identifier</key>
                <string>你的bundid</string>
                <key>bundle-version</key>
                <string>$bundleVersion</string>
                <key>kind</key>
                <string>software</string>
                <key>title</key>
                <string>$appDownloadName</string>
            </dict>
        </dict>
    </array>
</dict>
</plist>
EOF

# 移動
mv ${ipaPath}/$appDownloadName.ipa ${allIPAPackPath}/$appName

done

# 6.上傳蒲公英分發平臺

if [[ $ISUPLOAD = 1 ]]; then
    echo "\n 開始上傳蒲公英... \n"
    curl -F "file=@$AllIPAPackPath/$appName/$appDownloadName.ipa/Project.ipa" \
    -F "uKey=$USERKEY" \
    -F "_api_key=$APIKEY"\
    http://www.pgyer.com/apiv1/app/upload
    #判斷上傳結果
    if [[ $? = 0 ]]; then
        echo "\n ~~~~~~~\^o^/~~~~上傳到蒲公英成功~~~~\^o^/~~~ \n"
    else
        echo "\n ~~~~~\(╯-╰)/~~~~~~~上傳到蒲公英失敗~~~~~\(╯-╰)/~~~~~ \n"
    fi
fi

# 清除無關檔案
rm -rf $ipaPath/Payload

# 結束時間
endTime=`date +%s`
echo -e "打包時間$[ endTime - beginTime ]秒"
複製程式碼

參考

相關文章