Sparkleの更新が正しく動くかは、古いバージョンのアプリを起動して、実際に新しいバージョンへ更新して確認する。手順は次のとおりである。

  1. 古いバージョン(v1.0.0)のアプリをインストールして起動する
  2. 新しいバージョン(v1.0.1)をリリースする
  3. 古いバージョンのアプリで、更新を確認する
  4. 更新をインストールして、新しいバージョンで再起動することを確認する

実行例はSparkle 2.10.0、macOS 27.0.1のもの。画面はSparkleの英語のUIで、アプリのメニューは日本語である。リリースは、GitHub Actionsの自動化したワークフローから作成している。

古いバージョンのアプリを用意する

GitHub Releasesから、古いバージョンのzipをダウンロードして展開する。

$ gh release download v1.0.0 --repo [ユーザー名]/[リポジトリ名] --dir v1
$ ditto -x -k v1/[アプリ名]-1.0.0.zip v1/x
$ plutil -p v1/x/[アプリ名].app/Contents/Info.plist | grep -E 'ShortVersion|BundleVersion'
  "CFBundleShortVersionString" => "1.0.0"
  "CFBundleVersion" => "1"

アプリは、更新の際に書き換えられる。更新を確認する前に、書き込みできる場所へコピーしておく。

$ ditto v1/x/[アプリ名].app install/[アプリ名].app
$ open install/[アプリ名].app

更新を検出する

アプリが起動したら、アプリメニューの「Check for Updates…」を実行する。新しいバージョンがあれば、「Software Update」のウィンドウが表示される。

新しいバージョンの通知

A new version of [アプリ名] is available!
[アプリ名] 1.0.1 is now available—you have 1.0.0. Would you like to download it now?
[Skip This Version] [Remind Me Later] [Install Update]

ウィンドウには、「Automatically download and install updates in the future」のチェックボックスも表示される。

メッセージの「1.0.1」と「1.0.0」は、sparkle:shortVersionStringの表示用のバージョンである。更新の判定には、ビルド番号のsparkle:versionが使われる。

参考: 【Sparkle】ビルド番号とバージョン番号を使い分ける

自動の更新確認はすぐには実行されない

Info.plistのSUEnableAutomaticChecksをtrueにしていても、アプリを起動した直後に、自動で更新が確認されるとは限らない。起動してから約50秒の間は、自動の確認は実行されず、SULastCheckTimeも記録されなかった。動作の確認には、メニューの「Check for Updates…」から手動で実行する。

更新をインストールする

「Install Update」を押すと、更新のダウンロードと署名の検証が始まる。完了すると、「Ready to Install」になる。

インストールの準備ができた状態

「Install and Relaunch」を押すと、アプリが新しいバージョンに置き換わり、再起動する。

更新後のアプリを確認する

再起動したアプリは、新しいバージョンになっている。

$ plutil -p install/[アプリ名].app/Contents/Info.plist | grep -E 'ShortVersion|BundleVersion'
  "CFBundleShortVersionString" => "1.0.1"
  "CFBundleVersion" => "2"

署名、staple、Gatekeeperの判定も、更新後のアプリで確認できる。

$ codesign --verify --deep --strict install/[アプリ名].app
$ xcrun stapler validate install/[アプリ名].app
The validate action worked!
$ spctl --assess --type execute -vv install/[アプリ名].app
install/[アプリ名].app: accepted
source=Notarized Developer ID

更新の確認の記録は、アプリの設定ファイルに保存される。SULastCheckTimeが、最後に更新を確認した日時である。

$ plutil -p ~/Library/Preferences/[Bundle ID].plist
{
  "SUHasLaunchedBefore" => true
  "SULastCheckTime" => [最後に確認した日時(UTC)]
}

更新の直後は、defaults read [Bundle ID]が、この値を返さない場合があった。設定ファイルの内容は、plutil -pで直接確認する。

更新が検出されないとき、失敗するときの切り分け

更新が検出されない、または失敗する場合は、次の順に確認する。

  1. feedとzipが取得できるか(URL、404、リダイレクト、反映の遅延)
  2. ビルド番号(sparkle:version)が、インストール済みのアプリよりも大きいか
  3. sparkle:minimumSystemVersionが、使っているmacOSよりも高くないか
  4. 署名が正しいか(SUPublicEDKeyと、署名に使った秘密鍵の組み合わせ)

切り分けの環境を用意する

appcast.xmlの内容を変えて、Sparkleの反応を確認できる。ローカルでHTTPサーバーを起動し、アプリのSUFeedURLを、そのサーバーに向ける。

feedのURLは、Info.plistのSUFeedURLで決まる。defaults write [Bundle ID] SUFeedURL ...でアプリの設定を書き換えても、feedのURLは変わらない。検証用のアプリのコピーでは、Info.plistを書き換え、署名し直す。

$ plutil -replace SUFeedURL -string "http://localhost:8765/appcast.xml" [アプリ名].app/Contents/Info.plist
$ codesign -f -s [SHA-1ハッシュ] --options runtime --timestamp=none [アプリ名].app
$ python3 -m http.server 8765 --directory www --bind 127.0.0.1

アプリを起動して、「Check for Updates…」を実行したとき、HTTPサーバーのログに、GET /appcast.xmlが記録されていれば、アプリはサーバーに接続している。リクエストが記録されていない場合は、feedのURLが書き換わっていない。

Sparkleの表示と原因

appcast.xmlの内容を変えたときの、Sparkleの表示は次のとおりである。

appcast.xmlの状態Sparkleの表示
新しいバージョンがあるA new version of [アプリ名] is available!
sparkle:versionがインストール済みと同じYou’re up to date! / [アプリ名] 1.0.0 is currently the newest version available.
sparkle:minimumSystemVersionが高いYour macOS version is too old / [アプリ名] 1.0.1 is available but your macOS version is too old to install it. At least macOS 99.0 is required.
feedが404Update Error! / An error occurred in retrieving update information. Please try again later.
sparkle:edSignatureが一致しない、または属性がないダウンロード後に、Update Error! / The update is improperly signed and could not be validated. Please try again later or contact the app developer.

sparkle:versionの比較は、表示用のバージョンではなく、ビルド番号で行われる。sparkle:shortVersionStringが新しくても、sparkle:versionが同じであれば、You’re up to date!になる。

署名のエラーは、feedの取得ではなく、zipをダウンロードした後に表示される。署名が一致しない場合と、署名の属性がない場合は、同じメッセージである。

署名の確認には、sign_update --verifyが使える。

参考: 【Sparkle】EdDSA署名鍵を生成して公開鍵をInfo.plistに設定する

$ sign_update --account [アカウント名] --verify [アプリ名]-1.0.1.zip "[sparkle:edSignatureの値]"

--accountを省略すると、既定のed25519の鍵が使われる。別のアカウント名で生成した鍵の署名は、検証に失敗する。署名の検証に失敗したときは、署名した鍵と、検証に使う鍵が一致しているかを確認する。

enclosureのlengthが実際のサイズと違っていても、署名が正しければ、検証は通り、「Ready to Install」まで進んだ。

メニューの操作を自動化する

動作確認を繰り返すときは、AppleScriptで、メニューやボタンの操作を自動化できる。

$ osascript -e 'tell application "System Events" to tell process "[アプリ名]" to click menu item "Check for Updates…" of menu 1 of menu bar item 2 of menu bar 1'
$ osascript -e 'tell application "System Events" to tell process "[アプリ名]" to click button "Install Update" of window "Software Update"'

ターミナルのアプリに、アクセシビリティの権限が必要になる場合がある。