How to Automatically Update Xcode Build Numbers from Git

Xcode provides two places to keep track of your app’s revisions: version and build. To see them, click on your Project name in the Navigator, and then click on your main build target under Targets. Click on the General tab. In this post I’ll show you how to automatically update your build numbers with every release build! First, an overview.

Version (CFBundleShortVersionString) is an arbitrary label to specify a build’s relationship to other builds from the perspective of external users or API consumers. According to Semantic Versioning, it is usually in the format MAJOR.MINOR.PATCH, where:

  • MAJOR version when you make incompatible API changes
  • MINOR version when you add functionality in a backwards-compatible manner
  • PATCH version when you make backwards-compatible bug fixes

Build (CFBundleVersion), on the other hand, is used to identify the exact version of a binary executable from the perspective of developers and testers (and the App Store). In a continuous integration environment, this can simply be an integer value that is incremented every time by the build server. However, in a distributed build environment, auto-incrementing build numbers doesn’t really work. Multiple developers build on their machines multiple times per day, so build numbers can become out of sync unless immediately checked in to source control. A more reliable method would be to use source control to generate the build numbers. Could we simply use the “short” SHA hash of the latest git commit?

git rev-parse --short HEAD
e42216e

The problem is that App Store requires the build number to be an “monotonically increasing string, comprised of one or more period-separated integers.” So instead, let’s count the number of git commits:

git rev-list HEAD | wc -l | tr -d ' '
80

Perfect! Now let’s make a build script to automatically update the build number in our Info.plist file whenever compiling a release build. Click on your Project name in the Navigator, and then click on your main build target under Targets. Click on the Build Phases tab. Go to Editor > Add Build Phase > Add Run Script Build Phase. Copy and paste the following code over the text that says ‘Type a script or drag a script file from your workspace to insert its path.’

#Update build number with number of git commits if in release mode
if [ ${CONFIGURATION} == "Release" ]; then
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
fi;

Note: Remember to only generate release builds when working in your release branch in git, typically master. Otherwise you will be using commit counts from another branch. (You really shouldn’t be making release builds from non-release branches anyway!)

About Martin Rybak

I am a New York area software developer and MBA with 10+ years of server-side experience on the Microsoft stack. I've also been a native iOS developer since before the days of ARC. I architect and develop full-stack web applications, iOS apps, database systems, and backend services.

10 responses to “How to Automatically Update Xcode Build Numbers from Git

  1. Are you sure, that “monotonically increasing string, comprised of one or more period-separated integers.” is still up-to-date for the Build version number?
    “The build string represents an iteration (released or unreleased) of the bundle and can contain a mix of characters and numbers, as in 12E123. For Mac apps, the (…)” source: https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/ConfiguringYourApp/ConfiguringYourApp.html#//apple_ref/doc/uid/TP40012582-CH28-SW18

    • Hi Herman, it’s not the first time that Apple says something inconsistent! The link you sent states:

      “The build string represents an iteration (released or unreleased) of the bundle and can contain a mix of characters and numbers, as in 12E123.”

      Then following “For details on possible values” link, it states that:

      “The build version number should be a string comprised of three non-negative, period-separated integers with the first integer being greater than zero.”

      Both were updated 2/11/14. I just sent feedback to Apple about this inconsistency. Thanks!

  2. krafty

    After testing this I’ve noticed that this script injects the build number only after the archive is created. In order for this to work, I would have to create 2 archives. The result of first archive changes the CFBundleVersion and the result of the second is an Archive I can use with the updated number.

    How can I get around this to only build it once? I’ve tried moving up the run phase in the build phases with no success.

  3. combinatorial

    If you add ${PROJECT_DIR}/${INFOPLIST_FILE} in the Output Files section for the script then dependencies work correctly and the build includes the updated plist before it builds the archive.

    • krafty

      Hmm…I had already set my ‘Info.plist File’ under Build Settings > Packaging to include the project directory. So when the posted script uses `${INFOPLIST_FILE}`, it is already referencing the project directory and is still showing the aforementioned behavior.

      So far my “solution” has been to remove the Release condition and to run in the simulator first before creating an Archive.

      • combinatorial

        The key is to have Info.plist in the “Output Files” section for the script, it is at the bottom of the “Run Script” section.

  4. krafty

    Oh I see, and thank you! Apparently all I had to do was scroll down! It looks like I’ve got it working properly now.

    Added tip: In my project I have two release schemas: (Release_Staging and Release_Live), so I modified the bash script to check that ${CONFIGURATION} starts with “Release”:

    #Update build number with number of git commits
    if [[ ${CONFIGURATION} == “Release”* ]] ; then
    buildNumber=$(git rev-list HEAD | wc -l | tr -d ‘ ‘)
    /usr/libexec/PlistBuddy -c “Set :CFBundleShortVersionString $buildNumber” “${INFOPLIST_FILE}”
    fi;

  5. Robert

    Slightly more simply, you can use git –rev-list –count HEAD to obtain the commit count.

Leave a comment