Automating Xcode Project Version Increments With Shell Scripts

Posted 8/23/2016.

Goal: Automate version and build number increments for a Xcode project using a shell script.

In my last tutorial, I explained how to make a private, cross-platform Swift framework using Github and CocoaPods. You can find that here.

I've made several frameworks and found myself going through the same process every time I wanted to update one of them to a new version. After finishing the code updates, I would increment the version number for each target (iOS, macOS, tvOS), update the version in the corresponding Podspec file, commit the version update, push it to Github, tag the new version, and update the private specs repo. The process is not difficult, but it takes time and must be explained to and followed by each person working on the framework.

So let's learn how to automate this process using a shell script and the built-in "defaults" command in macOS. In this tutorial we'll increment the version and build numbers of a Xcode single view application, but you can write a script to automate any changes you need to make regularly to a project's plist file and add other commands that complete your workflow (e.g. pushing to Github).

First we're going to create our single view application in Xcode. I named mine "ScriptTest" and put it in my Documents folder. Next, open Terminal and navigate to where you just put your new Xcode project. In your text editor of choice, create a new file called "xcode-version-increment.sh" and add the following line:

#!/bin/sh

Great, we have our empty shell script. Now let's make it executable by typing the following into Terminal

chmod a+rx xcode-version-increment.sh

"chmod" is the command for modifying file and directory access permissions. "a+rx" means that all users can read and execute the file.

Our first goal is to increment the version number of our application. Because iOS apps typically use semantic versioning (major version, minor version, patch e.g. 2.1.0), it's not as simple as incrementing one number. So in our script we're going to allow the user to specify the new version number as a parameter.

Shell scripts give a number to each parameter you pass on the command line, which you can then access in the script. These are called positional parameters. They are zero-based and include the name of the script. When we run our script we're going to type the following in Terminal:

./xcode-version-increment.sh 1.0.1

In this example "./xcode-version-increment" is parameter 0 and "1.0.1" is parameter 1.

Now try adding the following to your script:

VERSION_NUMBER=$1
echo "Released $VERSION_NUMBER"

Here we're simply defining a variable called "VERSION_NUMBER" equal to whatever we pass in as the first parameter and then printing it using the "echo" command. If you run the script as shown above with "1.0.1" Terminal should print "Released 1.0.1".

Next we're going to read the build number from our project's Info.plist file so we can increment it before writing it back to the plist. We'll use the "defaults" command for this. But first, we need to get the plist's path. Since the defaults command uses the path without its extension, we'll define two variables as follows:

INFO_PLIST="${HOME}/Documents/ScriptTest/ScriptTest/Info"
INFO_PLIST_EXT="${INFO_PLIST}.plist"

This will allow us to use the path both with and without the extension. Now we can read and increment the build number using the key Apple has defined for build number, CFBundleVersion.

BUILD_NUMBER="$(defaults read $INFO_PLIST CFBundleVersion)"
((BUILD_NUMBER+=1))

Finally, let's write the version and build numbers back to the plist (CFBundleShortVersionString is Apple's key for version number).

defaults write $INFO_PLIST CFBundleShortVersionString $VERSION_NUMBER
defaults write $INFO_PLIST CFBundleVersion $BUILD_NUMBER

The defaults command writes a binary file, so there's actually one more step so that we can keep the Info.plist file readable and comparable using source control. We'll use the plutil command to convert the plist back to XML.

plutil -convert xml1 $INFO_PLIST_EXT

And that's it. Let's add in some print statements and run the script.

echo "Released $VERSION_NUMBER"
echo "Incremented build number to $BUILD_NUMBER"

Run:

./xcode-version-increment.sh 1.0.2

You should see "Released 1.0.2" and "Incremented build number to 2". Here's the full script:

#!/bin/sh

VERSION_NUMBER=$1
INFO_PLIST="${HOME}/Documents/ScriptTest/ScriptTest/Info"
INFO_PLIST_EXT="${INFO_PLIST}.plist"
BUILD_NUMBER="$(defaults read $INFO_PLIST CFBundleVersion)"
((BUILD_NUMBER+=1))

defaults write $INFO_PLIST CFBundleShortVersionString $VERSION_NUMBER
defaults write $INFO_PLIST CFBundleVersion $BUILD_NUMBER
plutil -convert xml1 $INFO_PLIST_EXT

echo "Released $VERSION_NUMBER"
echo "Incremented build number to $BUILD_NUMBER"