Mac App Store外で配布するmacOSアプリに、自動アップデートの機能を組み込むときは、Sparkleを使う。SparkleはmacOS向けのアップデート用フレームワークで、更新の確認、ダウンロード、署名の検証、インストールまでを担当する。

Swift Package Manager(SwiftPM)でSparkleを導入し、更新チェックを組み込む手順は、次の3つである。

  1. XcodeでSparkleのパッケージを追加する
  2. 更新チェックのコードを書く
  3. Info.plistに、更新情報の配信先(SUFeedURL)を設定する

実行例はXcode 27.0、macOS 27.0.1、Sparkle 2.10.0のもの。アプリはSwiftUIで作成している。

Sparkleのパッケージを追加する

Xcodeのメニューから「File > Add Package Dependencies…」を開き、検索欄にSparkleのリポジトリのURLを入力する。

https://github.com/sparkle-project/Sparkle

Add Package Dependenciesの画面

「Dependency Rule」の既定は「Up to Next Major Version」で、現在の最新版(2.10.0)から次のメジャーバージョン(3.0.0)の手前までを指定する。「Add Package」を押すと、パッケージの取得が始まる。

取得が終わると、「Choose Package Products for Sparkle」が表示される。「Add to Target」の既定はNoneである。

Add to Targetが「None」の状態

Noneのままでは、アプリにSparkleがリンクされない。「Add to Target」を、アプリのターゲットに変更する。

Add to Targetをアプリのターゲットに変更した状態

「Add Package」を押すと、パッケージが追加される。Xcodeのナビゲーターに「Package Dependencies」のセクションが現れ、Sparkleのバージョンが表示される。

Package Dependenciesに追加されたSparkle

パッケージの追加で、次のファイルが変更される。

  • project.pbxprojに、パッケージの参照、プロダクトの依存、リンクの設定が追加される
  • [プロジェクト名].xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedが作成され、解決されたバージョン(2.10.0)が記録される

Package.resolvedをgitで管理すると、チーム全員が同じバージョンのSparkleを使える。

更新チェックのコードを書く

更新チェックには、SPUStandardUpdaterControllerを使う。SparkleのUIを含む標準のコントローラである。

SwiftUIのアプリでは、Appのプロパティとしてコントローラを持ち、メニューから更新確認を呼び出す。

import SwiftUI
import Sparkle

@main
struct [アプリ名]App: App {
    private let updaterController = SPUStandardUpdaterController(
        startingUpdater: true,
        updaterDelegate: nil,
        userDriverDelegate: nil
    )

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandGroup(after: .appInfo) {
                Button("Check for Updates…") {
                    updaterController.checkForUpdates(nil)
                }
            }
        }
    }
}

startingUpdater: trueを指定すると、アプリの起動時にSparkleの更新機能が開始する。updaterDelegateとuserDriverDelegateは、動作や画面をカスタマイズするときに使う。今回はnilにしている。

CommandGroup(after: .appInfo)で、アプリメニューの「About」の下に「Check for Updates…」を追加している。メニューから実行すると、checkForUpdates(nil)で更新の確認が始まる。

更新の確認中にメニューを無効にしたい場合は、SPUUpdaterのcanCheckForUpdatesを使う。このコードでは、省略している。

このコードは、Xcode 27.0でそのままビルドできる。

Info.plistに配信先を設定する

Sparkleは、Info.plistのSUFeedURLから、更新情報(appcast)の取得先を読み取る。あわせて、自動で更新を確認するかをSUEnableAutomaticChecksで指定する。

キー値
SUFeedURLappcastのURL
SUEnableAutomaticCheckstrueにすると、自動で更新を確認する

INFOPLIST_KEYでは設定できない

XcodeのプロジェクトがGENERATE_INFOPLIST_FILE = YES(Info.plistを自動生成する設定)のとき、ビルド設定のINFOPLIST_KEY_SUFeedURLを設定しても、Info.plistに出力されない。SUFeedURLのような独自のキーは、INFOPLIST_KEY_の接頭辞では埋め込めない。

$ plutil -p [アプリ名].app/Contents/Info.plist | grep SU

