Versioning and Xcode

If you’ve ever used agvtool to manage versioning your project you know it’s a pain. Among other complaints, I’d rather not have version information buried in project files (or have to modify that project file to bump the version).

 Pre-process Info.plist

My initial reaction was to extract the version into something like an xcconfig file and use plist pre-processing to inject it into the Info.plist. The issue there is that Xcode doesn’t correctly pick up changes to the xcconfig file (it’s presumably looking at mod times of the Info.plist file itself), so any version bumps require a clean build to pick up.

 Build Rule

My next thought was to use a build rule to process the Info.plist file manually. The downside there is that you need to change the INFOPLIST_FILE build setting to point to the processed file in the derived sources directory, but doing so disables Xcode’s built-in editing capabilities (e.g. the “Info” tab in the target settings).

 Chrome

After digging around, I found this gem in the Chrome source code:

#
# Xcode supports build variable substitutions and CPP; sadly,
# that doesn't work because:
#
# 1. Xcode wants to do the Info.plist work before it runs any
#    build phases, this means if we were to generate a .h file
#    for INFOPLIST_PREFIX_HEADER we'd have to put it in another
#    target so it runs in time.
# 2. Xcode also doesn't check to see if the header being used as
#    a prefix for the Info.plist has changed.  So even if we
#    updated it, it's only looking at the modtime of the
#    info.plist to see if that's changed.
#
# So, we work around all of this by making a script build phase
# that will run during the app build, and simply update the
# info.plist in place.  This way by the time the app target is
# done, the info.plist is correct.
#

Chrome’s solution is simply to edit the plist file in the built product(s) in-place, after all other processing has already been done. ?

Chrome keeps its version information in chrome/VERSION:

MAJOR=41
MINOR=0
BUILD=2251
PATCH=0

It uses a couple of Python scripts to read in the contents of that file and inject it into the Info.plist.

 End Result

I’ve adopted a simplified version of that approach:

#!/bin/sh

# Read in the version information
vers="$SOURCE_ROOT/VERSION"
source $vers

# Update the plist
plist="$TARGET_BUILD_DIR/$INFOPLIST_PATH"
plutil -replace CFBundleVersion -string "$MAJOR.$BUILD" $plist
plutil -replace CFBundleShortVersionString -string "$MAJOR.$MINOR.$PATCH" $plist

The VERSION file is easy to parse with any external tools and can also be loaded into the bash script as variables via the source command.

 
9
Kudos
 
9
Kudos

Now read this

When Full-Disk Encryption Goes Wrong

Last September I watched in horror as my MacBook Pro slowly died. It started with a text from my wife: “Hmmm,” I thought. “Maybe the System folder or something got corrupted. Should be a quick fix.” Little did I know that we had just... Continue →