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:
#!/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.
#!/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 "- "
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
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 […]