Monthly Archives: February 2015

Automatically Incrementing Version and Build Numbers

Something that crops up from time to time is the question as to how you can go about getting Xcode to automatically increment a target’s build and/or version numbers.

While I’m not claiming for an instant to be an expert in this regard, I can however provide a couple of potential solutions to the problem. There are of course plenty of other ways of achieving the same thing, so as always, find the one that suits your needs best! Furthermore, you don’t need to automatically change both of them, you can pick and choose which one(s) you want to maintain manually and which one(s) you want to have script.

So what are we talking about here? This:

About

In this example, the version number is 11.22.33 and the build number is 1HM735.

The significance of each one is hit-your-head-on-the-desk amusing. This Apple document explains that the version number “identifies a released version” of your app. The build number “identifies an unreleased or released version”. These definitions are echoed in the Information Property List Key Reference document.
Thanks.
Thwack.

Don’t put the ice pack back in the freezer just yet. Things are going to get even more head-scratchingly perplexing in the next section.

Yes, I do realise what they’re trying to say. It just doesn’t win any prizes for clarity. Moving right along…

In practice, the version number is indeed what the public sees as the version of the app they have; the build number is used for the developer’s internal housekeeping. Again, use it for whatever is meaningful to your situation!

The Fully Manual Approach

or a behind the scenes look at how it works

Setting both of these values manually is done in the General tab of the target’s settings.

General Tab

Along with most (or should that be all?) settings, these values are stored in the target’s Info.plist file. (They can also be viewed in the target’s Info tab.)
This is where you’re going to need that ice pack back. The following table shows the raw key names as they appear in the Info.plist alongside their “human readable” equivalents Xcode displays. For the record, it is possible to get Xcode to show the plist keys in their raw format by choosing Editor -> Show Raw Keys & Values.

  plist Key description
Version 11.22.33 CFBundleShortVersionString Bundle versions string, short
Build 1HM735 CFBundleVersion Bundle version

So yes, the version number is known as the short version, and the build number is known as the version.
Thwack.

From that though it should be pretty apparent how the process can be automated. It’s a simple matter of changing the value of the appropriate plist key whenever needed. For example, you could choose to increment the build number every time you do a “build and run”. For an extra layer of sophistication you could increment the version number but only when you do a release build (as opposed to a debug build).

The rest of this post will show two ways of doing it. The first one is a “roll your own” approach. It requires you to write a shell script and add it to the target’s build settings.

The second approach uses an Apple supplied command line tool called agvtool.

Roll, Roll, Roll Your Own Gently Down the Stack

The three issues to solve when creating your own solution are when, where and how.

We’re pretty limited in the “when” department. We want to update the values before the app is compiled so the compiled and running version reflects the numbers shown in Xcode. It is of course possible to change the values post build, but I find this confusing as the version you’re running shows different values to the settings in Xcode. However if it makes sense to do this in your situation, go for it. Just realise this article is all about making the alterations pre-compile.

Determining the when also narrows down the choice of where. There are two places to install pre-build scripts. The first is by adding a new Run Script phase to the Build Phases tab of the target. It is very important that once you add this run script phase you drag and drop it so that it is the second top item in the list of build phases (it is not possible to make it the first item). Each build phase is run in the order in which it appears in the list – we need our incrementing script to be run before anything else.

Add run script phase

The second location the script can be placed is in the target’s scheme. Add a new Run Script action to the pre-action entry in the Build action.

pre-build action

Deciding which location to choose is entirely dependent on the complexity and structure of your project. Putting it in the Scheme gives you a lot of flexibility with respect to when the script will be run. The combination of target and configuration gives you very fine grained control over the exact circumstance under which your script will be executed. For simpler projects in which you have nothing more than the default release and debug configurations, the build phases location is simpler. Also to my mind, this locates the script along side the rest of the build settings rather than in the out of the way scheme editor. For this example I’m locating it in the build phase.

That just leaves the how. As shown earlier, the version and build values are stored in the target’s Info.plist file. There’s a nice little command line utility called PlistBuddy that can be used to read, write and replace values in a plist file. All it takes now is to write a small shell script to do just that.

The Script

The most basic script will contain three lines. One to read the current value, one to increment it, and the last to write it out. The location of the plist file can be gleaned from the environment variables that Xcode sets every time it runs. The particular variable of interest in this case is PRODUCT_SETTINGS_PATH. If you want to see all the the vars that Xcode sets, add a line to the start of the script along the lines of:

An alternative to this is to tick the box “Show Environment Variables in Build Log” that appears just under where you type the script in in the build phase tab.

So without further ado here is the script at its most basic:

This however breaks if there has been no initial value assigned from within Xcode (or if it’s been deleted for whatever reason). Version 6.2 β4 does pre-fill the version and build fields to 1.0 and 1 respectively, but that’s not always been the case. The smart programmer will not rely on that behaviour! With that in mind a better version of the script can be written.

