6
Jul
2009
 

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:

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

Comments

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