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!


Vector images in the Asset Catalogue

Amongst Xcode 6’s list of new features is the ability to provide a single vector based image and have Xcode generate the @2x @3x images for you.

TL;DR Instructions

  1. Acquire a vector PDF. Loading an existing image and doing something like “save as” or “export to” PDF is not sufficient.
  2. Place your vector PDF in Xcode’s asset catalogue.
  3. In the Attributes Inspector, set the type of the image to “Vector” using the drop down menu.
  4. Create a UIImageView whose dimensions are the same as your vector image (or set the content mode to a non-scaling mode).

If you can’t get it to work with your image, trying using this one. If it works, the problem lies with your vector image. This section of this document provides some hints for creating vector PDFs.

I’ve uploaded a project to github with a working vector PDF.

A slightly more detailed explanation.

The vector PDF

This seems to be the hardest part to get right. It took me several attempts to get an image that would work. I’m sad to say I don’t know exactly what it is that I did to get the one that works, working. The solution seems to lie in knowing which options to choose (or rather, to not choose) when saving the image.

If you pull an image off the ‘net in SVG format, the workflow goes something like the following:

  1. Open in Adobe Illustrator. (If you don’t have Adobe Illustrator I have no idea what alternatives are available. If anyone has a favourite, post a comment!)
  2. Transform/scale your vector image so that its dimensions match the dimensions that you want your @1 image to have. This is crucial. Getting this bit wrong is guaranteed to cause the whole thing to fall over in a screaming heap.
  3. File -> Save as and choose PDF. In the save options, untick all the options, especially the “embed thumbnails” and “preserve editing capabilities”.

    Adobe Illustrator save PDF options.

    Adobe Illustrator save options.

In Xcode

Add your vector image to your asset catalogue:

  1. Create a new image set and name it appropriately.
  2. In the Attributes Inspector down the right hand side, make sure the “Types” drop down box is set to “Vectors”.
  3. Drag and drop your vector PDF into the asset catalogue.
Xcode 6 asset catalogue with vector PDF.

Asset catalogue with vector PDF. Click to view at full size.

(Yes, you can combine steps one and three above. Dragging and dropping an image into the image set list on the left hand side will automatically create a new image set and give it the same name as the image file.)

In your Storyboard/Xib

Add a UIImageView. The dimensions of the imageView need to be the same as the dimensions of your vector PDF. This is another crucial point – failure to do this will result in a screaming heap of blurred images.

Let’s illustrate the above with an example.

The horse image that I’ve used is 200px x 200px – the Attributes Inspector in the asset catalogue tells me so. In my storyboard, I now need to create a UIImageView whose dimensions are also 200×200.

To understand this, a small explanation of how the vector PDF is used by Xcode is in order. Do note however that this is rather speculative. I’ve not seen formal documentation anywhere explaining how it works behind the scenes. This is nothing more than informed speculation.

Xcode takes your vector PDF and looks at its dimensions. In this case, it sees it as being 200×200. During the build phase of the compile and run process, it takes that PDF and from it, generates three PNG images. One at 200×200, a second at 400×400 (the @2x) and a third at 600×600 (oddly enough, the @3x). These three images are then compiled into the asset catalogue and when your programme runs, the usual image loading mechanism takes over. Depending on the device you’re running on, the app will select and display the @1x, @2x or @3x as required.

The key point to take away from that preceding paragraph is that the images displayed by the phone are not not not generated on the fly. They are created at build time.

Therefore, given my PDF with dimensions of 200×200, if I use an imageView with differing dimensions, the chosen PNG is going to be scaled – by default the content mode is “scale to fill”. Even if you choose a different scaling mode, the net result is that it will still be scaled and you will still get a blurred image.

If for whatever reason your imageView has dimensions that don’t match that of the PDF, choose a non-scaling content mode such as (but not limited to) “centre”, “top”, “bottom right”.

Sample project

There is a sample project up on github.

The first tab uses the horse PDF whose imageView is a matching 200px x 200px. If you view the project on different devices you will see it scale nicely.

The second tab uses a vector PDF stolen from the Mozilla project. This tab illustrates what happens when the dimensions of the PDF and the imageView differ, but the content mode is set to a non-scaling mode (in this case, “bottom right”).

For groans and giggles change the content mode of the Dino tab to “aspect fit” or similar.