The Script II – Script Harder

PlistBuddy is kind enough to tell you if it cannot read a particular key. It does this by returning a non-zero return value. By looking at this return value, we can take defensive action and deal with the key not being present. The new and improved script might look something like this:

We could of course keep going. There still plenty of error conditions that could be encountered – what if the current build number is a decimal? Or contains non-numeric characters? It is not the intention of this article to be an exercise in shell scripting or edge cases. Hopefully though this is a good starting point from which to build your own robust little script.

The Script: With a Vengeance

So far all that’s been incremented is the build number. It doesn’t make much sense to increment the version with every build. It’s probably best to manually manage the build numbers, but as an exercise in demonstrating what’s possible, the third and final version of the script increments the version number only when building a release configuration, not a debug one. The build number still gets updated with every build.

Achieving this is done with a little bit more help from the environment variables that Xcode sets. This time CONFIGURATION is the variable to look at.

The addition to this version of the script does lack some of the sophistication needed in the real world. Version numbers pretty much always tend to be floating points for a start. Bash only deals with integers so you’d need to look at alternatives like zsh, writing the script in python (or some other language) or making use of the command line utility bc. The same caveats also still apply as with version two with respect to non-numerics. This is just meant to be a start!

You’ll probably need to manually manage the version number when you need to bump it up to the next major release. If you really, really wanted to automate that too, you could add a user variable to the target’s build settings and set it to a particular value when you want to do the major version bump. The script could check for the presence of this variable and its value and perform the major bump for you.

agvtool – Apple Generic Versioning Tool

Apple provides instructions for using agvtool in the Technical Q&A QA1827 document. As the document explains, there’s a three step process to setting up a project before you can use agvtool. To quickly sum up, they are:

  1. In the Build Settings tab of any target, search for CURRENT_PROJECT_VERSION (Current Project Version) and set its value to a number of your choice.
  2. Now search for VERSIONING_SYSTEM (Versioning System) and change its value to “Apple Generic” using the drop down box.
  3. For each target, set the initial Version and Build number in the General tab (or in the Info.plist directly).

Before looking at how the tool works, it’s worth having a look at the following table in order to understand what the tool means by the word “version” and all variations on that theme.

Name Value in “about” screenshot Location Stored as
Project Version project.pbxproj CURRENT_PROJECT_VERSION
Version 11.22.33 Info.plist CFBundleShortVersionString
Build 1HM735 Info.plist CFBundleVersion

With that as clear as mud, let’s look at using the tool itself. The first thing to keep in mind is that using this tool does not get you out of having to write a script. For all the setting up and choosing versioning types, you still need to either run agvtool manually or script it. In the shell script, you will need to cd to the project’s directory (the directory containing the .xcodeproj file) before you use the tool. You can find out the path to the project file from the PROJECT_FILE_PATH environment variable that’s set by Xcode and available to the script.

In many ways the tool doesn’t quite act as you might expect. Surprises include:

  1. You cannot set or increment the version or bundle numbers on a per target basis. Setting or incrementing one alters them all. That is quite often not what you want.
  2. You cannot increment the version number at all. You can only set it.
  3. Incrementing the build number doesn’t really increment it at all. What it does is increment the project number, and then set the build numbers for all your targets to be the same as the project number.
  4. You cannot change the build number without also changing the project version.
  5. The fact that while you can query the tool to return the current project version and version, you cannot query it for any build versions.
  6. It only deals in integers. Any floats will be rounded up.

If all of that sounds confusing, that’s because it is. Here is Yet Another Table that will hopefully illustrate which command achieves which effect.

  View Set Increment
Project what-version new-version next-version
Version what-marketing-version new-marketing-version
Build new-version -all next-version -all

It does however handle strings that contain non-numerical characters (i.e. letters). If it encounters something like 123ABC, it strips the trailing letters and increments the number to return 124. If it encounters something like BN78AA, it simply sets the new version to 1.

That actually brings up an interesting point. Just what constitutes a valid version or build number?

Valid Numbers

There are plenty of guidelines that suggest you use a three part float such as 1.2.3. What else is valid?

Mac OS X apps that you release yourself (i.e. not via the Mac App Store) have no practical limitations. It even handles emoji!

It is however a very different story when you’re trying to submit to the App Store. If your build or version numbers are not an integer or floating point number you’ll get the following error:

Bad build number

Keep in mind that while during production you can use any combination of numbers, letters, emoji, Egyptian hieroglyphs or wood carvings, any attempt to upload that to the App Store will result in the aforementioned error.

In closing

I personally see no real use for agvtool. It seems rather limited in functionality, can catch you out in surprising ways and doesn’t seem to offer any benefits over rolling your own script. Writing your own script gives you flexibility limited only by your scripting skills. I highly recommended it as the best solution for this particular job.

As always, questions and comments welcome!