From Hacker to microISV: Tagging, Building and Releasing

by Fraser Hess

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:

if [ "$tag" == "" ]; then
	echo "No tag specified"
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.



while getopts ":t:up:w:" Option
  case $Option in
    w ) tweet=$OPTARG;;
    u ) upload=1;;
    p ) project=$OPTARG;;
    t ) tag=$OPTARG;;
shift $(($OPTIND - 1))

if [ "$project" == "" ]; then
	echo "No project specified"

if [ "$tag" == "" ]; then
	echo "No tag specified"

# Configuration

if [ ! -d  $final_builds ]; then
	mkdir $final_builds

# clean up
if [ -d $build_folder ]; then
	rm -rf $build_folder

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!"
	#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 Put the following text in your appcast
		echo ""
		echo "Version $tag"
		echo ""
		echo "$release_notes_webfolder/$project.html"
		echo ""
		echo "$pubdate"
		echo ""
		echo ""
		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
	open $final_builds
	say "done building"

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 […]