From Hacker to microISV: Tagging, Building and Releasing
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. Chris Hanson wrote a great piece a few years back about getting agv setup in your XCode project. I followed his instructions for that. I also set the CFBundleShortVersionString in Info.plist to __VERSION__. You’ll see why I do that later.
When it comes time to build my app for shipping, either privately or publicly, I run tag.sh. This bash script increments the agv version and creates a git tag. The script takes one parameter which is the marketing version of your build (1.0, 2.0.4fc1, 2.5b6 etc).
Before running tag.sh, the state of the git repo should be clean- there should be no other staged files or uncommitted edits. It’s also a good idea to close the XCode project since agvtool will modify the XCode project file.
tag.sh also takes care of committing the changed CFBundleVersion for you. Here is it’s code:
1 2 3 4 5 6 7 8 9 10 11 | #!/bin/sh tag=$1 if [ "$tag" == "" ]; then echo "No tag specified" exit fi agvtool next-version -all git commit -a -m "Increment CFBundleVersion for $tag" git tag -m "Tag for $tag" -a $tag git push origin master git push --tags |
So having run tag.sh I have created a git tag for my build so I can always go back and see that version of the code either in github or using git checkout <tag name> at the command line.
After this I use a jumbo script that checks out the tag I just created, builds it, zips it up, uploads it to my web server, outputs an <item> for your Sparkle Appcast (including signing the update) and maybe tweets about it. Phew. It’s a heavily modified version of Gus Mueller’s build script. Here’s the usage:
build.sh -p ProjectName -t tag [-u [-w tweet]]
The two required parameters are a project name and a tag. Optionally you can upload and then tweet about the newly available build. I do all of my public builds in a separate clone of my git repo that never gets edited or otherwise messed with (~/Development/building/). There’s a whole lot going on in this script that could be explained, but for now I’ll just leave you with the code to walk through, and if something needs clarified, post a comment and I’ll do my best.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | #!/bin/bash upload=0 tweet="" while getopts ":t:up:w:" Option do case $Option in w ) tweet=$OPTARG;; u ) upload=1;; p ) project=$OPTARG;; t ) tag=$OPTARG;; esac done shift $(($OPTIND - 1)) if [ "$project" == "" ]; then echo "No project specified" exit fi if [ "$tag" == "" ]; then echo "No tag specified" exit fi # Configuration final_builds=~/Development/release_builds code_folder=~/Development/building/$project build_folder=$code_folder/build keys_folder=~/Development/keys upload_destination=user@yourcompany.com:/usr/local/apache2/htdocs/downloads/ release_notes_webfolder=http://yourcompany.com/releasenotes downloads_webfolder=http://yourcompany.com/downloads twitter_uname=someone@somewhere.com twitter_pword=password if [ ! -d $final_builds ]; then mkdir $final_builds fi # clean up if [ -d $build_folder ]; then rm -rf $build_folder fi cd $code_folder git pull origin master git pull --tags git checkout $tag sed -i "" 's/__VERSION__/'$tag'/g' Info.plist echo building project xcodebuild -target $project -configuration Release OBJROOT=$build_folder SYMROOT=$build_folder OTHER_CFLAGS="" if [ $? != 0 ]; then echo "Bad build for $project" say "bad build!" else #ok, let's index the documentation if we've got it. #/Developer/Applications/Utilities/Help\ Indexer.app/Contents/MacOS/Help\ Indexer "/tmp/buildapp/build/Release/BuildApp.app/Contents/Resources/English.lproj/BuildAppHelp" mv $build_folder/Release/$project.app $final_builds # make the zip file cd $final_builds zip -r $project-$tag.zip $project.app rm -rf $project.app if [ $upload == 1 ]; then echo uploading to server... # upload scp $project-$tag.zip $upload_destination # get values for appcast dsasignature=`$keys_folder/sign_update.rb $final_builds/$project-$tag.zip $keys_folder/$project\_dsa_priv.pem` filesize=`stat -f %z $final_builds/$project-$tag.zip` pubdate=`date "+%a, %d %h %Y %T %z"` cd $code_folder cfbundleversion=`agvtool what-version -terse` #output appcast item echo echo Put the following text in your appcast echo "<item>" echo "<title>Version $tag</title>" echo "<sparkle:releaseNotesLink>" echo "$release_notes_webfolder/$project.html" echo "</sparkle:releaseNotesLink>" echo "<pubDate>$pubdate</pubDate>" echo "<enclosure url=\"$downloads_webfolder/$project-$tag.zip\"" echo "sparkle:version=\"$cfbundleversion\"" echo "sparkle:shortVersionString=\"$tag\"" echo "sparkle:dsaSignature=\"$dsasignature\"" echo "length=\"$filesize\"" echo "type=\"application/octet-stream\" />" echo "</item>" if [ "$tweet" != "" ]; then echo "Calling twitter: $tweet" curl -u $twitter_uname:$twitter_pword -d status="$project $tag is up. $tweet" http://twitter.com/statuses/update.xml fi fi open $final_builds say "done building" fi cd $code_folder git checkout Info.plist rm -rf $build_folder |
Other References
Push tags to github Sparkle Basic Setup


[...] how to do this. I think I’m narrowing in on using a combination of Fraser’s “From Hacker to microISV” scripts for prepping for ad hoc distribution and Aral’s packaging script for actual [...]
[...] 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 [...]