Writing on Tablets Tech stuff from @yeltzland

Strategy for automatically setting app version numbers

Auto-incrementing version numbers in apps using the number of Git revisions

One of the minor pain points for an app developer is managing app version numbers.

How I try to minimise the work needed when updating my apps is to automate this as much as possible, so I thought it would be useful to document how I do this, for my reference as much as yours.

iOS

Reusing the same version number across multiple targets

On iOS, if you have multiple targets in your solution, you need to ensure all the extensions have the same version number.

This can be quite a long list of things to keep in sync - for example, my Yeltzland app has 6 targets that need to have an identical version number (the app itself, Today widget, Watch app and extension, Siri intent and Siri intent UI)

I make this easier by having a user defined build property called "SHARED_VERSION_NUMBER" set at the Project level in Xcode.

User defined build property

I then use this variable in the Info.plists for each target as shown below.

Using the build property

Obviously this means I just need to update the version number in one place to automatically update it in all the targets.

Setting the build number

As I said above, I set the build number to be the number of Git revisions in the repo so far.

This guarantees a unique and incrementing build number for releases, and a informative number that helps when communicating with users about what version of the app they are running/testing.

I do this by adding a custom Build Phase to each of my targets that:

  1. Uses git rev-list --all --count to get the current number of git revisions in the repo
  2. Sets the ‘CFBundleVersion’ property in the Info.plist file to this number, using the PlistBuddy utility

Here’s the script I use:

git=`sh /etc/profile; which git`
appBuild=`$git rev-list --all --count`
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
echo "Updated ${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"

Android

My Android apps are usually more straightforward, with a single build.gradle file used to set the versionCode and versionName for the app.

I use this simple build function in the app’s build.gradle to get the git revision number:

static def generateVersionCode() {
    def result = "git rev-list --all --count".execute().text.trim()
    return result.toInteger()
}

def majorVersion = "4.1.2"

...

android {
    ...

    defaultConfig {
        ...
        versionCode generateVersionCode()
        versionName "${majorVersion}.${generateVersionCode()}"
        ...
    }
    ...
}

Code adapted from this Gist

Xamarin

I have a Xamarin iOS and Android native app - inherited from another company - so wanted to use a similar strategy for incrementing the build numbers.

This solution isn’t as nice - that can probably be said for Xamarin in general! - but it does work.

It involves manually adding build steps into the iOS and Android project files, that use the now familiar git command to get the build number we need, then using XmlPoke commands to set the values in either the iOS Info.plist or the Android AndroidManifest.xml files.

The pain is it changes the actual files in the repo - not just setting values at build time - so the Info.plist and AndroidManifest.xml files are continually being changed in the repo.

Below are what I added to my two project files for reference …

iOS Project file

<Target Name="BeforeBuild">
 <Exec Command=“git rev-list --all --count” ConsoleToMSBuild="true">
  <Output TaskParameter="ConsoleOutput" PropertyName="APPBUILD" />
 </Exec>
 <PropertyGroup>
  <VersionNumber>1.0.0</VersionNumber>
 </PropertyGroup>
 <XmlPoke XmlInputPath="Resources/Info.plist" Query="//dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" Value="$(APPBUILD)" />
 <XmlPoke XmlInputPath="Resources/Info.plist" Query="//dict/key[. = 'CFBundleShortVersionString']/following-sibling::string[1]" Value="$(VersionNumber)" />
</Target>

Android Project file

<Target Name="BeforeBuild">
 <Exec Command=“git rev-list --all --count” ConsoleToMSBuild="true">
  <Output TaskParameter="ConsoleOutput" PropertyName="APPBUILD" />
 </Exec>
 <PropertyGroup>
  <VersionNumber>1.0.0</VersionNumber>
 </PropertyGroup>
 <XmlPoke XmlInputPath="Properties\AndroidManifest.xml" Namespaces="&lt;Namespace Prefix='android' Uri='http://schemas.android.com/apk/res/android' /&gt;" Query="manifest/@android:versionCode" Value=“$(APPBUILD)” />
 <XmlPoke XmlInputPath="Properties\AndroidManifest.xml" Namespaces="&lt;Namespace Prefix='android' Uri='http://schemas.android.com/apk/res/android' /&gt;" Query="manifest/@android:versionName" Value="$(VersionNumber).$$(APPBUILD)" />
</Target>

Summary

I’m very pleased I’ve got all this working (particularly in Xamarin), and I’d defintely recommend using an approach like this for your apps.

iOS Android Xamarin