Better Sparkle Appcasts With Jekyll

This is an old post!

This post is over 2 years old. Solutions referenced in this article may no longer be valid. Please consider this when utilizing any information referenced here.

If you have done and OS X/macOS development, especially any that predated the Mac App Store, you are probably aware of Sparkle. Even if you haven’t done any development, you have probably used Sparkle because it was basically the de facto method of providing update functionality in Mac Apps, and even to this day is still widely used on many apps distributed outside the official App Store.

Updates are distributed to applications by means of an “appcast”, an extension of the RSS specification containing information about updates. RSS itself is based on XML, which means you can build them just like you would build any other published document.

The problem comes when you start having a lot of updates in an appcast. Maintaining a large file can become difficult. But fortunately, using Jekyll collections, we can generate a single appcast using multiple files that are much easier to maintain. And, as an added bonus, we can use that same data to generate a download and changelog page from the same data.

Collections

The first step is to set up a Jekyll Collection for our releases. Each release will be accompanied by a separate document in the collection specific to that release. To add this collection, edit your _config.yml file to add the collection.

collections_dir: collections
collections:
  example_tool_releases:
    permalink: /example-tool/notes/:name.html
    output: true

An important piece to notice there is the output: true part. This will render the documents as individual files at the permalink location.

Making Updates

Each Sparkle release needs to be accompanied by some additional data:

  • edSignature - A signature for your update. Sparkle requires you to sign your updates to protect your users against malicious updates.

  • version - The new version, which Sparkle uses to compare against the current version to determine whether an update is needed.

  • length - The side of the update in bytes.

That is the minimum you need, but you can set additional options as well. Generating these pieces of data is beyond the scope of this post, but is pretty thoroughly documented at the above link.

For each release, we are going to create a note file in collections/_example_tool_releases to accompany the release, with the above data set as YAML front matter and the release notes themselves in the body.

So, for example, to create a 1.0.2 release of our example tool, we would create the file collections/_example_tool_releases/1.0.2.md and edit it to look something like this:

---
name: 1.0.2
date: Fri, 29 Mar 2019 03:19:19 +0000
download: https://example.com/example-tool/example-tool-1.0.2.dmg
signature: <really long signature here>
length: 70979697
---
* Fix: no default tile size was set, resulting in no tiles being shown.
* Fix: bad data from the server could crash the client.
* Fix: changes didn't take effect until restarting.
* Fix: crash in image downloading.
* Disabled sandboxing.

You can create as many updates as you like as separate files. You could even have a build phase as part of your release that generates the markdown files from commit messages. And, as these are just standard Jekyll markdown files, you can use all the usual Jekyll and Liquid functionality to create complex representations (though be aware of the limits of the display medium inside the Sparkle update box.)

Generating the Appcast

So now that you have all these documents, we need to generate the appcast itself. Earlier, we said that the appcast is just an extension to RSS, which is itself based on XML. Knowing that, it is simply another standard Jekyll document that can use all the same liquid functionality.

So you could create a file called /example-tool/appcast.xml (or wherever your app is looking for its appcast at) like this:

---
baseurl: https://example.com
---
<?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>Example Tool Changelog</title>
        <link>{{ page.baseurl }}/example-tool/appcast.xml</link>
        <description>Most recent changes with links to updates.</description>
        <language>en</language>
        {% assign sorted = site.example_tool_releases | sort: 'date' | reverse %}
        {% for release in sorted %}
        <item>
            <title>Version {{ release.name }}</title>
            <sparkle:releaseNotesLink>
                {{ page.baseurl }}{{ release.url }}
            </sparkle:releaseNotesLink>
            <pubDate>{{ release.date }}</pubDate>
            <enclosure url="{{ release.download }}"
                    sparkle:version="{{ release.name }}"
                    sparkle:edSignature="{{ release.signature }}"
                    length="{{ release.length }}"
                    type="application/octet-stream" />
        </item>
        {% endfor %}
    </channel>
</rss>

The important thing here is to keep the --- at the top of the file so that Jekyll knows to parse the file for liquid tags. And, if you defined any additional front matter in your markdown files, you can add the appropriate XML tag to the loop here.

Generating A Changlog and Download Page

We can also take this same data and use it to generate a nice HTML changelog and download page:

---
layout: default
title: Example Tool
---
<div class="row">
    <div class="col">
        <h4>Download</h4>

        {% assign sorted = site.example_tool_releases | sort: 'date' | reverse %}
        {% for release in sorted %}
            <h5><a href="{{ release.download }}">Version {{ release.name }}</a></h5>
            <div>Released {{ release.date | date: '%B %d, %Y'}}, {{ release.length }} bytes</div>
            {{ release }}
        {% endfor %}
    </div>
</div>

Comments (0)

Interested in why you can't leave comments on my blog? Read the article about why comments are uniquely terrible and need to die. If you are still interested in commenting on this article, feel free to reach out to me directly and/or share it on social media.

Contact Me
Share It
Jekyll
I moved the dystill website to Jekyll and Bootstrap. This was pretty simple overall, since the site is just one page. It was more a task for converting the custom CSS I wrote to use the matching Boostrap libs. I also added the neat little ubiquitous “Fork me on Github” ribbon you see on a lot of sites. Go check it out at dystill.org.
Read More
What I Use
Since it’s been a good six years since I did one of these, here’s what I am using in the year 2022 as far as tech and tech-adjacent things.
Read More