Experimentation with build numbering

Build numbering is not the most interesting subject but during my investigations I found some cool ideas to use in my own scheme.

##tl;dr

MacRuby is cool and I over engineered a solution for fun.

#Starting point

I started out with some StackOverflow questions 1, 2 and then ended up with this blog post over at cimgf.com. I’ve not used pearl and as stated in some of the comments it’s got some hairy regex’s in there which ideally I’d rather not have to deal with.

I’m a Ruby fan and as such that would be my preferred language to use for this. Scrolling down to the comments on that blog there are examples in python and ruby so there’s a good starting off point.

##Issue 1

The example in the blog uses the commit hash as the build number.

In accordance with Apple’s Information Property List Key Reference the commit hash can not be used as the build number because this this property should be:

… a monotonically increased string, comprised of one or more period-separated integers

A couple of examples on StackOverflow and in comments of the original blog post suggest using

git rev-list HEAD | wc -l

This first command git rev-list HEAD prints the hash of each commit on a new line

paul$ git rev-list HEAD
ced64f89f841d46b2a4262a2987ca363be704d49
ff538caa32f704fb09295d41a20fe2f8488ca6ae
72847eb10e3c3731a696c84e3ae4179640d28ed2

This is then piped into wc -l which counts the number of lines given to it, in this case from standard input. Here the result would be 3.

I quite like this idea for numbering as it works a lot better when the code base is shared among a team. If you was simply incrementing a counter you have to deal with issues such as where is it stored and how do you keep it consistent. It should also serve as a rough guide to how many stable builds it took to get the app to it’s current state.

##Issue 2

I quite like the thought of having the commit hash

I’d like to keep the commit hash details so that I can easily track down what exact commit this came from. A quick scan of the Apple’s Information Property List Key Reference confirms that you are allowed to add any keys/values to the info plist.

##Additional requirements

  • I also want to update the settings bundle
  • I don’t want any regex’s
  • I want to play with doing this fairly natively

#Let’s build

So I started out by wrapping up those build paths that come from the ENV variable

##Helpers

class Environment
  %w(BUILT_PRODUCTS_DIR INFOPLIST_PATH WRAPPER_NAME).each { |const| const_set(const, const) }
  
  def initialize env
    @env = env
  end
  
  def build_info_plist_path
    "#{@env[BUILT_PRODUCTS_DIR]}/#{@env[INFOPLIST_PATH]}"
  end
  
  def build_settings_plist_path
    "#{@env[BUILT_PRODUCTS_DIR]}/#{@env[WRAPPER_NAME]}/Settings.bundle/Root.plist"
  end
end

I only need two paths but they need to be constructed and I would rather not have this construction logic scattered about. I’m also preferring to use constants for keys as opposed to strings so I’m dynamically generating them first.

This next bit may look especially odd for people who don’t know Ruby, but do know Objective-C

class NSMutableDictionary
  def self.plist_open path
    plist = self.alloc.initWithContentsOfFile path
    if block_given?
      yield plist
      plist.writeToFile path, atomically: true
    end
    plist
  end
end

The cat is out of the bag - I’m playing around with MacRuby. I can get the native handling of plists from Objective-C but the ease of Ruby. Here I am opening up the NSMutableDictionary class and adding my own helper method to open a file, allow me to make changes and then save them back out. I added this as a convenience for later on.

##Actual logic

The constructor for the main class looks like this

class XcodeVersioner  
  %w(DefaultValue PreferenceSpecifiers CFBundleShortVersionString CFBundleVersion PSGitCommit Key).each { |const| const_set(const, const) }
  
  attr_reader :bundle_version, :build_number, :git_commit, :env
  
  def initialize env
    @env            = env
    @build_number   = `git rev-list HEAD | wc -l`.to_s.strip
    @git_commit     = `git rev-parse HEAD`.to_s.strip
    @bundle_version = ''
  end

...
  

I start out by declaring constants again to avoid magic strings being littered everywhere. Then I add the attr_reader’s for ivars. The Environment helper class is passed into the constructor and then the basic information is retrieved from git.

Next up is the method that gets called to start the real work

def run
  version_info_plist
  version_settings
  puts "Bundle Version => #{bundle_version} (#{build_number}) with commit #{git_commit}"
end

In this method we just call two more methods where the nitty gritty details are hidden and then print out the result (this will show up in the build logs).

If you’ve not used blocks much in Objective-C then you really should. You can make some really cool API’s, of course this is Ruby but they work pretty much the same. Using the helper method I added to NSMutableDictionary I get something like this for editing the info plist

def version_info_plist
  NSMutableDictionary.plist_open(env.build_info_plist_path) do |plist|
    @bundle_version        = plist[CFBundleShortVersionString]
    plist[CFBundleVersion] = build_number
    plist[PSGitCommit]    = git_commit
  end
