Programming, automation, algorithms, macOS, and more.

Increment build number for deployment builds

If we want to distribute “deployment builds” regularly (e.g. to our betatesters) it pays to automate the process. A deployment build can be done from the command line using:

xcodebuild clean
xcodebuild -buildstyle Deployment

But if we do this, we probably also want to bump the version number, or at least the build number, so that we can distinguish between the different builds.

The version string of your program is stored in the Info.plist file as the CFBundleVersion key/value pair. By default this file is in XML, but to make it easier to work with, we can convert it to the old-style ascii format using the pl command. Example:

% pl < Info.plist | grep CFBundleVersion
   CFBundleVersion = "1.0b0";

I use “major.minor” followed by a b (for beta) and then the build number, which is consistent with how Apple wants you to use CFBundleVersion.

To increase this number (from a shell script) we can use the ability in perl to execute code as part of the format string (when doing regular expression substitution), example:

% pl < Info.plist \
 | perl -pe 's/(CFBundleVersion = ".+\D)(\d+)(";)/$1.($2+1).$3/eg' \
 | grep CFBundleVersion
    CFBundleVersion = "1.0b1";

The regular expression pattern finds the last numeric part of the version number and increments it, so if we’d used “1.0.1” or “1.0” instead, it would also work. I have added the latter grep only to limit the output here, in your script you’d probably do:

mv Info.plist Info.plist_backup && pl < Info.plist_backup \
 | perl -pe > Info.plist 's/(CFBundleVersion = ".+\D)(\d+)(";)/$1.($2+1).$3/eg'

There is one other place where the version number of your program may exist, and that is in the InfoPlist.strings file, which contain localized versions of your Info.plist values. I have solved this by having a file called InfoPlist_in.strings containing the following:

CFBundleName = "TextMate";
CFBundleShortVersionString = "${TM_SHORT_VERSION}";
CFBundleGetInfoString = "TextMate version ${TM_SHORT_VERSION}, Copyright ${TM_YEAR} MacroMates.";
NSHumanReadableCopyright = "Copyright ${TM_YEAR} MacroMates.";

Basically just holding placeholders for the variables. And then to produce the correct file I again turn to perl and do:

printf > English.lproj/InfoPlist.strings "\xfe\xff"
TM_YEAR=`date +%Y` \
TM_SHORT_VERSION=`sed -n < Info.plist 's/^.*CFBundleVersion = "\(.*\)[abdf].*".*$/\1/p'` \
perl < InfoPlist_in.strings -pe 's/\$\{([^}]*)\}/$ENV{$1}/g' \
 | iconv >> English.lproj/InfoPlist.strings -f utf8 -t ucs-2

The first line (the printf) is just to get a unicode byte order marker (BOM), then I setup a TM_YEAR and TM_SHORT_VERSION variable (this is how it is done in bash) and call perl to substitute all occurences of ${...} with the actual value of the variable, and I run this through iconv to convert the result to ucs-2 (UTF-16).

{{ numberOfCommentsTitle }}

{{ submitComment.success }}

Error Posting Comment

{{ submitComment.error }}