Application Update
This tutorial will guide you through the process of setting up an update feature for your Cocoa application, with the great help of the Sparkle framework. The support for this tutorial projet is the SparkleSampleApp, which is part of the Monobjc samples.
The tutorial is divided into the following steps:
The tutorial is focused on the Monobjc side of the Sparkle's integration, not on Sparkle itself. Please refer to Sparkle documentation for Sparkle related issue.
Requirements
You need to install Sparkle framework in order to use it (pretty obvious, isn't it ?). You can download the redistribution package from the Sparkle website. Once decompressed, drag the Sparkle.framwork folder to a standard location (/System/Library/Framewors, /Library/Framewors or ~/Library/Framewors).
Adding Sparkle support
Adding Sparkle support to an existing application is fairly simple:
- Open up your main NIB file (usually MainMenu.nib) in Interface Builder.
- Go to File > Read Class Files… and choose all the files inside
Sparkle.framework/Headersfolder. - Drag a generic Object (a blue cube) from the Library to your document.
- Select this object in your document window, and under the Information tab of the inspector, set the class of the object to
SUUpdater. This will instantiate your Sparkle updater object. - If you'd like, make a "Check for Updates…" menu item in the application menu; set its target to the
SUUpdaterinstance and its action tocheckForUpdates:.
Packaging with the Sparkle Framework
As Sparkle is not a standard framework (it is not present on every Mac), you must include it within your application bundle. Mac OS X can embed frameworks as private frameworks, which means that they are restricted to the application bundle's scope.
The <mkappl/> task manages all the work to ease lookup and embedding of private framework. The NAnt script below shows how to use it:
<?xml version="1.0"?>
<project name="Monobjc Application" default="build" basedir=".">
<description>Monobjc Application Build File</description>
<property name="lib.dir" value="lib" overwrite="false"/>
<property name="build.dir" value="build" overwrite="false"/>
<property name="dist.dir" value="dist" overwrite="false"/>
<property name="app.name" value="SparkleSampleApp" overwrite="false"/>
<!-- ===============================================================================
Remove all the generated files
================================================================================ -->
<target name="clean" description="Remove all the generated files">
<delete dir="${build.dir}" failonerror="false" />
<delete dir="${dist.dir}" failonerror="false" />
</target>
<!-- ===============================================================================
Prepare the build
================================================================================ -->
<target name="prepare" description="Prepare the build">
<!-- Creates folders -->
<mkdir dir="${build.dir}" failonerror="false" />
<mkdir dir="${dist.dir}" failonerror="false" />
<!-- Loads custom tasks -->
<loadtasks assembly="${lib.dir}/NAnt.Monobjc.dll" />
</target>
<!-- ===============================================================================
Build the application
================================================================================ -->
<target name="build" description="Build the application" depends="prepare">
<!-- Compile source files -->
<csc target="exe" output="${build.dir}/${app.name}.exe">
<sources>
<include name="**/*.cs"/>
</sources>
<references basedir=".">
<include name="${lib.dir}/Monobjc.dll"/>
<include name="${lib.dir}/Monobjc.Cocoa.dll"/>
<include name="${lib.dir}/Monobjc.Sparkle.dll"/>
</references>
</csc>
<!-- Create the application bundle -->
<mkappl name="${app.name}"
icon="Monobjc.icns"
infoplist="Info.plist"
todir="${dist.dir}">
<!-- Copy bridge libraries -->
<copy-in-macos basedir="${lib.dir}">
<include name="libmonobjc.*.dylib"/>
</copy-in-macos>
<!-- Copy executable -->
<copy-in-resources basedir="${build.dir}">
<include name="${app.name}.exe"/>
</copy-in-resources>
<!-- Copy libraries -->
<copy-in-resources basedir="${lib.dir}">
<include name="Monobjc.dll"/>
<include name="Monobjc.Cocoa.dll"/>
<include name="Monobjc.Sparkle.dll"/>
</copy-in-resources>
<!-- Copy other files or folder -->
<copy-in-resources basedir=".">
<include name="*.lproj/**"/>
<include name="dsa_pub.pem"/>
</copy-in-resources>
<!-- Copy private frameworks -->
<copy-in-frameworks framework="Sparkle"/>
</mkappl>
</target>
</project>
Publishing updates
The application updates are made available through an appcast (based on RSS). The appcast URL is stored in the Info.plist file of the application, so the update engine can make update queries. Here is an example of Info.plist containing Sparkle keys:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>SparkleSampleApp</string>
<key>CFBundleIconFile</key>
<string>Monobjc</string>
<key>CFBundleIdentifier</key>
<string>net.monobjc.samples.SparkleSampleApp</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUFeedURL</key>
<string>http://downloads.monobjc.net/sparkle/sparkletestcast.xml</string>
<key>SUEnableSystemProfiling</key>
<true/>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
</dict>
</plist>
To file the appcast, you need the following:
- A zip file containing the updated application. Simply create a zip with the update application and upload it to your website.
- A release notes file. This is a HTML file, that is downloaded from the website and displayed to the user.
- The DSA signature of the update zip file (Sparkle now mandates that update are signed or delivered by SSL). See the Sparkle Basic Setup to learn how to generate and use the DSA key pair.
Once you have all this elements, you can publish the update. Below an example of the appcast XML file processed by the update engine:
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>Sparkle Sample App Changelog</title>
<link>http://downloads.monobjc.net/sparkle/sparkletestcast.xml</link>
<description>Most recent changes with links to updates.</description>
<language>en</language>
<item>
<title>Version 2.0 (2 bugs fixed; 3 new features)</title>
<sparkle:releaseNotesLink>http://downloads.monobjc.net/sparkle/notes_2.0.html</sparkle:releaseNotesLink>
<pubDate>Wed, 09 Jan 2008 19:20:11 +0000</pubDate>
<enclosure url="http://downloads.monobjc.net/sparkle/SparkleSampleApp_2.0.zip"
length="10034838" type="application/octet-stream"
sparkle:dsaSignature="MC0CFHbL8KPRLJoC8+siBUmIR1QAT53cAhUAitn9ULHTgXt1Sjq3Nt0b6qVHY8o="/>
</item>
</channel>
</rss>
For futher information of what Sparkle can do for your, go the Sparkle documentation wiki.