end

Let’s break this one down line by line

  1. Method defintion
  2. Call the helper method with a path to the file. The NSMutableDictionary opens the file and then yield’s it back into the block as the variable plist
  3. I grab the bundle version from the info plist file and store it into an ivar
  4. I set the CFBundleVersion version to the build_number retrieved in the constructor
  5. I set the PSGitCommit to the git_commit retrieved in the constructor
  6. The end of the block
  7. The end of the method

The code between the do and matching end are the block of code that gets yield‘d to by the plist_open method.

The method for working with the settings bundle is very similar in concept, but it’s slightly more awkward to work with due to the structure of the plist.

The structure of the settings bundle plist looks like this

{
  "PreferenceSpecifiers" => [
    {"Type"=>"PSGroupSpecifier", "FooterText"=>"Some Footer Text", "Title"=>"Info"}, 
    {"DefaultValue"=>"", "Key"=>"CFBundleShortVersionString", "Type"=>"PSTitleValueSpecifier", "Title"=>"Version"},
    {"DefaultValue"=>"", "Key"=>"PSGitCommit", "Type"=>"PSTitleValueSpecifier", "Title"=>"Debug"},
  ], 
  "StringsTable"=>"Root"
}

The bit’s we care about are the dictionaries that are held within an array. The array is contained in a dictionary under the key PreferenceSpecifiers.

What this means for me is that I have to loop through each item in the array and then use a case statement to figure out if it is a preference specifier that I want to edit. This all ends up looking like:

def version_settings
  NSMutableDictionary.plist_open(env.build_settings_plist_path) do |plist|
    plist[PreferenceSpecifiers].each do |preference_specifier| 
      case preference_specifier[Key]
      when CFBundleShortVersionString
        preference_specifier[DefaultValue] = "#{bundle_version} (#{build_number})"
      when PSGitCommit
        preference_specifier[DefaultValue] = git_commit[0...10]
      end
    end
  end
end

##Conclusion

This probably looks completely over engineered when compared to the original blog post’s code. I would have to agree with that statement, but I do believe that I’ve had some fun experimentation with MacRuby, made a useful tool for myself and have abstracted the logic out nicely for easier maintenance. Before I did this I never thought of the power at my fingertips with the Cocoa framework and Ruby combined - this example is only taking advantage of NSMutableDictionary’s ability to natively handle the plist format but I am sure better examples could be found.

iOS simulator directory command line tool

I made my first Ruby gem and more importantly it solves a problem that really frustrates me (ever so occasionally).

The problem is that I tend to hang out on StackOverflow answering questions, which often means I end up writing code. To test the code works I’ll jump into Xcode and create a new project but save it to /tmp so I don’t get clutter on my hard drive. This leads to the following mess in my simulators directory

Finder mess

When it comes time to do some real work and I need to check the contents of a real app I hit this mess, which is annoying and often leads to applying a sort on date to find likely folders to look in.

So I took this opportunity to have a play with a few concepts

  • Making a gem
  • Making a command line tool
  • Playing with state machines

The quick project has indeed served it’s purpose - I actually wrote some code and didn’t just think about writing it and I got to play with the above concepts.

#The tool

I called it sidir as an abbreviation for “Simulator Directory” and because it sounds like the refreshing beverage.

So providing I didn’t mess something fundamental up the following should work (it works for me at least with Ruby 1.9.3)

gem install sidir

From there we can start it up with

sidir

We’ll get the sidir prompt which also tells you where it is in relation to the sim directory

sidir / >

