递增构建版本号的更好方法是什么?

141

我一直在使用一个shell脚本作为Xcode构建过程的一部分,用于递增plist文件中的构建号码,然而这经常使得Xcode 4.2.1崩溃(出现关于目标不属于项目的错误; 我猜测更改plist文件会以某种方式使Xcode产生混淆)。

该shell脚本这样做是为了只有在文件比plist文件更新时才由agvtool递增构建号码(因此仅仅构建并不会递增值):

if [ -n \"`find ProjDir -newer ProjDir/Project-Info.plist`\" ]; then agvtool -noscm next-version -all; else echo \"Version not incremented\"; fi
有没有一种方式可以递增构建号(在plist文件或其他任何地方),而不会破坏Xcode?
最终编辑:我现在使用一个Python脚本来完成这种工作,我刚刚在GitHub上公开了这个脚本。它没有很好的文档说明,但不应该难以理解。作为奖励,这个仓库还包含一个有用的脚本,可以自动将第三方库捆绑到应用程序包中。

1
如果有人感兴趣:我稍微修改了脚本,使用十六进制数代替十进制数 - https://gist.github.com/sascha/5398750 - Sascha
1
您可以将此脚本直接添加为预构建操作,无需调用外部脚本。不要在构建阶段运行此脚本;Xcode仅会在每两次构建中复制更新的plist文件。 - Ed McManus
3
一开始我遇到了“权限被拒绝”的错误,所以我想将这个问答链接提供给其他遇到同样问题的人:https://dev59.com/92kw5IYBdhLWcg3wfKrZ。 - Jason
@Tander 看起来你没有将 plist 文件作为脚本的参数提供。 - trojanfoe
@Tander 在名为 tools 的目录中,就在 .xcodeproj 旁边。 - trojanfoe
显示剩余8条评论
22个回答

82

我试过很多这个问题的答案,但都没有完全满足我。不过,最终我想出了一个混合方案,我真的很喜欢!

我们只需将构建产品的版本号设置为Git提交的数量即可。这不会影响您的源代码控制,因为脚本仅会改变构建产品。

请在您的构建阶段末尾添加此“运行脚本”构建阶段:

if [ "${CONFIGURATION}" = "Release" ]; then
    buildNumber=$(git rev-list --count head)
    /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
fi

将项目中的 Info.plist 版本设置为您想要的任何版本,但在构建发布版本时它永远不会被使用。我将我的设置为 AUTOMATEDDEVELOPMENT,这样当我运行开发版本时就很清楚了。

就是这样!构建后的应用程序将具有不断增长的构建号。(只要您始终从同一分支进行构建。)

我喜欢这种方法的原因:

  • 简单易行
  • 不会污染Git版本历史记录
  • CFBundleVersion 完全自动化
  • 漂亮的版本号可以随时修改

其他注意事项:

  • 如果您的项目中有应用程序扩展,请在这些目标上设置相同的构建脚本。这将使所有版本号自动化并保持同步。App Store 要求扩展版本与主应用程序匹配。

15
你可以使用 git rev-list --count HEAD 代替 git rev-list HEAD | wc -l | tr -d ' ' - kennytm
当你说构建的开始和结束时,这些脚本应该放在构建阶段的哪个位置?难道不是所有的构建阶段都会执行以构建档案吗?你如何确保 git rev-list 的值进入档案,但在构建完成后将 plist 的更改还原? - conorgriffin
1
我不确定它应该放在哪里,我把第一个脚本放在第一个构建阶段,最后一个脚本放在最后一个构建阶段,这对我有效。 - Wil Gieseler
但是您不能提交 Info.plist,因为一旦提交,构建号就会过时。这种解决方案并不新颖,只有在生成一个未被版本控制跟踪的文件时才有效,而 Info.plist 总是被跟踪的。 - Droppy
1
你绝对可以使用这个解决方案提交 Info.plist - 这就是整个重点。Info.plist 始终设置并检查版本号设置为“DEVELOPMENT”,在构建过程中暂时更改,然后再次设置为“DEVELOPMENT”,因此 Info.plist 是稳定的。 - Wil Gieseler
显示剩余6条评论

39

我已经使用过这个gist,它的效果符合预期。 https://gist.github.com/sekati/3172554 (所有功劳归原作者所有)

这些脚本是我随着时间修改的。

xcode-versionString-generator.sh,

xcode-build-number-generator.sh

由于这些gist有助于开发团队,因此我将其制作成了GitHub项目。让我们好好发展它。 这是GitHub项目: https://github.com/alokc83/Xcode-build-and-version-generator

我已经对两个脚本的代码进行了微小的改进。 不要使用下面的代码,请从GitHub上获取最新版本

获取版本号:

# xcode-version-bump.sh
# @desc Auto-increment the version number (only) when a project is archived for export. 
# @usage
# 1. Select: your Target in Xcode
# 2. Select: Build Phases Tab
# 3. Select: Add Build Phase -> Add Run Script
# 4. Paste code below in to new "Run Script" section
# 5. Check the checkbox "Run script only when installing"
# 6. Drag the "Run Script" below "Link Binaries With Libraries"
# 7. Insure your starting version number is in SemVer format (e.g. 1.0.0)

# This splits a two-decimal version string, such as "0.45.123", allowing us to increment the third position.
VERSIONNUM=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${PROJECT_DIR}/${INFOPLIST_FILE}")
NEWSUBVERSION=`echo $VERSIONNUM | awk -F "." '{print $3}'`
NEWSUBVERSION=$(($NEWSUBVERSION + 1))
NEWVERSIONSTRING=`echo $VERSIONNUM | awk -F "." '{print $1 "." $2 ".'$NEWSUBVERSION'" }'`
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $NEWVERSIONSTRING" "${PROJECT_DIR}/${INFOPLIST_FILE}"

建立:

# xcode-build-bump.sh
# @desc Auto-increment the build number every time the project is run. 
# @usage
# 1. Select: your Target in Xcode
# 2. Select: Build Phases Tab
# 3. Select: Add Build Phase -> Add Run Script
# 4. Paste code below into new "Run Script" section
# 5. Drag the "Run Script" below "Link Binaries With Libraries"
# 6. Ensure that your starting build number is set to a whole integer and not a float (e.g. 1, not 1.0)

buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"

这将始终增加构建号,而我只希望在源文件更改时才增加。这只是我目前使用的脚本,安全检查较少,并且对已更改的内容不太挑剔。 - trojanfoe
XCode 5:编辑器(菜单栏)→ 添加构建阶段 → 添加复制文件构建阶段: - Jonny
@trojanfoe:你可以将此脚本作为提交后钩子运行。在这种情况下,只有当您提交代码到存储库时,它才会增加构建编号。LostInTheTrees的下面回答是您可能想要执行的更多操作。 - Alok C
@Alix链接的shell脚本现在与此处发布的脚本有很大不同。如果只在进行归档构建时增加构建编号,您可以使用我制作的一个gist,该gist非常基于上面Alix的脚本,链接在这里:https://gist.github.com/mattpotts/abcffea6d08ad45739ef - Matt

29
如果我理解你的问题正确的话,你想要修改 Project-Info.plist 文件,它是 Xcode 的标准项目模板的一部分?
我这么问的原因是 Project-Info.plist 通常都在版本控制下,修改它意味着它将被标记为已修改。
如果你认为这没问题,那么下面的代码片段将更新构建号并在此过程中将文件标记为已修改。其中 get_build_number 是一个脚本(在此示例中是占位符),用于获取你想要使用的(可能增加后的)构建号:
#!/bin/sh

# get_build_number is a placeholder for your script to get the latest build number
build_number = `get_build_number`

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${build_number}" ProjDir/Project-Info.plist

PlistBuddy允许您设置plist文件中的任何键,而不仅仅是版本号。您可以创建所需的所有plist文件,并在需要时将它们包含在资源中。然后可以从捆绑包中读取它们。

至于您需要在关于窗格和其他地方显示版本,您还可以查看设置CFBundleGetInfoStringCFBundleShortVersionString


我不需要在 plist 文件中包含 git 提交(或标签),因此一个简单的递增系统就足够了(由 agvtool 提供)。然而,在构建过程中修改 plist 的行为经常会导致 Xcode 崩溃(自从删除脚本后,它没有再崩溃过,在之前每 3 次构建左右就会崩溃)。是否可以将版本信息放在另一个 plist 文件中,并将其与 bundle 一起包含并从 App 中访问? - trojanfoe
很棒的脚本 - 我更喜欢将其与Hugues BR的建议结合起来,仅在归档构建时使用它。这样可以保持数字较低,并忽略发布之间执行的任何开发构建。 - Jay
5
get_build_number是什么?它只是一个占位符吗? - chrisp
是的,get_build_number 只是一个占位符 - 已更新答案以澄清。 - Monolo

14

我不知道哪种方法是最好的,但为了防止任何人在搜索时找不到答案,我将发布苹果公司的回答...

根据这篇苹果公司的问答文章

使用agvtool自动化版本和构建号

版本和构建号分别指定应用程序的市场版本和内部版本。agvtool是一个命令行工具,允许你自动将这些数字递增到下一个最高数字或特定数字。

构建号标识您的应用程序的未发布或已发布版本。它存储在您的应用程序的Info.plist中作为CFBundleVersion(Bundle版本)。

您必须在Xcode项目中完成以下步骤:

  1. 启用agvtool

导航到您目标的“构建设置”面板,然后针对所有构建配置按如下方式进行更新:

  • 将当前项目版本设置为您选择的值。
你的Xcode项目数据文件project.pbxproj包含一个CURRENT_PROJECT_VERSION(当前项目版本)构建设置,用于指定项目的当前版本。agvtool在project.pbxproj中搜索CURRENT_PROJECT_VERSION。如果存在,则继续运行,否则停止运行。它的值用于更新构建号。
将版本控制系统设置为Apple通用格式。
默认情况下,Xcode不使用任何版本控制系统。将版本控制系统设置为Apple通用格式可确保Xcode将包含所有由agvtool生成的版本信息。

Set Versioning System to Apple Generic

  1. 设置您的版本和构建号码

agvtool会在您的应用程序的Info.plist中搜索您的版本和构建号码。如果它们存在,它会更新它们,否则不做任何操作。请确保您的Info.plist中存在CFBundleVersion(Bundle版本)和CFBundleShortVersionString(Bundle版本字符串,简短)键,如下图所示:

Set up your version and build numbers

请退出Xcode,然后在终端应用程序中导航到包含.xcodeproj项目文件的目录,然后再运行以下任何命令。 .xcodeproj项目文件包含由agvtool使用的project.pbxproj。(这是您可以在脚本中而不是命令行中运行的部分。) 更新版本号 要将版本号更新为特定版本,请运行:
xcrun agvtool new-marketing-version <your_specific_version>

Ex: 将版本号更新为2.0

xcrun agvtool new-marketing-version 2.0

更新构建版本号

要自动增加您的构建版本号,请运行

xcrun agvtool next-version -all

为了将您的应用程序的构建版本设置为特定版本,请运行以下命令:
xcrun agvtool new-version -all <your_specific_version>

例:将构建号设置为2.6.9

xcrun agvtool new-version -all 2.6.9

奖励:

要查看当前版本号,请运行

xcrun agvtool what-marketing-version

要查看当前版本号,请运行

xcrun agvtool what-version

8
这个问题是因为agvtool会中止Xcode的构建,因此无法将其作为脚本整合进构建阶段。 - Daniel Schlaug
1
你能否在方案的构建前操作中使用agvtool来进行版本号的自动增加呢? - lottadot
我将其添加为方案中的后置操作脚本。这样,在构建成功后它会递增,不会取消构建。 - Chad

14

整个条目非常有帮助。我使用了这个技巧,但将我的脚本设置为GIT的 post-commit 钩子,因此 CFBundleVersion 在每次成功提交后都会增加。钩子脚本放在 .git/hooks 中。项目目录中留下了一个日志。

这符合我最基本的标准。我想能够从GIT拉取版本并重新构建之前完全相同的构建。任何在构建过程中进行的增量都不能达到这个要求。

这是我的脚本:

#!/bin/sh
#
# post-commit
#
# This script increments the CFBundleVersion for each successful commit
#

plist="./XYZZY/XYZZY-Info.plist"
buildnum=$(/usr/libexec/Plistbuddy -c "Print CFBundleVersion" "$plist")
if [ -z "$buildnum" ]; then
    exit 1
fi
buildnumplus=$(expr $buildnum + 1)
/usr/libexec/Plistbuddy -c "Set CFBundleVersion $buildnumplus" "$plist"

echo $(date) "- Incremented CFBundleVersion to" $buildnumplus >> hookLog.txt

我有同样的需求,这就是为什么只有在源文件被修改时才会增加构建号。我发现这种方法非常有效,自从创建以来就没有对“bump_build_number.sh”脚本进行过更改。 - trojanfoe

11

FWIW - 这是我目前使用的方法,只针对发布构建版本(包括归档)来增加构建号。在Xcode 5.1下运行良好。

只需将这段代码片段复制/粘贴到Xcode中的Run script构建阶段即可:

buildnum=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$PRODUCT_SETTINGS_PATH")

if [ "$CONFIGURATION" = "Release" ]; then
buildnum=$((buildnum + 1))
echo "Build number updated to $buildnum"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildnum" "$PRODUCT_SETTINGS_PATH"
fi;

你如何增加CFBundleVersion?在Xcode 5.1中,它是一个格式为“1.0”的字符串? - chrisp
1
终于有人做对了 :) 我为什么要关心我在开发设备上运行的每一个构建?发布(以及发布给测试人员)才是重点,而不是“嗯,把那个向左移动2像素”的构建。 - uvesten