INFOPLIST_KEY_SUFeedURLとINFOPLIST_KEY_SUEnableAutomaticChecksを設定してビルドしても、この出力は空になる。

Info.plistのファイルを追加する

独自のキーを出力するには、Info.plistのファイルを用意し、ビルド設定のINFOPLIST_FILEに指定する。GENERATE_INFOPLIST_FILE = YESのままでも、自動生成される内容と、用意したファイルの内容がマージされる。

プロジェクトの直下に、Info.plistを作成する。

<?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>SUFeedURL</key>
    <string>https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/appcast.xml</string>
    <key>SUEnableAutomaticChecks</key>
    <true/>
</dict>
</plist>

アプリのターゲットの「Build Settings」で、INFOPLIST_FILE(Info.plist File)にInfo.plistを指定する。project.pbxprojでは、次の設定になる。

INFOPLIST_FILE = Info.plist;

ビルドしたアプリのInfo.plistに、SUFeedURLとSUEnableAutomaticChecksが出力されていることを確認する。

$ plutil -p [アプリ名].app/Contents/Info.plist | grep -E 'SU|BundleVersion|ShortVersion'
  "CFBundleShortVersionString" => "1.0"
  "CFBundleVersion" => "1"
  "SUEnableAutomaticChecks" => true
  "SUFeedURL" => "https://github.com/[ユーザー名]/[リポジトリ名]/releases/latest/download/appcast.xml"

SUFeedURLのappcastをreleases/latest/download/のURLで配信する方法は、GitHub Releasesの記事で解説する。更新の署名を検証するためのSUPublicEDKeyは、鍵の生成の記事で追加する。

参考: 【GitHub Releases】appcast.xmlを固定URLで配信する

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

ビルドしたアプリにSparkleが含まれる

ビルドしたアプリのContents/Frameworksには、Sparkle.frameworkが含まれる。

$ ls [アプリ名].app/Contents/Frameworks
Sparkle.framework

Sparkleのフレームワークの内部には、更新のダウンロードやインストールを担当するXPCサービス(Downloader.xpc、Installer.xpc)と、Updater.app、Autoupdateが含まれる。署名の検証では、これらも対象になる。

更新の確認を実行する

ビルドしたアプリを起動し、アプリメニューの「Check for Updates…」を実行すると、更新の確認が始まる。

SUEnableAutomaticChecksをtrueにしても、起動の直後に自動で確認されるとは限らない。起動してから約50秒の間は、自動の確認は実行されなかった。動作の確認には、メニューから実行する。

Sparkleのコマンドラインツールの場所

Sparkleのパッケージには、次のコマンドラインツールが含まれる。

ツール用途
generate_keys更新の署名に使う鍵(EdDSA)を生成する
generate_appcast配布するアーカイブからappcastを生成する
sign_updateアーカイブに署名する、または署名を検証する
BinaryDelta差分更新のファイルを作成・適用する

これらは、XcodeがSparkleのパッケージを取得したときに、DerivedDataに展開される。Xcodeから開いたプロジェクトでは、次のパスにある。

~/Library/Developer/Xcode/DerivedData/[プロジェクト名]-[ハッシュ]/SourcePackages/artifacts/sparkle/Sparkle/bin/

xcodebuildに-derivedDataPathを指定してビルドした場合は、指定したディレクトリのSourcePackages/artifacts/sparkle/Sparkle/bin/に展開される。

$ ls [DerivedDataのパス]/SourcePackages/artifacts/sparkle/Sparkle/bin
BinaryDelta
generate_appcast
generate_keys
old_dsa_scripts
sign_update

SourcePackages/checkouts/Sparkle/にも、同じ名前のgenerate_keysなどが存在するが、これはディレクトリである。実行できるファイルは、artifacts/の下のbin/にある。

DerivedDataに複数のプロジェクトがある場合は、findで探す。

$ find ~/Library/Developer/Xcode/DerivedData -path '*Sparkle/bin/generate_keys'

App Sandboxを使う場合の注意

ここで検証したアプリは、App Sandboxを無効にしている。App Sandboxを有効にするアプリでは、Sparkleの動作に追加の設定が必要になる。設定の内容は、Sparkleの公式ドキュメントで確認する。