(Serious draw back #1 it is hard coded to start in ~/Library/Application Support/iPhone Simulator/)

From here we can get the available commands with help

sidir / > help
      cd <item> - change to directory at <item>
      ls        - list available simulators
      show      - open Finder here

So let’s see what is around with ls

sidir / > ls
4.3.2
5.0
5.1

I can get finder to open here with show but I’ll cd into the 5.1 sims directory

sidir / > cd 5.1

(There is some basic tab completion - another thing I wanted to play with)

Let’s see what commands we have available here

sidir /5.1 > help
      cd    <item> - open finder at <item>
      cd    ..     - move back to simulators
      ls           - list available applications
      show         - open Finder here
      rm    <item> - delete the application <item> and all associated data 
      reset <item> - clean all paths in <item> as if the app is freshly installed

We have slightly more things to do.

Things to note

  • cd <item> will open up Finder - I didn’t bother going any further as it would just be replicating normal shell behaviour
  • rm and reset work on apps not the whole sims directory

The idea behind reset was to take an application directory and clean it all out just leaving what is installed by default. This kind of emulates a clean install - I’m not sure how useful this actually is as there is also rm which just deletes the whole application folder, which I find a lot quicker/easier than tap and holding in the simulator until the apps wobble and then clicking the x and then… you get the point.

#Conclusion

Making a gem was fun but I got tripped up a lot as it was my first gem and I had to learn about the structuring etc. I’ve got a simple utility that I find handy even if it just an elaborate way to easily get the application names. I’ve slapped this on github and it’s available as a gem - who knows someone else might find a use for it.

I have also found it amazing how just after I hit deploy on a gem, public github or a blog I find a great number of spelling mistakes, issues or oversights that I then have to jump in and correct. Upto version 0.0.3 already!!!

Xcode conditional break points

I’ve been developing for iOS for quite a while now and I’m always amazed at the things I don’t know that can really change my workflow. I’ve felt at home in the debugger for a while - printing objects and scalar variables, assigning values and generally navigating around fairly smoothly. Today I got a new tool to put in my programming belt with the knowledge that it’s possible to create conditional breakpoints - this is something that I wish I knew a long time ago.

##Problem in context

It’s a fairly common problem that you’ll have an issue that occurs only while in some kind of loop, whether it is a for loop or a user interaction loop of some kind e.g. tapping a button multiple times. It can be both tedious and error prone trying to get yourself set up to examine state when you have to perform repetitious work like pressing continue on the debugger. It’s easy to overshoot your assumed condition and have to start again or maybe it would just take far too long to get yourself in the correct state manually.

##Meet conditional breakpoints

Our test scenario is both silly and simple but it gives us something to work with.

“We have noticed that every 10th time through our loop we get an error - we would like to stop execution and examine our object.”

MyObject *myObject = [[MyObject alloc] init];

for (int i = 0; i < 1000; i++) {
  [myObject doSomething];        // Break point on this line
}

###The old way

We set up a break point and we are happy to click continue as we only have to do it a few times. Hopefully our counting and hand to eye co-ordination is up to standard and we manage to click through correctly the first time to get us in the right state

click: Run 
click: Continue    
click: Continue  
click: Continue  
click: Continue  
click: Continue  
click: Continue  
click: Continue  
click: Continue  
click: Continue  
click: Continue  
Examine the object

We “fix” the issue but now it happens every 100th time through the loop. At this point we would probably abandon our tools and deteriorate to caveman debugging. Log everything and then scan the logs to see what is happening… but luckily we don’t have to give up just yet.

##Conditions

If you right click the breakpoint in Xcode you’ll see an option to “Edit Breakpoint…”.

Conditional Breakpoint Options

As you can see there are some interesting configuration options here.

  • Condition - A condition to be met for the breakpoint to fire
  • Ignore - Amount of times the condition needs to be met before firing
  • Action - Any actions to run which can be Applescript, Capture OpenGL frame, Debugger Command, Log Message, Shell Command, Sound
  • Options - As it says in the image

Remember using Debugger commands alone we can access all kinds of information like the values of variables, back traces etc

###Basic condition

To start we’ll set the condition and run an action to see if we can grab the 100th iteration

Conditional Breakpoint Options

Side note - p i will just print the variable i

We hit run and sure enough the debugger stops and in our console we get output like:

(int) $101 = 100
(lldb)

###Basic condition with ignore

To try out the ignore option we’ll stop on the 5th time that i == i, which will leave us with a value of 5.

Conditional Breakpoint Options

This gives us output like:

(int) $1 = 5
(lldb)

###Non trivial condition

The Condition expression can be non trivial as long as llvm can understand it. For example if I wanted to examine the state around my object when one of the properties is set to the string broken I can get the breakpoint to fire like this:

Conditional Breakpoint Options

Apart from the annoying cast this is nice and clean.

##”But I really like caveman debugging…”

You may be thinking “this is cool but I can just write the code that will print things when different conditions occur” - and you’d be right. There are two reasons why I prefer using the conditional breakpoint

  1. Where possible I don’t like changing production code for development purposes.
  2. If I logged the object out I would only get the information that I logged. With the breakpoint I can examine all the state around the object as well because the execution is paused.

##Crazy thoughts

I’m still only thinking about the wide range of debugging possibilities available but one far fetched scenario that could have uses in a collaborative environment could be for capturing those bugs that are hard to reproduce.

By setting up a conditional breakpoint and sharing it with the team, you could have multiple workstations + multiple developers going about their normal work but potentially capturing that awkward code path that you can never quite reproduce. At this point you can literally do anything - dump everything to a log, run a script to email you information, have the computer say "STOP you've found the lochness monster bug", who knows what fun you could have.

##Conclusion

Conditional breakpoints are very flexible and powerful. Although I prefer to avoid the debugger by writing the best code I can, knowing how to use the tools effectively really helps out in times of need. Don’t be fooled by only having 4 options to configure, these give you amazing power and versatility and should help avoid the times when you are stuck caveman debugging large repetitive tasks.