You are currently browsing the monthly archive for October 2012.

New! Improved! Or how it should be done in the first place!
Previously I wrote an article about integrating Git and Xcode: Displaying Git details in XCode 4.2 projects. That was a good approach but after writing it I realized that I had only completed half the task I should have, and this article addresses how I should have done it.

What I was trying to do in that previous article was to automatically include versioning information from the Git repository into the iOS application. In doing so I could then explicitly match any version of the App in the wild to the actual code used to produce that App – which would aid greatly in diagnosing any reported bugs. The script I provided extracted the last commit date and hash as well as the last tag inserted into the repository. It then generated an include file that imported the data as Objective-C string literals that could be used directly by the code.

This process worked well, but it wasn’t until I went to upgrade an App in the App store that I saw what was missing from what I did. The problem was that even though I was including the version information in the code itself, the version information that Apple wanted needed to be included in Info.plist file for the App. So regardless of my smarts, I was still left with having to manually update the version and build information in XCode prior to submitting an Archive to Apple.

So this article addresses the deficiencies of the previous article and shows how to automatically inject information into the Info.plist file and achieve a “hands-off” approach to tagging your Apps with their build number. And while I am using Git to supply that data, this build number could come from any source you liked.

What needs to change
The version information that I ultimately want to automatically set are two values in the Info.plist file for the project. These are (with descriptions taken from Apple):

CFBundleVersion Specifies the build version number of the bundle, which identifies an iteration (released or unreleased) of the bundle. This is a monotonically increased string, comprised of one or more period-separated integers. This key is not localizable.
CFBundleShortVersionString  Specifies the release version number of the bundle, which identifies a released iteration of the app. The release version number is a string comprised of three period-separated integers. The first integer represents major revisions to the app, such as revisions that implement new features or major changes. The second integer denotes revisions that implement less prominent features. The third integer represents maintenance releases.

The value for this key differs from the value for “CFBundleVersion,” which identifies an iteration (released or unreleased) of the app. This key can be localized by including it in your InfoPlist.strings files.

Within an XCode project, CFBundleShortVersionString and CFBundleVersion can be found in these locations:

Info.plist field Summary Name Info name
CFBundleShortVersionString Version Bundle version string; short
CFBundleVersion Build Bundle version

 

But first a word from your sponsor
In scanning various websites I have seen a lot of pages that want to achieve the same outcome as what I want. However A lot of them are based on executing /usr/libexec/PlistBuddy in order to directly insert data into the Info.plist file. This works, but to me it suffers from some deficiencies:

  1. Directly rewriting a file in the project is a brute force approach.
  2. If the project is under version control, then the act of building the project changes the version history.
  3. Because the value is changed “behind the scenes”, when looking at the project in Xcode it isn’t obvious that the values in question are being changed.
  4. It ignores the mechanism that Apple already provides for updating the file.

So with that said I am going to charge my solution with:

  1. Using the Apple supplied method for changing the Info.plist file
  2. Raising the visibility of the data that is being automatically changed
  3. Not changing the project’s version history when simply building the project

The true path
The key to doing things the Apple way is to note two options in a project’s build settings:

  • Preprocess Info.plist File
  • Info.plist Preprocessor prefix file

Setting the first one to “yes” turns on the processing of the Info.plist file and perform the substitutions of symbols for values, while the second setting designates the file that contains the required #defines that link the symbols with their values.

In my case (as per my previous article) I set the prefix file to be “gitDataAutoGenerated.h”, so that all I need to do is to generate this file prior to the Info.plist preprocessing during the building of the project. Within that file I created a #define called AUTOVERSION which I matched up with my auto created version number extracted from the Git repository for the project. Then in the the appropriate spots on the projects Summary or Info Page, I set the value of the CFBundleVersion and/or CFBundleShortVersionString to be “AUTOVERSION” (with no quotes) and then everything magically gets linked up when I build the project.

And as “gitDataAutoGenerated.h” is not a part of the project, it can be changed with impunity without affecting the project’s version control history.

First things first
As per my previous article, I had created a “run script” build phase within the project that hosted the script that generated the “gitDataAutoGenerated.h” file. But this approach totally failed when it came to modifying the Info.plist file. After inspection of the build output messages, it was obvious that even though I had set up the script to run prior to any compilation, the preprocessing of the Info.plist file was actually being run before the script. So it was time for plan “B”

Plan “B” is to create an additional target within the project that is dedicated to building the include file, and to make the App target dependent on the new script target. The process for doing this is:

  1. Add a new “Aggregate” target to the project called “VersionNumberScripts”
  2. Within the Build phases of this target, create a “Run script” phase and either copy over the the script from the equivalent phase from the App, or create the script from scratch. (If the script was copied, then the App’s run script phase can be deleted.)
  3. Within the build phases of the App, add the new VersionNumberScripts target as a Target Dependency

Once this is done the version number script will be run prior to the App target being built, thus ensuring that the correct information is ready for XCode to preprocess the Info.plist file in a timely manner.

Following the script
While the script from the previous article was fairly decent, I have updated it to be a little more robust by including default values for if/when the git commands fail to return any data. This new script is:

