Suffixed Builds 2009/08/09

Developers know that when you both use and write an application, it is easy to be worried about some little programming error causing corruption of your personal documents. This is particularly true for single-document applications where you have to jump through hoops to switch between your personal data and some test data.

There are tools like rooSwitch to help quickly switch between sets of preferences and application support files, but I was interested in an approach that was more automatic and allowed me to run a stable build at the same time as a development build.

Running your own work day-to-day, without fear of corrupting anything, means you’ll be more likely to use your app consistently and will be more likely to find real problems with its usability. Any friction that prevents this is to be avoided.

At its heart, the approach I use is very simple: make the non-stable builds be totally different applications than the stable build.

Setup

In Xcode’s Target Info panel:

  • Turn on Info.plist preprocessing for all your build configurations
  • Edit your Info.plist to have:

    <key>CFBundleIdentifier</key>
    <string>$(OMNI_BUNDLE_IDENTIFIER)</string>
    
  • For all configurations, add the following settings:

    • OMNI_BUILD_FILE_SUFFIX = -$(CONFIGURATION)
    • OMNI_BUILD_IDENTIFIER_SUFFIX = .$(CONFIGURATION)
    • OMNI_BUNDLE_IDENTIFIER = com.yourcompany.YourApp$(OMNI_BUILD_IDENTIFIER_SUFFIX)
    • OTHER_CFLAGS = $(value) -DOMNI_BUILD_FILE_SUFFIX=@\"$(OMNI_BUILD_FILE_SUFFIX)\"
  • Edit your Product Name to be MyProduct$(OMNI_BUILD_FILE_SUFFIX)

At this point, you have an application with its own name and bundle identifier. The bundle identifier gives your development app its own preferences domain as well as making LaunchServices treat it as its own app (so both your stable and development builds can be running). The application name is also important: this allows you to run AppleScripts, targeting your development build:

   tell application "MyProduct-Debug"
     ...
   end

The bundle identifier should also give you a separate directory for your entries in ~/Library/Caches and ~/Library/Caches/Metadata, assuming you use it for your subdirectories therein.

Resources

Next, you should search your application code and update other file names and identifiers as you desire. You can use the OMNI_BUILD_FILE_SUFFIX macro defined above to expand into the proper suffix for file names. Some likely items to consider are:

  • Your ~/Library/Application Support subfolder
  • The name of your default document, if you are a single-document application
  • Default search paths for plug-ins, templates, or anything else not in your Application Support folder.
  • Services menu item titles, so you can have a parellel set of services
  • The NSPortName entry in your NSServices (usually you’d set this to your bundle identifier, which has been updated already)

You might also consider whether you want to update your UTI identifiers and file extensions to have OMNI_BUILD_IDENTIFIER_SUFFIX appended (or a lowercase variant since UTIs can have case-sensitive hate). This would allow you to double-click on a development document and make sure LaunchServices throws it at the right app.

Releasing

During internal cycles of development and QA, your automated build machine will be spitting out MyProduct-Release.app which seems kind of weird at first glance, but it actually useful. This allows your QA folks to run the latest stable build and the new release build side-by-side, just like you might be running the -Debug variant. Once you are getting near to release, you will want to edit the Release configuration to have empty definitions of OMNI_BUILD_FILE_SUFFIX and OMNI_BUILD_IDENTIFIER_SUFFIX.

Which is which?

Finally, if you are running two copies of your app next to each other, it can be difficult to distinguish which is which by looking at the window or app icon. OmniAppKit has a little snippet for adding a construction-tape banner to your window title bar, if you find it useful. See -[NSWindow(OAExtensions) addConstructionWarning] and OAConstructionTimeView. With Info.plist preprocessing enabled, you can pick a totally different app icon, or you can do something like apply a transparent wash in some #ifdef DEBUG code.

blog comments powered by Disqus