1. 背景
在iOS项目打包时,有两个版本号,一个是Version,即显示在App Store中的版本号,其key为CFBundleShortVersionString
;另一个是Build,即编译版本号,其key为CFBundleVersion
。
2. 问题发现
当App准备上架时,需要打包提交至App Store审核,在这个过程中我们可能会多次上传ipa包。在Version相同的情况下,若Build还相同,则上传到App Store Connect时会提示已有该版本,不能再次上传。
3. 问题解决
为了避免这种情况,我们可以在每次打包前去手动修改Build,以保证不会重复。或者我们可以通过脚本让Build在每次打包时自动加一,省去每次手动修改的麻烦。
2023.07 功能增强:版本号和Build版本号都可自动+1(版本号中的最小数字版本号自动+1)。
- 假如当前Version版本号为1.0.0,则+1后为1.0.1
- 假如当前Build版本号为10,则+1后为11
1、可通过设置脚本中的
VERSION_INCREASE_WHEN_BUILD
和BUILD_INCREASE_WHEN_BUILD
两个变量来控制Version或Build是否自动+1。
2、可到Xcode项目的.xcodeproj/project.pbxproj
配置文件中获取ReleaseXCBuildConfiguration
节点 和 DebugXCBuildConfiguration
节点的UUID,分别赋值给该脚本中的变量VERSION_UUID_Release
和VERSION_UUID_Debug
。
- 查找
XCBuildConfiguration
节点UUID的方法:先搜索“isa = XCBuildConfiguration”节点,再查看该节点是否包含MARKETING_VERSION
和CURRENT_PROJECT_VERSION
,若包含,则取该节点的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后,版本号中的最小数字版本号自动+1。
#
#
# 1. 最小数字版本号:版本号中最后一个.后的数字。无.时,与版本号中的数字相同。
#
# 2. 版本号格式:(注意:版本号设置为以下格式时,该脚本才能实现其功能。)
# - 1.0.0 规范的版本号 (推荐用于上架版本号)
# - 1.0.0_Beta 规范的版本号加上以下划线分割的后缀 (推荐用于开发阶段的版本号)
# - 10 纯数字版本号 (推荐用于Build版本号)
# - 10_Beta 纯数字版本号加上以下划线分割的后缀 (推荐用于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. 软件版本号规范
# 软件版本号由4部分组成:主版本号.子版本号.阶段版本号.日期版本号加希腊字母版本号。
# 示例:1.0.0_Alpha、1.0.0.230701_Alpha
#
# - 主版本号:当功能模块有较大的变动(比如增加多个模块或整体架构发生变化)时进行修改。
# - 子版本号:当功能有一定的增加或变化时进行修改。
# - 阶段版本号:一般是Bug修复或是一些小的变动,要经常发布修订版时进行修改。
# - 日期版本号:用于记录修改项目的当前日期,每天对项目的修改都需要修改。
# - 希腊字母版本号:用于标注当前版本的软件处于哪个开发阶段,当软件进入到另一个阶段时需要修改。
# (开发阶段:Base、Alpha、Beta、RC、Release)
#
# ******************************************************
# 每次编译后版本号是否+1 (0-不执行+1,1-每次Build后+1,2-每次Archive后+1)
VERSION_INCREASE_WHEN_BUILD=2
# 每次编译后编译版本号是否+1 (0-不执行+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 所属节点(对象)的UUID
# UUID示例:336C88122A4D5E2C003DA11D,UUID需要手动到project.pbxproj中查找确定。
# 方法:先搜索isa = XCBuildConfiguration节点,再查看该节点是否包含
# MARKETING_VERSION 和 CURRENT_PROJECT_VERSION,若包含,则取该节点的UUID。
VERSION_UUID_Release=""
VERSION_UUID_Debug=""
# 注意:项目设置有不同Target时,需要根据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
# 数字版本号自动+1函数
# - 参数: version
function version_auto_increment() {
version=$1
version_new=$1
if [ -n "$version" ]; then
# =~ : 用于判断左边的字符串和右边的正则表达式pattern是否匹配。
# #*"." : 从字符串第一次出现.的位置开始,截取.右边的所有字符。
# ##*"." : 从字符串最后一次出现.的位置开始,截取.右边的所有字符。
# %"."* : 从字符串最后一次出现.的位置开始,截取.左边的所有字符。
# %%"."* : 从字符串第一次出现.的位置开始,截取.左边的所有字符。
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的整数,通常只是用来供其他地方调用获取状态,一般用0表示成功,1表示失败
#return 1
# echo用于返回字符串结果
echo ${version_new}
}
# 版本号的处理
#if [ -z "$VERSION_UUID_Release" ]; then
# echo "VI_请到项目.xcodeproj/project.pbxproj中获取Release XCBuildConfiguration节点的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中以后者为优先,所以再判断
# 读取出来的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节点的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重复的问题。
提示:
- 可以双击 Version Increase 进行名称的修改;
- 增加一项 New Run Script Phase 后,Xcode默认名称为Run Script ;
- 每次编译或打包后,可以查看脚本的执行结果,如下图所示:
右上角搜索框中输入VI_
即可过滤显示脚本的执行结果。
小知识
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中某个字段相应的值:
/usr/libexec/PlistBuddy -c "Set :字段名 值" plist文件路径- Shell的echo指令与PHP的echo指令类似,都是用于字符串的输出。
命令格式:echo string- Shell脚本没有{}括号,所以用fi表示if语句块的结束。
- #! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell。