Automating versioning of your app

Versioning is without a doubt one of the most integrated parts of development cycle when it comes to publishing an app. Although Xcode leaves you wanting so much more than static numbers in default Info.plist for your project file. But to be fair, there are more than one ways to make versioning more automated and integrated into development cycle. It mostly depends on the taste of the developer. But basically it all boils down to 2 major steps:

  1. Get the version number from VCS;
  2. Inject that number into config file (Info.plist in our case) for display purposes;

Get the version number from VCS

There are a few ways to get the version number you need. In case you’re using git, the versioning number could be retreived using the following command:

$ git rev-list --count --all

Granted there are more than one way to do it, but for the purpose if this demo, I’m going to use this command.

This is the number of commits in the whole repository. If we set this number as value to CFBundleVersion key, we’d change the actual file and it would require another commit to confirm the changes. However this would change the number of commits and we’d have to change the number again and repeat the procedure ad infinitum.

Now that we’ve got the version, we should store it somewhere until it gets injected into Info.plist.

So the workaround for this is to inject the number directly into the Info.plist of built, but not signed product. After all, we DO have the $BUILT_PRODUCTS_DIR variable available to us in every build step. However we need not do this since Apple provides just the needed build settings for us in the target’s “Build Settings” tab. And that’s Info.plist preprocessing!

Change the settings

The settings you’re looking for are under Packaging section of Build Settings.

  • Info.plist Other Preprocessor Flags
  • Info.plist Preprocessor Definitions
  • Info.plist Preprocessor Prefix File
  • Preprocess Info.plist file

These settings are displayed if you search for “preprocessing” in the build settings editor window. We are going to change the last two.

Set the “Info.plist Preprocessor Prefix File” to “{Target}/{Target}_vers.h”. This is only one of the ways you can name that prefix file. You can name whatever you want as long as you use it later.

Set the “Preprocess Info.plist file” to “YES”.

And we’re done in the project settings. Next step - update the Info.plist file.

Setup the Info.plist file

It’s easiest to update the info from General tab of selected target. The value for “Build” field should contain the define name that you’re going to put into your Info.plist prefix file. So let’s name it {TARGET}BUILD_.

You must also add this file to .gitignore. This is needed because it will change with every build after a commit is done. Otherwise it will needlessly polute your index.

Put your version where the prefix file is

Now open up your current scheme and add “Run Script” pre-action in “Build” step. Add the following lines to the script

cd $SRCROOT  
TARGET_BUILD=`git rev-list --count --all`  
echo "#define {TARGET}_BUILD $BUILD" > $INFOPLIST_PREFIX_HEADER  

It’s self explainatory so I’m not goind to waste bytes explaining this to you.

Here are the advantages of this approach:
1. This file will be overwritten each time you hit build. Eventhough it will contain the same build version if you did not create any commits;
2. Since it’s in .gitignore it will not polute your index;
3. If you decide to add it to Xcode project’s file navigator (don’t forget to uncheck the target dependency checkbox) - you will be able to see which version your latest build product is by opening the file.

So that's all there is to it for injecting the version of your app into the app's bundle.

Retrieve the build number

The single line in Swift you can use to retrieve the build number is this:

NSBundle.mainBundle().objectForInfoDictionaryKey:"CFBundleVersion"