I have been working on an internal OS X application recently. Some of the users are non-technical, so instead of asking folks to download and compile the source every time, we used the Github Releases to host the binary application files.
To get people to actually download the new release, I have been sending out emails linking to the download page.
However, this process involves:
- Open the email
- Open the link to the download page
- Download the zip file containing the binary
- Extract the application from the zip file
- Move and replace the old application
- Re-launch the application
And it turns out to be quite error-prone, it would be great to have some automatic update scheme.
Since this is an internal tool, we cannot rely on the Mac App Store to handle these updates. Luckily, there is a open source alternative out there – Sparkle. From the website:
Sparkle is an easy-to-use software update framework for Mac applications.
It provided the ability to check, download, install and re-launch the updates, all without leaving the application itself.
To check for updates, Sparkle will check for an Appcast file. The file is essentially a RSS feed for software updates. Within the feed, it includes release notes to be displayed in the update window and download links to the corresponding zipped binaries. The format is pretty straight forward to understand.
…is Not Enough
There is one problem though, Sparkle did not provide anything to create the file out of the box; it has to be coded by hand, and when there are tons of releases, it quickly gets tedious.
Also, since we are already hosting the binaries releases on Github (and serves it with https, which is important), it would be nice to not have to spin up another server just to host the appcast file.
After googling around, I found people are having similar needs are suggesting creating tools that orchestrate the process of creating a release by calling Github Release api to generate appcast xml.
But I am lazy and I do not really want to code up a tool that needs to
- Authenticate with Github
- Pull down releases
- Render Markdown into HTML
- Upload them to Github Pages
Github Pages (Jekyll/Liquid)
Wait……did we mention Github Pages?
Turns out there is an easier way to get access to the release information, they are already available to the rendering context in Repository Metadata, containing everything one can obtain by calling the Release REST API.
However, there is one thing missing from the Release API.
For Sparkle’s default version check mechanism,
Info.plist are used to determine if an update is needed and
CFBundleShortVersionString is used for actual version presented to user. However, in Github Release, there is only on
tag file corresponding to the tag of the code on the repo.
Again, I am lazy and do not really want to write extra version resolve logic for sparkle to checkout the git repo and extra the version. This will also involve access to Github API and defeat the purpose.
So I decided to encode that information into the filename of the binary release.
If we name the file as
Project.v2.2.b20151781.zip, we can extract them with some Liquid magic.
I have hardcoded the Project name here, but it can be extracted from repo name as well.
With that solved, then it is pretty much gluing different parts together.
To generate correct date format, we used
date_to_rfc822 from Jekyll; to display html for release notes, the
markdownify filter was used to transform the markdown used in Github Release.
Note that I have also broken out a
release_only flag so we can take use of the pre-release for alpha channel, then I can override them in individual file.
The files are added to
gh-pages branch of the repo for Jekyll generation. Again, I suggest you checkout the documentation for Github Pages if you are unfamiliar with the process.
To further automate the release and not having to manually change the filename of my binary releases. I have also added two scripts to automate the version bump and archive process for the naming convention.
The scripts could be more DRY since they share some common environment variables. But they works as of now.
Now my workflow for releasing a new version:
- Bump the version and build the zip file using the script
- Create a new release on Github, upload zip file and fill out release notes as normal.
- Trigger the build by updating the
gh-pagesbranch. I usually just merge
masterinto the branch, you can also force the build by an empty commit, if your
gh-pagesis not kept up to date with
That’s it, the next time a user check for an update, it will be downloaded from Github Releases, sweet.
If you haven’t find out already, all the files are located in this gist.