With Apple now shipping OS X upgrades every 12-15 months, Mac developers are very quickly finding themselves supporting their apps on multiple OS X versions. Until recently, my approach to testing on multiple OS X versions involved partitioning an external USB drive and installing the OS X versions onto it and booting off the partitions to test.
While this approach is inexpensive, the test-discover-reboot-fix-build-test cycle just got to be too much for even this frugal Scot.
Enter the Mac mini
My new solution has 2 parts: a maxed out Mac mini with VMware Fusion to support all the targeted OS X versions and a scripted piece that will sync new test builds to the VMs.
The current $799 Mac mini has a quad-core Intel Core i7, a 1TB HD and is expandable to 16GB of RAM. Our local MicroCenter had a $749 sale on that model so I picked one up along with a 16GB RAM upgrade. After I setup 4 VMs, one for each of 10.6 – 10.9, I found the performance to be incredibly slow. Analyzing the issue quickly identified the speed of the stock HD as a bottleneck, so after considering returning the whole kit for a BTO Fusion Drive model, I picked up an OWC 240GB Mercury EXTREME™ Pro 6G SSD, and now the performance is great. (Installing an extra HD in a Mac mini is possible, but if you do, make sure you watch and understand the videos on the topic and make sure you order the correct parts to install in your Mac mini.)
Moving the builds around
Each of my Mac Xcode projects now has an extra scheme called MyApp test build. The following scheme settings are used: In Run MyApp.app, I’m using the Release build configuration and I’ve set Launch to Wait for MyApp.app to launch since I don’t intend to run these builds from Xcode. Most importantly, in Build->Post Actions, I’ve added a Run Script Action with Provide build settings from set to the app. The script below creates a new uniquely-named folder in the
$root_destination_folder every time you build and will copy the resulting product into that newly created folder. The folder name combines date/time, product name, and the current
git describe. The
$root_destination_folder should be in a folder that is synced by a service. I used Dropbox at first, but it doesn’t seem optimized for the large number of small files that compose a Mac application bundle, even with LAN syncing turned on. I’m now using BitTorrent Sync, which uses the BitTorrent protocol. Even as a beta release, I had great success with it. By adding the shared folder to the BitTorrent Sync client on each test VM, every time I make a test build, it automatically appears on each VM.
By running multiple OS X versions in VMs simultaneously and syncing test builds, I’ve nearly eliminated all the waiting involved in testing and iterating across multiple OS X versions.
root_destination_folder=/Users/fraser/Development/test_builds_sync date=`date +%Y%m%d-%H%M%S` product=$PRODUCT_NAME git=/usr/bin/git cd $SRCROOT version=`$git describe --dirty` full_dir_path=$root_destination_folder/$date-$product-$version mkdir -p $full_dir_path cp -RH $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME $full_dir_path
Note 1: For testing on Mac OS X 10.6 Snow Leopard, only Snow Leopard Server is supported in VMware Fusion. Apple has made Snow Leopard Server available to paid Mac developers as a free download. The provided serial number expires at the end of 2014.
With the March 1st start date approaching when sandboxing becomes a requirement for submissions to the Mac App Store, I’ve been considering my options. (more…)
A couple of years ago I posted my scripts for tagging and building. The build script doesn’t work so well for the App Stores and the Build and Archive-based submission process, so here’s an updated approach that works inside Xcode. (Same as my last article, I’m still using git, the tag.sh script, and Apple Generic Versioning (agv).)
- In your Xcode project, create a Shell Script target called GenGitVersion.
- Insert the following script in GenGitVersion’s Run Script phase, replacing the path to your git executable if need be:
git=/usr/local/git/bin/git version=`$git describe` echo "#define GIT_VERSION $version" > InfoPlist.h touch Info.plist
- Make GenGitVersion a dependency of your main target.
- Add “InfoPlist.h” to your .gitignore file.
- In your main target’s build settings:
- Turn on Preprocess Info.plist File
- Set Info.plist Preprocessor Prefix File to InfoPlist.h
- In your app’s Info.plist set the Bundle versions string, short to GIT_VERSION
Now each time you build the main target, the version will be populated in the build’s Info.plist.
- If you tag a commit, the version returned by
git describeis the tag name. If the current commit isn’t tagged, you’ll get the most recent tag plus the number of additional commits and an abbreviated commit name. Here’s a 1.0b1 tag with 2 additional commits: 1.0b1-2-g3925f3b. This can be a good way to identify a private developer build.
- You can optionally add
git describeand the version will have
-dirtyappended if there are uncommitted changes to your working tree.
git describewill fail if there are no tags in the current working tree.
It’s common practice for any software project with multiple coders to use some version control mechanism. CVS or Subversion used to be popular. These days distributed systems like git and Mercurial are the quickly replacing the old standards. But what about the cases when you’re the only coder?
Now that we’re all using XCode 3.2 on Snow Leopard (you are, right?) and building 64-bit apps you may find that not everything 64-bit works when your app is run on Leopard.
If your app is fullscreen, like a game, has a presentation mode, or plays long running movie files, you’ll want to disable the display from sleeping. DVD Player and Keynote are perhaps the two most obvious examples of this functionality.
It is important to develop a consistent build process for your applications. I have written a couple of bash scripts to help me with this process.
I use git for version control and also the services of github. Now in another post on this site Marcus covered how to put git commit checksums in your Info.plist’s CFBundleVersion. I have opted to use Apple Generic Versioning (or agv for short) instead as it has an easy to read incrementing build number and is super easy to script. It’s also great for use with Sparkle since Sparkle uses the CFBundleVersion to see if the appcast has a newer version.
This is the first in a short series on my adventures getting my software out the door. Rather than this first lesson be a lesson in what to do, here’s what not to do.
NSLog() is a great tool that helps debugging efforts. Unfortunately it is expensive, especially on the iPhone, and depending on how it’s used or what you’re logging, it could leak sensitive or proprietary information. If you look around the web, you’ll find a few different ways to drop NSLog in your release builds. Here is what I’ve put together based on those.
iTunes has a very neat way of searching your library, where it takes each word in your search and tries to find that word in multiple fields.Â For example, you can search for “yesterday beatles” and it will match “yesterday” in the Name field and “beatles” in the Artist field. The basic predicate binding for NSSearchField provided by Interface Builder is not complex enough to archive this kind of search.Â I need to build the predicate dynamically since I can’t assume what field the user is trying to search and that each additional word should filter the list further – just like iTunes.Â Here is how to go about adding iTunes-style searching.