You are currently browsing the category archive for the ‘XCode’ category.
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:
- Directly rewriting a file in the project is a brute force approach.
- If the project is under version control, then the act of building the project changes the version history.
- 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.
- It ignores the mechanism that Apple already provides for updating the file.
So with that said I am going to charge my solution with:
- Using the Apple supplied method for changing the Info.plist file
- Raising the visibility of the data that is being automatically changed
- 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:
- Add a new “Aggregate” target to the project called “VersionNumberScripts”
- 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.)
- 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!
NOTE that this code has been updated to support iOS6 and the 4 inch retina screen
Apple shakes it up – sort of
The App that I am currently working towards will use some accelerometer based gestures in order to configure some of its aspects in a fun way. So in looking around for example code that shows the response from the accelerometers I of course quickly found Apple’s sample App – AccelerometerGraph.
In my mind this App provides only the bare minimum of information and configuration in displaying the accelerometer data. I also think the actually graphing of the traces is not the best as:
- You can’t tell what axis is represented by which trace
- By grouping the raw data into one graph and the filtered data into the other graph you are really grouping unlike data together
But that’s my opinion. Here is a screen shot of AccelerometerGraph so you can form your own opinion:
After playing around with this App for a bit I decided that I wanted a test bed with a bit more flexibility and one that also conformed to my ideas of data display bets practices. Hence the idea for Shake was born.
We can re-build it
My design goals for my improvement on Apple’s App included:
- Split the graph into 3 traces – one for each axis
- Each graph would display raw, filtered and RMS values of the data with color coded traces
- Be able to select input data from the actual accelerometer input as well as three other calculated data sources: sine wave, step and impulse
- Implement the same filters as Apple, but also implement additional filters of my own choosing
- Calculate the RMS values of the filtered data over a moving window of arbitrary length
- Detect when the RMS value has exceeded a threshold value for a contiguous number of samples
- Be able to easily configure each axis both independently or all at the same time
- Release the source code of the resultant program (see right at the end!)
I managed to achieve all of this with the exception that I did not implement the adaptive aspect of Apple’s filters. I’m going to have to admit that when I got around to doing this I realized that my overall architecture was not conducive to doing so in an efficient way – and I was feeling way to lazy to re-engineer the needed changes!
With all of that said, revealed here for the first time are the beautiful images of my latest creation!
![]() |
![]() |
|
![]() |
![]() |
|
![]() |
![]() |
|
![]() |
Step right up .. pick your Axis
When enter the setup for Shake! the first screen you are presented with is for the over-all configuration of each axis as well as the “All Axes” choice. By selecting a single axis, you will head down the path of configuring only that one axis. The “All Axis” choice is special in that any configuration choices made here will be copied to the other 3 axes – however you will need to confirm this action before the copying occurs.
This screen also indicates that the sampling frequency of the overall Shake! system is fixed, and not changeable. This constraint comes about because the additional filters I included are designed to operate at a fixed sampling frequency – thus if that frequency is changed then the filters will need to be redesigned. So I had the choice of either including the filter design software that I used (and more of that later) or in being lazy and fixing the sampling frequency to what Apple used for AccelerometerGraph (which is also fixed!)
Finally this screen shows the meaning of each trace color used on the graphs on the main screen:
- Red – the raw signal used for this axis
- Green – the output from filtering the raw signal
- Blue – the RMS value of the filtered signal
- Pink – the detection of the RMS level exceeding its threshold
Axis Of Setup
For each of the axes the configuration choices are split up into 3 main sections:
- Signal source
- Filter Type
- Level Detection
And that’s about all you need to know!
Fresh Signal Sources! Get Your Fresh Signal Sources!
Within the Signal Source setup, there are 4 choices that can be made:
- The actual data from the accelerometer for the axis
- A calculated Sine wave generator, for which you can set the amplitude and frequency (within limits)
- A calculated step generator, for which you can configure nothing!
- A calculated Impulse generator, again for which you can’t configure anything
The limits of the sine wave generator are that the amplitude has to be between 0 and 2g, and the frequency has to be between 0 and 30 Hz (half the Nyquist frequency for the system in order to eliminate reflections in the frequency domain).
In addition, all of the calculated signal sources output a fixed number of zero samples in order to let the filtering settle down before it starts to process the real data.
Within the Shake! code, the calculated signal generators were implemented in the JdSineSignal, JdStepSignal and JdImpulseSignal classes.
Filtering life’s little bumps
In order to satiate my desire for testing different filters with Shake!, I built in 7 different types:
- Pass Through – actually a “non-filter”
- Apple’s 5 Hz Low Pass 1st order filter
- Apple’s 5 Hz High Pass 1st order filter
- Butterworth 5 Hz Low Pass 2nd order filter
- Butterworth 5 Hz High Pass 2nd order filter
- Butterworth 1 to 3 Hz Band Pass 2nd order filter
- Butterworth 2.5 to 5 Hz Band Pass 2nd order filter
Implementing Apple’s filters allowed me to compare the response of Shake! with that of AcclerometerGraph and helped me eliminate a few bugs that I had missed.
The interesting filters are the Butterworth ones. For people not versed in signal filtering a Butterworth Filter is a particular type of filter that has a flat frequency response in the pass band. There are several different types of basic filter configurations of which a Butterworth filter is just one example. Other filter types (for example) are the Bessel Filter, Chebyshev Filter and Elliptic Filter each of which have different signal characteristics. However Butterworth filters were chosen for Shake! by a purely arbitrary decision – in fact any type of filter would probably have been suitable with what I am doing in Shake!
In their implementation all digital filters come down to a simple equation of the sum of coefficients multiplied by delayed signal values. The only difference between Butterworth, Bessel and etc are the choice of the coefficients and the where in the scheme of things that these coefficients are applied. The most general equation for all digital filters would be something like:
y[n] = a.x[n] + b.x[n-1] + c.x[n-2] + .. + A.y[n-1] + B.y[n-2] + C.y[n-3] …
Where n is the current time index, so that n-1 is the previous time index, n-2 is the index before that etc. Thus x[n] is the current input signal, x[n-1] was the input signal last time. And y[n] is the new output, y[n-1] was the output for the previous signal etc etc. a, b, c, A, B, C are simple, fixed numerical coefficients. The trick to digital filter design is to pick the correct coefficients for the filter design that you want.
I’m not going to go into how to design a digital filter – that’s a whole university level course in itself, and something I did myself a long long time ago. What I can do though is to point you all at the filter design software that I found online.
The software was designed by Tony Fisher, a University of York lecturer in Computer Science, who unfortunately passed away several years ago. However his software can be found (in an interactive web page, and also for down-load) at Interactive Digital Filter Design.
Using this software is a breeze – all you need to do is to select the type of filter you want, the system sampling frequency and the frequency breakpoints of the filter. The software then pops out all the design details plus a C code template for coding the filter directly (and that minimizes multiplications). In addition it also generates a frequency and phase response graph for the filter.
So using the 5 Hz High Pass Butterworth filter as an example, the software generated this pseudo C code :
#define NZEROS 2 #define NPOLES 2 #define GAIN 1.450734152e+00 static float xv[NZEROS+1], yv[NPOLES+1]; static void filterloop() { for (;;) { xv[0] = xv[1]; xv[1] = xv[2]; xv[2] = next input value / GAIN; yv[0] = yv[1]; yv[1] = yv[2]; yv[2] = (xv[0] + xv[2]) - 2 * xv[1] + ( -0.4775922501 * yv[0]) + ( 1.2796324250 * yv[1]); next output value = yv[2]; } }
Within Shake! I created a a custom class JdGenericFilter that would take the details from one of these filter designs as a template and encapsulate all of the processing needed to cover any filter design up to a 10th order filter. For example my template for the above filter is:
const static DigitalFilterTemplate butterworth2OHP5Hz = { /* Tag */ 2, /* Title */ "Butterworth HP 1", /* Description */ "2nd Order High Pass 5 Hz", /* Filter Class */ kFilterClassHighPass, /* Sample Frequency Hz */ 60.0, /* Corner Freq 1 Hz */ 5.0, /* Corner Freq 2 Hz */ 0, /* Freq 3 Hz */ 0, /* Order */ 2, /* Gain */ 1.450734152e+00, /* Number of Zeroes */ 2, /* Number of X Coeffs */ 3, /* X Coeffs */ { 1, -2, 1 }, /* Number of Poles */ 2, /* Number of Y Coeffs */ 2, /* Y Coeffs */ { -0.4775922501, 1.2796324250 } };
And the actual filter is instantiated and used by code like
JdGenericFilter* filter = [[JdGenericFilter alloc] initFilter:butterworth2OHP5Hz]; .. .. double input = ...; double output = [filter newInput:input];
The big caveats with this filter class are:
- The code is no where near the most efficient code in the world and should not be used for a real world digital filter.
- If you change the sampling frequency, then you HAVE to change the filter design.
Finally, I implemented Apple’s filters in the JdSimpleLP and JdSimpleHP classes.
Crossing the (RMS) threshold
Filtering the signal is just the first part of the battle. Detection of when the signal has exceeded a threshold is the next part – for which the Level Detection setup defines how this is done.
The basis of the Level Detection is to simply calculate the RMS value of the filtered signal over a rolling sample window, and then indicate a trigger when that level has exceeded a threshold for a set number of samples. The setup for the Level Detection reflects these requirements and allows you to independently change all three variables.
The RMS (or Root Mean Square) value of a signal is simply the square root of the mean value of the sum of the squares of the input values. Thus it is useful for detecting the absolute level of a signal that varies in sign (i.e an acceleration). By calculating the RMS value over a window of samples, you can tune how much history of the signal is used – the shorter the window, then the shorter the history.
After calculating the RMS value, its value is compared against a fixed threshold value. If the RMS value continually exceeds that threshold for a fixed number of samples, then a trigger is generated which causes the associated trace on the graph to change. For convenience the trace changes to the trigger level when such an even occurs.
Within the Shake! code, the RS detection is implemented in the JdRMS class.
And You can build it too
Once again I am releasing my source code to GitHub under a BSD-3 style license, so you are free to use it however you like as long as you acknowledge where it originated from. And if you do use any part of it please drop me a line to let me know!
You can download the source code for Shake from GitHub at https://github.com/JoalahDesigns/Shake
Até o próximo blog …
Hope you all enjoy this latest installment.
Peter
Not happy Jan
While the integration of Git into XCode is a good step forward, for me it doesn’t go far enough. As I have been using XCode and Git I have been finding many use cases which you just can’t do within XCode, and that force you to drop back to the command line in order to get things. This includes things like seeing the true state of things, creating branches from previous commits and helping XCode/Git sort out the state of a project after it got a little confused.
So this week I am going to keep on the subject of Git point out a couple useful command line basics. This won’t be an extensive Git tutorial, so if you are looking for a whole lot more, then you should head on over to somewhere like Top 10 Git Tutorials for Beginners where you’ll find an extensive list of dedicated tutorials.
Before you can use any of these commands, you’ll need to open a Terminal window and navigate to the directory that your project is located in.
So show me!
The most basic Git command you can use is simply:
git
Which will respond with a summary of all the Git sub-commands
usage: git [--version] [--exec-path[=]] [--html-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=] [--work-tree=] [-c name=value] [--help] <command> [<args>] The most commonly used git commands are: add Add file contents to the index bisect Find by binary search the change that introduced a bug branch List, create, or delete branches checkout Checkout a branch or paths to the working tree clone Clone a repository into a new directory commit Record changes to the repository diff Show changes between commits, commit and working tree, etc fetch Download objects and refs from another repository grep Print lines matching a pattern init Create an empty git repository or reinitialize an existing one log Show commit logs merge Join two or more development histories together mv Move or rename a file, a directory, or a symlink pull Fetch from and merge with another repository or a local branch push Update remote refs along with associated objects rebase Forward-port local commits to the updated upstream head reset Reset current HEAD to the specified state rm Remove files from the working tree and from the index show Show various types of objects status Show the working tree status tag Create, list, delete or verify a tag object signed with GPG See 'git help ' for more information on a specific command.
Of these the one I use the most is:
git status
As it shows what the current state of my repository is. For example, on a project I am currently working on, running this command results in:
165-156-000-000:NotchFlow joalah$ git status # On branch master # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: NotchFlow/CalcViewController.m # no changes added to commit (use "git add" and/or "git commit -a")
Which tells me that I am working on the “master” branch and there is one file that has been modified but not yet committed to the repository. While I knew from the XCode IDE that I had modified a file, the IDE itself doesn’t tell you what branch you are currently working on. For that you need to either open the XCode Organizer, or drop into the command line.
Another important command for seeing what is what, is:
git branch
By itself it will list out the branches that exist in the repository, and mark the one that is currently checked out:
n165-156-000-000:NotchFlow joalah$ git branch CleanupMVC LastWorking NewKeyboardLayout SmallerPreferences * master
And finally the ultimate list of information from the repository can be seen in:
git log
This lists everything that has been done, tracking each change with the hash of each commit, and listing them all the way to the creation of the repository. For a project that has been under development for a while this can be a surprisingly long list of commits. Thus the log command comes with a slew of options with which to limit, arrange and order its results. The simplest of these options are things like:
-n |
Limits the output to the previous ‘n’ commits |
-pretty=oneline |
Summarizes the commit hash and comment all on one line |
--abbrev-commit |
Displays a partial commit hash |
An example of using this command is:
n165-156-000-000:NotchFlow joalah$ git log --pretty=oneline -5 --abbrev-commit adb209e Merged in cleanup of main view controller 55f771a Removed unused file 0624d7c Finished ceasing up locations 705aef0 ignoring autogenerated files 7c03745 Cleaned up location of text
One benefit of the abbreviated commit hash is that you can use it anywhere that git wants to see a commit hash. Thus you don’t have to key in all 40 characters of the full commit hash all the time.
Jumping from branch to branch
Once you know what branches are available, you can easily switch between them with
git checkout <branchname>
For example:
n165-156-000-000:NotchFlow joalah$ git branch CleanupMVC LastWorking NewKeyboardLayout SmallerPreferences * master n165-156-000-000:NotchFlow joalah$ git checkout CleanupMVC M NotchFlow/CalcViewController.m Switched to branch 'CleanupMVC' n165-156-000-000:NotchFlow joalah$ git branch * CleanupMVC LastWorking NewKeyboardLayout SmallerPreferences master
Splitting branches
Like from within the XCode Organizer tool you can create and delete branches using the git command line. But differing from the Organizer you can also do so with a lot more options than just creating a new branch from the current state of an existing branch. The git command for branching:
git branch
Has some useful variations such as:
-m [<oldbranch>] <newbranch> |
Move or rename an existing branch. |
-d <branchname> |
Delete an existing branch. |
<branchname> [<start-point>] |
Create a new branch. |
To me the ‘Create’ variant is the most useful, as it allows you to create a new branch from an arbitrary starting point in the repository. The starting point can be one of:
<branchname> |
The name of an existing branch |
<commit-id> |
An arbitrary commit id from anywhere in the repository |
<tag> |
An arbitrary tag from anywhere in the repository |
If it is not specified, the the HEAD of the currently checked out branch will be used.
The following is an example of creating a new branch from a pre-existing commit, and then checking out the new branch. Note that the starting point was specified with only the abbreviate commit hash:
n165-156-000-000:NotchFlow joalah$ git log -5 --pretty=oneline -5 --abbrev-commit 55f771a Removed unused file 0624d7c Finished ceasing up locations 7c03745 Cleaned up location of text e0bb05d Working with new messaging system e70b543 Back together again .. I think n165-156-000-000:NotchFlow joalah$ git branch WorkingAgain e0bb05d n165-156-000-000:NotchFlow joalah$ git branch * CleanupMVC LastWorking NewKeyboardLayout SmallerPreferences WorkingAgain master n165-156-000-000:NotchFlow joalah$ git checkout WorkingAgain M NotchFlow/CalcViewController.m Switched to branch 'WorkingAgain' n165-156-000-000:NotchFlow joalah$ git log -5 --pretty=oneline -5 --abbrev-commit e0bb05d Working with new messaging system e70b543 Back together again .. I think 2c98537 New Display sort of broken 50b9427 Mostly works e7d9596 Sort of working with new keyboard n165-156-000-000:NotchFlow joalah$
Marking the trail
Finally in order to indicate a significant milestone in the life of a project, you can mark a commit with a tag. A tag is simply a text string that carries some meaning for the person who created it. Typically you would tag a commit to indicate something like the completion of a specific release version. The tags also include a description part which you can use to explain why a commit has been tagged.
With the git command:
git tag
You can list, add and delete tags through the commands options. Useful combinations of these options are:
-l [-n[<num>]] |
List all the existing tags, with ‘num’ lines of their descriptions. Note that if not specified, ‘num’ defaults to zero! |
<tagname> -m <msg> [<commit>] |
Create a new tag with a a description <msg> for either the last or an arbitrarily specified commit hash |
-d <tagname> |
Delete a tag |
As an example:
n165-156-000-000:NotchFlow joalah$ git tag -l -n1 v0.01 Technical Feature Complete n165-156-000-000:NotchFlow joalah$ git tag v1.0 -m 'testing tags' n165-156-000-000:NotchFlow joalah$ git tag -l -n1 v0.01 Technical Feature Complete v1.0 testing tags n165-156-000-000:NotchFlow joalah$ git tag -d v1.0 Deleted tag 'v1.0' (was 3e9e48e) n165-156-000-000:NotchFlow joalah$ git tag -l -n1 v0.01 Technical Feature Complete
Given a git tag, you can see the associate hash from this command:
git show <tag>
By default this produces a rather verbose output, but it can be trimmed down with a format
option as in this example:
n165-156-000-000:NotchFlow joalah$ git show V1.0 -s --format="%h, %cd, %cr, %s" tag v1.0 Tagger: Peter Milway <peter@JoalahDesigns.com> testing tags e41ee4a, Wed Mar 14 15:51:28 2012 -0400, 68 minutes ago, Added changes
Once again its been a pleasure
Peter
Note that I have revised these ideas in Displaying Git details in XCode projects revisited
Cruising along
Source or Version control systems (VCS) are great for organizing the journey of a program from its initial inception through to the latest release. Through judicious use of committing and tagging your files, you can easily retrieve, rebuild or debug any version of code from anywhere in your programs life history. (In fact proper use of a source control system is something that all professional software writers should master, no matter what flavour.)
With XCode 4, Apple chose to integrate Git directly into the editor, allowing it to offer to build repositories each time you create a new project. As a VCS, Git is one of the more modern varieties, built on a premise of distributed software development, and easily creating and merging multiple branches of code in parallel. But while not everyone works with a development team distributed around the world, everyone should know how to create and merge branches in XCode.
The ease of branching in Git helps to keep the main branch of your code pollution free. Instead of trying a new feature in the release version, you branch off the entire codebase to a test branch, and try new things, or build new features in isolation. Then when you are happy with the result, you just merge your changes back into the main branch.
Rocks ahead!
But what if you have multiple versions/branches of your program out in the world and a user reports a bug. How do you know what code was used to build that particular instance? Somehow you need to reconcile the bug report with the code in your VCS, so you know what code to pull for testing. If only the bug report was tagged with the codes version and you could look up that tag in the repository!
A naive way of doing this would be to place a string or numeric constant in your code that described the current version information and then manually update it when you decided on a version change. This could then be displayed on a label somewhere in your program.
NSString* version = @"Version 1.1"; ... label.text = version;
The problem with this type of system is two-fold:
- The version is never guaranteed to be correct, as you will make changes to the program, but forget to update the string.
- The VCS doesn’t know about the string, so you can’t ask it to return that particular version
Charting a better course
As the VCS knows about the what versions of code are stored in it, it is better to get the VCS to tell the code what version it is. And if you make this a part of the projects build system, then this will happen automatically and seamlessly – thus saving you from a manual step.
In the case of Git, there are several items that can be extracted from the repository that can uniquely identify what version of code was used to build a particular application’s release:
- The current branch name
- The last commit hash
- The last commit date
- The last commit comment
- The last repository tag
All of these items can easily be obtained by running the command line Git tool like so:
Current branch name: git rev-parse –abbrev-ref HEAD
Last commit hash: git log –pretty=format:”%h” -1
Last commit date: git log –pretty=format:”%ad” –date=short -1
Last commit comment: git log –pretty=format:”%s” -1
Last repository tag: git describe –abbrev=0 –tags
(For a description of these commands, please see the Git website)
All that is needed now is to integrate this into your projects build system!
Building the build system
When I researched how to get the Git details into a project, there were several solutions that involved running a script, creating a file and modifying plists and then doing a few more other things. To me they were overcomplicating things a bit. All I wanted was to create a bunch of #defines in a simple .h file and include that where it was needed. Doing this in XCode 4 turned out to be very very easy. All that I did was:
- Select the target I want to include the Git data in
- Selected the Build Phases of the target
- Created a new “Run Script”
- Entered OSX shell script commands to create the .h file
- Imported the .h file into the source as needed
- Added the .h file to my git .ignore file
Visually this is:
Select the Target in the project</td |
![]() |
Go to the Build Phases</td |
![]() |
Create a new “Run Script” phase</td |
![]() |
Move the “Run Script” to be before the “Compile Sources” phase</td |
![]() |
Edit the “Run Script”</td |
![]() |
All is revealed
My script file that I inserted into the Run Scripts is:
# build data file that is included in the source # so we can automatically report Git repo information # in the application cd ${PROJECT_DIR}/${TARGETNAME} gitDataFile="gitDataAutoGenerated.h" buildDate=`date "+%F %H:%M:%S"` currentBranch=`git rev-parse --abbrev-ref HEAD` lastCommitHash=`git log --pretty=format:"%h" -1` lastCommitDate=`git log --pretty=format:"%ad" --date=short -1` lastCommitComment=`git log --pretty=format:"%s" -1` lastRepoTag=`git describe --abbrev=0 --tags` 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
And for the current state of my project, this produces the gitDataAutoGenerated.h file with the following contents:
//----------------------------------------- // Auto generated file // Created 2012-03-04 18:41:15 //----------------------------------------- #define BUILD_DATE @"2012-03-04 18:41:15" #define GIT_CURRENT_BRANCH @"master" #define GIT_LAST_COMMIT_HASH @"8988fe8" #define GIT_LAST_COMMIT_DATE @"2012-03-04" #define GIT_LAST_COMMIT_COMMENT @"Toned down size of elements in preferences" #define GIT_LAST_REPO_TAG @"v0.01"
Which I include in my program as:
#import "gitDataAutoGenerated.h" ... programBuildDate.text = BUILD_DATE; programCreateDate.text = GIT_LAST_COMMIT_DATE; programVersion.text = [NSString stringWithFormat:@"%@%@%@", GIT_LAST_REPO_TAG,([GIT_LAST_REPO_TAG length]>0?@".":@"") , [GIT_LAST_COMMIT_HASH uppercaseString]];
And I display this on an information page as:
![]() |
So now if ever I get a bug report from this program, I can use the Git commit hash of 8988fe8 to go straight to the source and re-create exactly the program version that the user has. Of course if I was being really retentive I would have archived the software binary along with the source code! But thats a story for a different day.
Hope you all found this useful.
Peter