Better way of incrementing build number?

2019-01-01 11:34发布

I have been using a shell script as part of my Xcode build process to increment the build number within the plist file, however it's making Xcode 4.2.1 crash frequently (with an error about the target not belonging to a project; I'm guessing the changing of the plist file is confusing Xcode in some way).

The shell script did this so that the build number is only incremented by agvtool when a file is newer than the plist file (so just building didn't increment the value):

if [ -n \"`find ProjDir -newer ProjDir/Project-Info.plist`\" ]; then agvtool -noscm next-version -all; else echo \"Version not incremented\"; fi

Is there a way to increment the build number (in the plist file, or anywhere else) that doesn't break Xcode?

EDIT: Here is my final solution, based on the suggestion of @Monolo. I created the following script in ${PROJECT_DIR}/tools (sibling to the .xcodeproj directory):

#!/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

EDIT 2: I have modified the script to incorporate @Milliways suggestion.

I then invoked the script from Xcode target 'Build Phases' section: Xcode build phases screenshot

EDIT 3: As per @massimobio's answer, you'll need to add quotes around the plist argument if it contains spaces.

EDIT 4: Just to update that my preferred method of invoking this build script is now to create a separate target and make the app target dependant upon this Bump Build Number target. This ensures that it is invoked before the app target does anything with the plist (I've noticed that it likes to process the plist at the start of the build). I've also switched to a purely python-based solution that keeps the version number in a separate file, and writes version source files, as this is more useful for cross-platform products (i.e. Visual Studio under Windows can invoke the script, and obviously cmake/make-type builds can do so also). This has the benefit that the build number is always the same even under different platforms, and it's also possible to update the Visual Studio Resource.rc file with the current version/build as well.

Here is the python script I currently use to update Info.plist files within Xcode project.

20条回答
浅入江南
2楼-- · 2019-01-01 12:20

I tried the modified procedure and it did not work, because:-

  1. Xcode 4.2.1 changes the xcuserdata subdirectory in .xcodeproj

  2. git notes the previous change in Project-Info.plist

The following modification causes these to be ignored and only flags genuine changes:-

if [ -n "$(find $dir \! -path "*xcuserdata*" \! -path "*.git" -newer $plist)" ]; then
查看更多
深知你不懂我心
3楼-- · 2019-01-01 12:21

I use the last SVN revision for the build number. If you change the Info.plist in the build directory, you won't affect the source Info.plist:

# use the last SVN revision as the build number:
Info_plist="$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Info.plist"
defaults write "${Info_plist}" CFBundleVersion `svn stat -u | awk '/'"Status against revision:"'/ {print $4}'`
查看更多
浮光初槿花落
4楼-- · 2019-01-01 12:23

Building on Wil Gieseler's solution, I had just one change I wanted to make. His solution puts the count of git commits into the build number. Useful, but still kind of a pain to find the actual commit that created that build. I didn't care too much whether the build number was monotonically increasing, and so I dropped that requirement so that I could more easily access the commit that generated a given binary.

To that end, I modified his first script to the following:

# Set the build number to the decimal conversion of the short version of the current git SHA

# Get the short version of the current git SHA in hexadecimal
SHA=$(git rev-parse --short @)
# Uppercase any alphabetic chars in SHA (bc doesn't like lowercase hex numbers)
UPPERCASE_SHA=$(tr '[:lower:]' '[:upper:]' <<< "$SHA")
# Use bc to convert the uppercase SHA from hex to decimal
BUILD_NUM=$(bc <<< "ibase=16;obase=A;$UPPERCASE_SHA")
# Set our build number to that
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUM" "${PROJECT_DIR}/${INFOPLIST_FILE}"

# To convert a build number back to a usable git SHA, run the following, substituting the build number for <build number>
# bc <<< "ibase=10;obase=16;<build number>"

This converts the short version of the current git SHA into decimal. Hexadecimal characters don't play nicely with Apple's build number requirements, which is why I had to do this. To convert it back, you'd simply run something like this:

SHA=$(bc <<< "ibase=10;obase=16;<build number>")

in bash, where <build number> is the build number you got from a binary. Then, just run git checkout $SHA, and there you go.

Because this is an adaptation of Wil Gieseler's solution, as mentioned above, you'll also need the following post-build script:

# Set the build number to "DEVELOPMENT"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion DEVELOPMENT" "${PROJECT_DIR}/${INFOPLIST_FILE}"

which keeps your git history clean.

查看更多
残风、尘缘若梦
5楼-- · 2019-01-01 12:26

Here's my solution. If you're like me: terminal friendly, like ruby, like semantic versioning, try this.

Make a file named Rakefile which contains this:

require "xcodeproj"
require "versionomy"

XCODEPROJECT = "MyProject.xcodeproj"
INFOPLISTFILE = "MyProject/MyProject-Info.plist"

$UPDATES = [:major,:minor,:tiny]
$UPDATES.each { |part|
  desc "increment #{part} part of version"
  task "increment:#{part}" do |task|
    version=`/usr/libexec/Plistbuddy -c "Print CFBundleVersion" #{INFOPLISTFILE}`.chomp
    version=Versionomy.parse(version)
    version=version.bump(part)

    # I use the same string for CFBundleVersion and CFBundleShortVersionString for now
    `/usr/libexec/PlistBuddy -c "Set :CFBundleVersion #{version}" #{INFOPLISTFILE}`
    `/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString #{version}" #{INFOPLISTFILE}`
    print "version upgraded to #{version}\n"
  end
}

Prepare: gem install xcodeproj versionomy

Run: rake increment:major or rake increment:minor or rake increment:tiny whenever you want.

查看更多
琉璃瓶的回忆
6楼-- · 2019-01-01 12:27

FWIW - this is what I'm currently using to increase the build number only for release builds (which includes archiving). Works fine under Xcode 5.1.

Just copy/paste the snippet into a Run script build phase directly in Xcode:

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;
查看更多
旧时光的记忆
7楼-- · 2019-01-01 12:29

I update build number by following method.

$INFO_FILE is the path of the plist file. And $build_number is a new build number for this building.

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $build_number" "${INFO_FILE}"

Generally, my $build_number is composed by major and minor parts. The minor is come from project information. So I describe how to generate the major part.

## Composed by `major` and `minor`. 
## `minor` is parsed from project information. It's another story.
## Examples: `21.1`, or `21.1.3`
build_number="${major_number}.${minor_number}"

I have 2 strategies to decide the $build_number.

First Strategy

This strategy uses the git tag count to decide the major of build number. If there are 53 tags of the project, it will return 53 by following shell script.

Generally, it's increasing. And it will force the developer to put a git tag before publishing.

major_number=$(git tag -l | wc -l | grep -oE "\d+")

Second Strategy

Let Jenkins CI system decide the major part. It has an environment variable BUILD_NUMBER. It is increasing automatically when building on the CI system. This information is useful to trace the project history on the CI system.

major_number=${BUILD_NUMBER}
查看更多
登录 后发表回答