Sparkleのアプリは、Info.plistのSUFeedURLに書かれたURLから、appcast.xmlを取得する。このURLは、アプリに埋め込まれるため、リリースのたびに変えられない。GitHub Releasesのreleases/latest/download/を使うと、最新のリリースに添付したappcast.xmlを、固定のURLで配信できる。
https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/appcast.xml
このURLは、最新のリリースのappcast.xmlにリダイレクトされる。新しいリリースを作ると、同じURLの先が最新のリリースに切り替わる。実行例はmacOS 27.0.1、GitHub CLI(gh)2.91.0のもの。
appcast.xmlの作り方は、generate_appcastの記事で紹介している。
参考: 【Sparkle】generate_appcastでappcast.xmlを作る
- 前の記事: 【Sparkle】ビルド番号とバージョン番号を使い分ける
- 親記事: GitHubを使い、macOSアプリを署名・公証・自動アップデート付きで公開する
- 次の記事: 【GitHub Actions】一時keychainに証明書をインポートして署名する
リリースにzipとappcast.xmlを添付する
gh release createで、タグを指定してリリースを作り、zipとappcast.xmlを添付する。
$ gh release create [タグ名] [アプリ名]-1.0.zip appcast.xml \
--repo [ユーザー名]/[リポジトリ名] --title "[タイトル]" --notes "[説明]"
https://github.com/[ユーザー名]/[リポジトリ名]/releases/tag/[タグ名]
Sparkleは、認証なしでappcast.xmlを取得する。そのため、リポジトリは公開リポジトリにする。プライベートリポジトリのリリースでは、アセットの取得に認証が必要になる。
参考: 【GitHub CLI】gh repo createでリポジトリ作成からpushまでまとめて実行する
latest/downloadのリダイレクトを確認する
releases/latest/download/[アセット名]は、2段階のリダイレクトを経て、アセットのダウンロードになる。curl -sILで、リダイレクトの流れを確認できる。
$ curl -sIL "https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/appcast.xml" | grep -iE '^(HTTP|location)'
HTTP/2 302
location: https://github.com/[ユーザー名]/[リポジトリ名]/releases/download/[最新のタグ名]/appcast.xml
HTTP/2 302
location: https://release-assets.githubusercontent.com/github-production-release-asset/[...]
HTTP/2 200
1つ目のリダイレクトで、releases/download/[最新のタグ名]/appcast.xmlに移る。このURLは、最新のリリースのタグ名を含む。2つ目のリダイレクトで、署名付きのダウンロードURLに移る。署名付きのURLには、有効期限がある。
-Lを付けずにcurl -sIを実行すると、1つ目のリダイレクト先だけが表示される。latestが、どのタグを指しているかを確認できる。
$ curl -sI "https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/appcast.xml" | grep -i '^location'
location: https://github.com/[ユーザー名]/[リポジトリ名]/releases/download/[最新のタグ名]/appcast.xml
latestの対象はdraftとprereleaseを除く最新のリリース
latestが指すのは、draft(下書き)とprerelease(プレリリース)を除く、最新のリリースである。
prereleaseを作成しても、latestは切り替わらない。
$ gh release create [タグ名] appcast.xml --repo [ユーザー名]/[リポジトリ名] --prerelease --title "[タイトル]" --notes "[説明]"
$ gh release list --repo [ユーザー名]/[リポジトリ名]
[タイトル] Pre-release [タグ名] [作成日時]
[前のタイトル] Latest [前のタグ名] [作成日時]
gh apiで、latestのリリースを確認できる。prereleaseの作成後も、前のリリースのままである。
$ gh api repos/[ユーザー名]/[リポジトリ名]/releases/latest --jq .tag_name
[前のタグ名]
prereleaseは、latest/download/の対象から外れるが、タグを指定したURLでは取得できる。
$ curl -sIL -o /dev/null -w '%{http_code}\n' "https://github.com/[ユーザー名]/[リポジトリ名]/releases/download/[prereleaseのタグ名]/appcast.xml"
200
draftは、--draftで作成する。draftのリリースは、認証なしのAPIの一覧にも、公開ページにも表示されない。公開ページのURLは、404になる。
$ gh release create [タグ名] appcast.xml --repo [ユーザー名]/[リポジトリ名] --draft --title "[タイトル]" --notes "[説明]"
https://github.com/[ユーザー名]/[リポジトリ名]/releases/tag/untagged-[ハッシュ]
draftは、作成した時点ではタグが作られず、URLの末尾がuntagged-から始まる。latestにも影響しない。
リリースの前にappcast.xmlを確認したいときは、draftやprereleaseで作成する方法がある。latestが切り替わらないため、既存のユーザーのアプリには、影響しない。
リリースの作成から反映までに時間がかかる
新しいリリースの作成から、releases/latest/download/の指す先が新しいタグへ切り替わるまでは、時間を要する。gh release listとgh apiは、作成の直後から、新しいリリースをLatestとして表示する。一方、latest/download/のリダイレクトは、数十秒から数分の間、前のタグを指したままだった。
$ gh release list --repo [ユーザー名]/[リポジトリ名]
[新しいタイトル] Latest [新しいタグ名] [作成日時]
$ curl -sI "https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/appcast.xml" | grep -i '^location'
location: https://github.com/[ユーザー名]/[リポジトリ名]/releases/download/[前のタグ名]/appcast.xml
反映の途中で、新しいタグのアセットのURLにアクセスすると、404が返る。この404は、しばらく残る場合があった。新しいzipのURLを反映の前に確認すると、反映の後も、しばらく404になる。
リリースの確認では、反映の遅延を考慮する。
- リリースの正しさは、
gh api repos/[ユーザー名]/[リポジトリ名]/releases/latestで確認する latest/download/のURLは、1ホップ目のlocationが新しいタグに切り替わってから確認する- 切り替わる前に、アセットのURLへ何度もアクセスしない
旧バージョンのURLは失われる
appcast.xmlには、旧バージョンのエントリが含まれる。SUFeedURLのディレクトリがreleases/latest/download/であれば、旧バージョンのzipのurlも、releases/latest/download/になる。
<enclosure url="https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/[アプリ名]-1.0.zip" ... />
最新のリリースに、旧バージョンのzipが添付されていなければ、このURLは404になる。
$ curl -sIL -o /dev/null -w '%{http_code}\n' "https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/[アプリ名]-1.0.zip"
404
タグを指定したURL(releases/download/[旧タグ名]/[アプリ名]-1.0.zip)であれば、取得できる。
参考: 【Sparkle】古いバージョンから更新できることを動作確認する
$ curl -sIL -o /dev/null -w '%{http_code}\n' "https://github.com/[ユーザー名]/[リポジトリ名]/releases/download/[旧タグ名]/[アプリ名]-1.0.zip"
200
Sparkleは、appcast.xmlの最新のエントリのURLを使って更新する。旧バージョンのエントリのURLが404でも、最新のエントリが取得できれば、更新は成功する。
不要なリリースを削除する
検証用のリリースは、gh release deleteで削除する。1回のコマンドで指定できるタグは、1つだけである。--cleanup-tagを付けると、タグも削除される。
$ gh release delete [タグ名] --cleanup-tag --yes --repo [ユーザー名]/[リポジトリ名]
複数のタグを削除するときは、ループで実行する。
$ for t in [タグ名1] [タグ名2]; do gh release delete $t --cleanup-tag --yes --repo [ユーザー名]/[リポジトリ名]; done
draftのリリースには、タグがない。--cleanup-tagを付けると、HTTP 422: Reference does not existが表示されるが、リリースは削除される。