# build data file that is included in the source
# so we can automatically report Git repo information
# in the application

echo "Building file"

cd ${PROJECT_DIR}/${PROJECT_NAME}
gitDataFile="gitDataAutoGenerated.h"


echo "Get Information from system"

# Date and time that we are running this build
buildDate=`date "+%F %H:%M:%S"`

# Current branch in use
currentBranchTemp=`git rev-parse --abbrev-ref HEAD`
if [ -n "$currentBranchTemp" ]
 then
  currentBranch=$currentBranchTemp
 else
  currentBranch=""
fi

# Last hash from the current branch
lastCommitHashTemp=`git log --pretty=format:"%h" -1`
if [ -n "$lastCommitHashTemp" ]
 then
  lastCommitHash=$lastCommitHashTemp
 else
  lastCommitHash=""
fi

# Date and time of the last commit on this branch
lastCommitDateTemp=`git log --pretty=format:"%ad" --date=short -1`
if [ -n "$" ]
 then
  lastCommitDate=$lastCommitDateTemp
 else
  lastCommitDate=""
fi

# Comment from the last commit on this branch
lastCommitCommentTemp=`git log --pretty=format:"%s" -1`
if [ -n "$" ]
 then
  lastCommitComment=$lastCommitCommentTemp
 else
  lastCommitComment=""
fi

# Last tag applied to this branch
lastRepoTagTemp=`git describe --abbrev=0 --tags`
if [ -n "$lastRepoTagTemp" ]
 then
  lastRepoTag=$lastRepoTagTemp
 else
  lastRepoTag="0.0.0"
 fi

# Build the file with all the information in it
echo "Create header file"

echo -e "//-----------------------------------------" > $gitDataFile
echo -e "// Auto generated file" >> $gitDataFile
echo -e "// Created $buildDate" >> $gitDataFile
echo -e "//-----------------------------------------" >> $gitDataFile
echo -e "" >> $gitDataFile
echo -e "#define BUILD_DATE              @\"$buildDate\"" >> $gitDataFile
echo -e "#define GIT_CURRENT_BRANCH      @\"$currentBranch\"" >> $gitDataFile
echo -e "#define GIT_LAST_COMMIT_HASH    @\"$lastCommitHash\"" >> $gitDataFile
echo -e "#define GIT_LAST_COMMIT_DATE    @\"$lastCommitDate\"" >> $gitDataFile
echo -e "#define GIT_LAST_COMMIT_COMMENT @\"$lastCommitComment\"" >> $gitDataFile
echo -e "#define GIT_LAST_REPO_TAG       @\"$lastRepoTag\"" >> $gitDataFile
echo -e "#define AUTOVERSION             $lastRepoTag" >> $gitDataFile


# Force the system to process the plist file
echo "touch plist"
touch ${PROJECT_DIR}/Info.plist

Note that this script does depend on the target name of the App being the same as that of the overall project.

An example of this scripts output is:

//-----------------------------------------
// Auto generated file
// Created 2012-10-24 15:57:47
//-----------------------------------------

#define BUILD_DATE              @"2012-10-24 15:57:47"
#define GIT_CURRENT_BRANCH      @"versionNumber"
#define GIT_LAST_COMMIT_HASH    @"2a92057"
#define GIT_LAST_COMMIT_DATE    @"2012-10-24"
#define GIT_LAST_COMMIT_COMMENT @"Fixed layout of About view controller"
#define GIT_LAST_REPO_TAG       @"1.1.0"
#define AUTOVERSION             1.1.0

Tag! You’re It
So you now have this great script that will extract tags from the project’s Git repository and insert them in the App as needed. You successfully build the App and look at the result .. but .. but ..where’s the tags?!?!??! why are they missing?!?!? I know that I tagged the release in Git.

And that’s what happened to me when I was building this system. I had tagged the repository, I had built the release, but there were no tags to be seen. The issue turned out to be where I was building the App.

At the same time I was creating this new build process, I was also setting up an additional computer for development, and I was testing the new build system on this new computer. In order to transfer the project’s Git repository to the new computer, I created a local Git server (well really a user named git on my main dev computer), pushed the project from the original system to the git server, and then cloned the project onto the new computer. Simple! Easy! Allows me to work on multiple computers! All sorts of possibilities now start to emerge.

What I hadn’t realized was that if you push a repository to another location the tags don’t get pushed. You have explicitly push the tags in a separate command. See 2.6 Git Basics – Tagging for a complete explanation on tagging.

So in addition to doing something like this:

git push origin master

I also have to do

git push origin --tags

It kinda sucks splitting the functionality up like this, but at least I learnt something new about git.

Last things last
Well that’s a wrap on my new build system. Given how easy it was to set things up the right way I’m kicking myself for not doing this the first time around. Now I have this nailed down I’m in the process of updating my NotchFlow App to run under iOS 6 (and with the new 4″ retina screen resolution). So look forward to update on that App soon.

Finally I have to put in a plug for 89Paint in Richmond. They are sponsoring the local Gangplank group and I have been working out of their offices this week – it sure beats sitting at home by myself talking to the cat!

Advertisement