1. 背景
在iOS項目打包時炉奴,有兩個版本號佛吓,一個是Version擦酌,即顯示在App Store中的版本號俱诸,其key為CFBundleShortVersionString
;另一個是Build仑氛,即編譯版本號乙埃,其key為CFBundleVersion
闸英。
2. 問題發(fā)現(xiàn)
當App準備上架時,需要打包提交至App Store審核介袜,在這個過程中我們可能會多次上傳ipa包甫何。在Version相同的情況下,若Build還相同遇伞,則上傳到App Store Connect時會提示已有該版本辙喂,不能再次上傳。
3. 問題解決
為了避免這種情況鸠珠,我們可以在每次打包前去手動修改Build巍耗,以保證不會重復(fù)〗ヅ牛或者我們可以通過腳本讓Build在每次打包時自動加一炬太,省去每次手動修改的麻煩。
2023.07 功能增強:版本號和Build版本號都可自動+1(版本號中的最小數(shù)字版本號自動+1)驯耻。
- 假如當前Version版本號為1.0.0亲族,則+1后為1.0.1
- 假如當前Build版本號為10,則+1后為11
1可缚、可通過設(shè)置腳本中的
VERSION_INCREASE_WHEN_BUILD
和BUILD_INCREASE_WHEN_BUILD
兩個變量來控制Version或Build是否自動+1霎迫。
2、可到Xcode項目的.xcodeproj/project.pbxproj
配置文件中獲取ReleaseXCBuildConfiguration
節(jié)點 和 DebugXCBuildConfiguration
節(jié)點的UUID帘靡,分別賦值給該腳本中的變量VERSION_UUID_Release
和VERSION_UUID_Debug
知给。
- 查找
XCBuildConfiguration
節(jié)點UUID的方法:先搜索“isa = XCBuildConfiguration”節(jié)點,再查看該節(jié)點是否包含MARKETING_VERSION
和CURRENT_PROJECT_VERSION
描姚,若包含涩赢,則取該節(jié)點的UUID。
添加腳本的流程:
- Xcode切換到 Build Phases 選項卡轰胁;
- 點擊左上角"+"號來增加一項"New Run Script Phase"谒主;
- 添加如下腳本代碼:
Shell #!/bin/sh
代碼:
# ******************************************************
#
# Copyright 2015-2023 BTStudio. All rights reserved.
#
# Name : Version Increase
# Author : Wangzhi
# Last modified : 2023-07-03
# Email : wzqsyk@126.com
# Description : 每次Build或Archive后,版本號中的最小數(shù)字版本號自動+1赃阀。
#
#
# 1. 最小數(shù)字版本號:版本號中最后一個.后的數(shù)字霎肯。無.時,與版本號中的數(shù)字相同榛斯。
#
# 2. 版本號格式:(注意:版本號設(shè)置為以下格式時观游,該腳本才能實現(xiàn)其功能。)
# - 1.0.0 規(guī)范的版本號 (推薦用于上架版本號)
# - 1.0.0_Beta 規(guī)范的版本號加上以下劃線分割的后綴 (推薦用于開發(fā)階段的版本號)
# - 10 純數(shù)字版本號 (推薦用于Build版本號)
# - 10_Beta 純數(shù)字版本號加上以下劃線分割的后綴 (推薦用于Build版本號)
#
# 3. 版本號自動+1:
# - 若當前版本號為1.0.0驮俗,則+1后為1.0.1
# - 若當前版本號為1.0.0_Beta懂缕,則+1后為1.0.1_Beta
# - 若當前版本號為1,則+1后為2
# - 若當前版本號為1_Beta王凑,則+1后為2_Beta
# - 若當前版本號為1.0.0.00搪柑,則+1后為1.0.0.01
#
# 4. 軟件版本號規(guī)范
# 軟件版本號由4部分組成:主版本號.子版本號.階段版本號.日期版本號加希臘字母版本號聋丝。
# 示例:1.0.0_Alpha、1.0.0.230701_Alpha
#
# - 主版本號:當功能模塊有較大的變動(比如增加多個模塊或整體架構(gòu)發(fā)生變化)時進行修改工碾。
# - 子版本號:當功能有一定的增加或變化時進行修改弱睦。
# - 階段版本號:一般是Bug修復(fù)或是一些小的變動,要經(jīng)常發(fā)布修訂版時進行修改渊额。
# - 日期版本號:用于記錄修改項目的當前日期况木,每天對項目的修改都需要修改。
# - 希臘字母版本號:用于標注當前版本的軟件處于哪個開發(fā)階段旬迹,當軟件進入到另一個階段時需要修改火惊。
# (開發(fā)階段:Base、Alpha奔垦、Beta屹耐、RC、Release)
#
# ******************************************************
# 每次編譯后版本號是否+1 (0-不執(zhí)行+1,1-每次Build后+1,2-每次Archive后+1)
VERSION_INCREASE_WHEN_BUILD=2
# 每次編譯后編譯版本號是否+1 (0-不執(zhí)行+1,1-每次Build后+1,2-每次Archive后+1)
BUILD_INCREASE_WHEN_BUILD=2
# 項目配置文件project.pbxproj的路徑
ProjectPbxprojPath="${PROJECT_NAME}.xcodeproj/project.pbxproj"
# 配置文件中 MARKETING_VERSION 和 CURRENT_PROJECT_VERSION 所屬節(jié)點(對象)的UUID
# UUID示例:336C88122A4D5E2C003DA11D宴倍,UUID需要手動到project.pbxproj中查找確定张症。
# 方法:先搜索isa = XCBuildConfiguration節(jié)點,再查看該節(jié)點是否包含
# MARKETING_VERSION 和 CURRENT_PROJECT_VERSION鸵贬,若包含,則取該節(jié)點的UUID脖捻。
VERSION_UUID_Release=""
VERSION_UUID_Debug=""
# 注意:項目設(shè)置有不同Target時阔逼,需要根據(jù)Target名稱確定UUID
if [[ "${TARGET_NAME}" == "XXX" ]]; then
VERSION_UUID_Release=""
VERSION_UUID_Debug=""
elif [[ "${TARGET_NAME}" == "XXX_Beta" ]]; then
VERSION_UUID_Release=""
VERSION_UUID_Debug=""
fi
ReleaseVersion="1.0.0" # 版本號默認值
BuildVersion="1" # 編譯版本號默認值
# Info.plist中的版本號(CFBundleShortVersionString)
VersionOfInfo=""
# Info.plist中的編譯版本號(CFBundleVersion)
BuildVersionOfInfo=""
# 項目配置文件project.pbxproj中的版本號(MARKETING_VERSION)
VersionOfConfig=""
# 項目配置文件project.pbxproj中的編譯版本號(CURRENT_PROJECT_VERSION)
BuildVersionOfConfig=""
# Build或Archive的標志,默認為空
ConfigFlagForVersion=""
ConfigFlagForBuild=""
echo "VI_當前Xcode的 Build Configuration: $CONFIGURATION"
if [ $VERSION_INCREASE_WHEN_BUILD -eq 1 ]; then
ConfigFlagForVersion="Debug"
elif [ $VERSION_INCREASE_WHEN_BUILD -eq 2 ]; then
ConfigFlagForVersion="Release"
fi
if [ $BUILD_INCREASE_WHEN_BUILD -eq 1 ]; then
ConfigFlagForBuild="Debug"
elif [ $BUILD_INCREASE_WHEN_BUILD -eq 2 ]; then
ConfigFlagForBuild="Release"
fi
# 數(shù)字版本號自動+1函數(shù)
# - 參數(shù): version
function version_auto_increment() {
version=$1
version_new=$1
if [ -n "$version" ]; then
# =~ : 用于判斷左邊的字符串和右邊的正則表達式pattern是否匹配。
# #*"." : 從字符串第一次出現(xiàn).的位置開始地沮,截取.右邊的所有字符嗜浮。
# ##*"." : 從字符串最后一次出現(xiàn).的位置開始,截取.右邊的所有字符摩疑。
# %"."* : 從字符串最后一次出現(xiàn).的位置開始危融,截取.左邊的所有字符。
# %%"."* : 從字符串第一次出現(xiàn).的位置開始雷袋,截取.左邊的所有字符吉殃。
major_version="" # 主版本號
minor_version="" # 小版本號
suffix_version="" # 版本號后綴
# 符合格式:10
if [[ "$version" =~ ^[0-9]+$ ]]; then
# 示例版本號:12345
#
# 主版本號:無
# 小版本號:12345
# 版本號后綴:無
minor_version=$version
#printf "VI_版本號: ${version}=${major_version}+${minor_version}+${suffix_version}"
# 符合格式:1.0.0
elif [[ "$version" =~ ^[0-9][0-9\.]*[0-9]$ ]]; then
# 示例版本號:1.23.45
#
# 主版本號:1.23
# 小版本號:45
# 版本號后綴:無
major_version=${version%"."*}
minor_version=${version##*"."}
minor_version=${minor_version%%"_"*}
#printf "VI_版本號:: ${version}=${major_version}+${minor_version}+${suffix_version}"
# 符合格式:10_Beta
elif [[ "$version" =~ ^[0-9]+_[0-9A-z_]*$ ]]; then
# 示例版本號:12345_Beta
#
# 主版本號:無
# 小版本號:12345
# 版本號后綴:Beta
minor_version=${version%%"_"*}
suffix_version=${version#*"_"}
#printf "VI_版本號::: ${version}=${major_version}+${minor_version}+${suffix_version}"
# 符合格式:1.0.0_Beta
elif [[ "$version" =~ ^[0-9][0-9\.]*[0-9]_[0-9A-z_]*$ ]]; then
# 示例版本號:1.23.45_Beta
#
# 主版本號:1.23
# 小版本號:45
# 版本號后綴:Beta
major_version=${version%"."*}
minor_version=${version##*"."}
minor_version=${minor_version%%"_"*}
suffix_version=${version#*"_"}
#printf "VI_版本號:::: ${version}=${major_version}+${minor_version}+${suffix_version}"
else
version_new=$version
#printf "VI_版本號: ${version} 不符合格式,不做+1處理"
fi
# 小版本號不為空楷怒,再去處理小版本號+1
if [ -n "$minor_version" ]; then
minor_length=${#minor_version}
# 小版本號+1 (10#表示讓Shell強制將"00009"當成十進制來解釋蛋勺,而不是八進制)
minor_version=$((10#$minor_version + 1))
# 小版本號格式化
if [ $minor_length -eq 2 ]; then
minor_version=$(printf "%02d" $minor_version)
elif [ $minor_length -eq 3 ]; then
minor_version=$(printf "%03d" $minor_version)
elif [ $minor_length -eq 4 ]; then
minor_version=$(printf "%04d" $minor_version)
elif [ $minor_length -eq 5 ]; then
minor_version=$(printf "%05d" $minor_version)
else
minor_version=$(printf "%d" $minor_version)
fi
# 拼接新版本號
# 追加 . 和 _
if [ -n "$major_version" ]; then
major_version="${major_version}."
fi
if [ -n "$suffix_version" ]; then
suffix_version="_${suffix_version}"
fi
version_new=${major_version}${minor_version}${suffix_version}
#printf "VI_小版本號+1后: ${version_new}"
fi
fi
# return只能返回1-255的整數(shù),通常只是用來供其他地方調(diào)用獲取狀態(tài)鸠删,一般用0表示成功抱完,1表示失敗
#return 1
# echo用于返回字符串結(jié)果
echo ${version_new}
}
# 版本號的處理
#if [ -z "$VERSION_UUID_Release" ]; then
# echo "VI_請到項目.xcodeproj/project.pbxproj中獲取Release XCBuildConfiguration節(jié)點的UUID,并賦值給該腳本中的變量VERSION_UUID_Release"
#fi
if [[ "${ConfigFlagForVersion}" == "${CONFIGURATION}" ]]; then
# 用于處理的版本號
VersionOfHandle=""
# 1. 讀取版本號
VersionOfInfo=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "${INFOPLIST_FILE}")
VersionOfConfig=${MARKETING_VERSION}
: '
# 1.1 版本號的確定
# 判斷讀取出來的VersionOfInfo是否為空字符串:
# - 是:不做處理
# - 否:再判斷是否為字符串"$(MARKETING_VERSION)"
# -- 是:則需從MARKETING_VERSION讀取版本號
# -- 否:則讀取出來的VersionOfInfo即是版本號
if [ -n "$VersionOfInfo" ]; then
if [[ "$VersionOfInfo" == "\$(MARKETING_VERSION)" ]]; then
VersionOfHandle=$VersionOfConfig
echo "VI_Version(Info.plist): \$(MARKETING_VERSION)"
else
VersionOfHandle=$VersionOfInfo
echo "VI_Version(Info.plist): ${VersionOfHandle}"
fi
else
echo "VI_Version(Info.plist): is empty"
fi
# VersionOfInfo和VersionOfConfig中以后者為優(yōu)先刃泡,所以再判斷
# 讀取出來的VersionOfConfig是否為空字符串:
# - 是:不做處理
# - 否:則讀取出來的VersionOfConfig即是版本號
if [ -n "$VersionOfConfig" ]; then
VersionOfHandle=$VersionOfConfig
echo "VI_Version(project.pbxproj): ${VersionOfHandle}"
else
echo "VI_Version(project.pbxproj): is empty"
fi
'
if [ -n "$VersionOfInfo" ]; then
if [[ "$VersionOfInfo" == "\$(MARKETING_VERSION)" ]]; then
VersionOfHandle=$VersionOfConfig
echo "VI_Version(Info.plist): \$(MARKETING_VERSION)=${VersionOfHandle}"
else
VersionOfHandle=$VersionOfInfo
echo "VI_Version(Info.plist): ${VersionOfHandle}"
fi
else
VersionOfHandle=$VersionOfConfig
echo "VI_Version(project.pbxproj): ${VersionOfHandle}"
fi
# 2. 版本號+1處理
ReleaseVersion=$(version_auto_increment $VersionOfHandle)
echo "VI_Version小版本號+1后: ${ReleaseVersion}"
# 3. 寫入新版本號
# 3.1 新版本號寫入Info.plist
#if [ -n "$VersionOfInfo" ] && [[ "$VersionOfInfo" != "\$(MARKETING_VERSION)" ]]; then
if [ -n "$VersionOfInfo" ]; then
echo "VI_New Version寫入: Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $ReleaseVersion" "${INFOPLIST_FILE}"
fi
: '
# 3.2 新版本號寫入項目配置文件project.pbxproj
if [ -n "$VersionOfConfig" ]; then
echo "VI_New Version寫入: project.pbxproj"
# Debug
/usr/libexec/PlistBuddy -c "Set :objects:${VERSION_UUID_Debug}:buildSettings:MARKETING_VERSION ${ReleaseVersion}" "${ProjectPbxprojPath}"
# Release
/usr/libexec/PlistBuddy -c "Set :objects:${VERSION_UUID_Release}:buildSettings:MARKETING_VERSION ${ReleaseVersion}" "${ProjectPbxprojPath}"
fi
'
echo "VI_Release version need increase."
else
echo "VI_Release version don't need increase."
fi
# Build版本號的處理
#if [ -z "$VERSION_UUID_Debug" ]; then
# echo "VI_請到項目.xcodeproj/project.pbxproj中獲取Debug XCBuildConfiguration節(jié)點的UUID巧娱,并賦值給該腳本中的變量VERSION_UUID_Debug"
#fi
if [[ "${ConfigFlagForBuild}" == "${CONFIGURATION}" ]]; then
# 用于處理的編譯版本號
BuildVersionOfHandle=""
# 1. 讀取Build版本號
BuildVersionOfInfo=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${INFOPLIST_FILE}")
BuildVersionOfConfig=${CURRENT_PROJECT_VERSION}
: '
# 1.1 編譯版本號的確定(與版本號的確定邏輯相同)
if [ -n "$BuildVersionOfInfo" ]; then
if [[ "$BuildVersionOfInfo" == "\$(CURRENT_PROJECT_VERSION)" ]]; then
BuildVersionOfHandle=$BuildVersionOfConfig
echo "VI_Build(Info.plist): \$(CURRENT_PROJECT_VERSION)"
else
BuildVersionOfHandle=$BuildVersionOfInfo
echo "VI_Build(Info.plist): ${BuildVersionOfHandle}"
fi
else
echo "VI_Build(Info.plist): is empty"
fi
if [ -n "$BuildVersionOfConfig" ]; then
BuildVersionOfHandle=$BuildVersionOfConfig
echo "VI_Build(project.pbxproj): ${BuildVersionOfHandle}"
else
echo "VI_Build(project.pbxproj): is empty"
fi
'
if [ -n "$BuildVersionOfInfo" ]; then
if [[ "$BuildVersionOfInfo" == "\$(CURRENT_PROJECT_VERSION)" ]]; then
BuildVersionOfHandle=$BuildVersionOfConfig
echo "VI_Build(Info.plist): \$(CURRENT_PROJECT_VERSION)=${BuildVersionOfHandle}"
else
BuildVersionOfHandle=$BuildVersionOfInfo
echo "VI_Build(Info.plist): ${BuildVersionOfHandle}"
fi
else
BuildVersionOfHandle=$BuildVersionOfConfig
echo "VI_Build(project.pbxproj): ${BuildVersionOfHandle}"
fi
# 2. 版本號+1處理
BuildVersion=$(version_auto_increment $BuildVersionOfHandle)
echo "VI_Build小版本號+1后: ${BuildVersion}"
# 3. 寫入新版本號
# 3.1 新版本號寫入Info.plist
#if [ -n "$BuildVersionOfInfo" ] && [[ "$BuildVersionOfInfo" != "\$(CURRENT_PROJECT_VERSION)" ]]; then
if [ -n "$BuildVersionOfInfo" ]; then
echo "VI_New Build寫入: Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BuildVersion" "${INFOPLIST_FILE}"
fi
: '
# 3.2 新版本號寫入項目配置文件project.pbxproj
if [ -n "$BuildVersionOfConfig" ]; then
echo "VI_New Build寫入: project.pbxproj"
# Debug
/usr/libexec/PlistBuddy -c "Set :objects:${VERSION_UUID_Debug}:buildSettings:CURRENT_PROJECT_VERSION ${BuildVersion}" "${ProjectPbxprojPath}"
# Release
/usr/libexec/PlistBuddy -c "Set :objects:${VERSION_UUID_Release}:buildSettings:CURRENT_PROJECT_VERSION ${BuildVersion}" "${ProjectPbxprojPath}"
fi
'
echo "VI_Build version need increase."
else
echo "VI_Build version don't need increase."
fi
效果如下:
- 之后每次進行打包時赞别,Version或Build會自動+1,避免了Version或Build重復(fù)的問題事示。
提示:
- 可以雙擊 Version Increase 進行名稱的修改匣距;
- 增加一項 New Run Script Phase 后,Xcode默認名稱為Run Script 上荡;
- 每次編譯或打包后趴樱,可以查看腳本的執(zhí)行結(jié)果,如下圖所示:
右上角搜索框中輸入VI_
即可過濾顯示腳本的執(zhí)行結(jié)果酪捡。
小知識
PlistBuddy是Mac自帶的專門解析plist的小工具叁征,由于PlistBuddy并不在Mac默認的Path里,所以我們得通過絕對路徑(/usr/libexec/PlistBuddy)來引用這個工具逛薇。
Shell基本語法:
- 定義變量時捺疼,變量名不加美元符號$ (PHP語言中變量需要)。如:
your_name="runoob.com" (注意:變量名和等號之間不能有空格)- 使用一個定義過的變量永罚,只要在變量名前面加美元符號即可啤呼。如:
echo $your_name- 讀取plist中某個字段的值,并賦給變量var:
var=$(/usr/libexec/PlistBuddy -c "Print :字段名" plist文件路徑)- 修改plist中某個字段相應(yīng)的值:
/usr/libexec/PlistBuddy -c "Set :字段名 值" plist文件路徑- Shell的echo指令與PHP的echo指令類似呢袱,都是用于字符串的輸出官扣。
命令格式:echo string- Shell腳本沒有{}括號,所以用fi表示if語句塊的結(jié)束羞福。
- #! 是一個約定的標記惕蹄,它告訴系統(tǒng)這個腳本需要什么解釋器來執(zhí)行,即使用哪一種Shell治专。