Application Update

SparkleThis 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:

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:

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.