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).
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
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
End Result #
I’ve adopted a simplified version of that approach:
- In the root directory of my project I have a
VERSIONfile, structured much the same way as Chrome’s.
- I’ve removed
Info.plist(as they’re going to get overwritten anyway).
- I added a run script build phase to propagate the version from that file into the plist:
#!/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
VERSION file is easy to parse with any external tools and can also be loaded into the bash script as variables via the