7
感谢提供这个脚本。它的效果非常好。
我的 Info.plist 文件位于一个包含空格的子目录中,因此我需要在运行脚本时在 plist 路径周围加上引号:
${PROJECT_DIR}/tools/bump_build_number.sh "${PROJECT_DIR}/${INFOPLIST_FILE}"

同样地,Shell脚本中所有路径也需要加上引号:

#!/bin/sh

if [ $# -ne 1 ]; then
    echo usage: $0 plist-file
    exit 1
fi

plist=$1
dir=$(dirname "$plist")

# Only increment the build number if source files have changed
if [ -n "$(find "$dir" \! -path "*xcuserdata*" \! -path "*.git" -newer "$plist")" ]; then
    buildnum=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$plist")
    if [ -z "$buildnum" ]; then
        echo "No build number in $plist"
        exit 2
    fi
    buildnum=$(expr $buildnum + 1)
    /usr/libexec/Plistbuddy -c "Set CFBundleVersion $buildnum" "$plist"
    echo "Incremented build number to $buildnum"
else
    echo "Not incrementing build number as source files have not changed"
fi

是的,那很有道理。很高兴你觉得它有用;我一直在Xcode 4.{2,3,4,5}下使用它,没有任何问题。 - trojanfoe
如果我看到这个答案的话,我本可以节省几个小时!另外,请注意构建号码不能有小数点,否则BASH会崩溃。 - Brenden

6
目前我使用的脚本非常基于上述Alix的答案。我的改编在下面增加了一个检查,只在发布/存档构建时自动递增。
如果没有这个更改,每个开发者都将按照自己的速度递增构建号,会出现版本控制冲突。而且git历史记录也会不必要地被构建号不断更改所污染。
# xcode-build-bump.sh
# @desc Auto-increment Xcode target build number every time the project is archived
# @src stackoverflow.com/a/15483906
# @usage
# 1. Select: your Target in Xcode
# 2. Select: Build Phases Tab
# 3. Select: Add Build Phase -> Add Run Script
# 4. Paste code below in to new "Run Script" section
# 5. Drag the "Run Script" below "Link Binaries With Libraries"
# 6. Insure that your starting build number is set to a whole integer and not a float (e.g. 1, not 1.0)

if [ "Release" != "${CONFIGURATION}" ]
then
    exit 0
fi

buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"

它也可以作为GitHub gist提供(格式稍微容易复制和粘贴一些)。

5
我建议使用autorevision
Xcode允许使用头文件(可以在构建时自动生成,而不是在版本控制系统中)提供值,在构建时将在info.plist中扩展这些值。您可以在autorevision网站上找到设置此项的详细步骤。
Autorevision具有面向这些类型的头文件的输出类型,可帮助处理这些情况。

1
“Autorevision”似乎没有按要求增加构建号码? - trojanfoe
假设只在新提交上构建,则 VCS_NUM 应该是您要查找的内容(请参见示例中的 autorevision.h)。 - dak180
Vienna-Info.plistVienna-All.xcconfig 是如何在任何 Xcode 项目中设置的好例子。 - dak180

4
你可以使用苹果的通用版本控制。基本上,你只需要在托管.xcproj文件的目录中调用agvtool next-version -all即可。有关更多详细信息,请查看上面的网址。

3
这个解决方案的问题是,在项目的脚本中调用agvtool会取消您的构建。除非您能找到解决此问题的方法,否则这不是一个好的解决方案。 - dwlz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接