LoginSignup
7
6

More than 5 years have passed since last update.

MacApp用アップデート管理システムSparkleの使い方(Xamarin.Mac編)

Last updated at Posted at 2015-12-02

Sparkle is...

配布したプログラムのアップデートは大きく使い勝手に影響しない限り,なかなかやってもらえないものです。
MacAppStoreに公開するならば,アップデートはそちらにおまかせすることもできます。
AppStoreに公開せず,自サイトで配布する,企業内で配布するようなアプリケーションには,自前でアップデータを実装しなくてはなりません。
ここを楽したいときはSparkleというフレームワークを使います。

証明書の作成やアップデート情報配信用フィードの作成は,こちらの記事を参考にしてください。
MacApp用アップデート管理システムSparkleの使い方 by econa77

この記事について,

  • OSX 10.11以降をターゲットにする場合はHTTPS通信必須
  • length はアーカイブのサイズ (wc -c < archive.zip)

という2点を補足しておきます。特に社内サーバに置いて更新を見に行かせるなどを考えている場合は注意してください。

用意するもの

この記事で使ったコードはすべてgithubにあります。
SparkleSample

Native binding

Sparkleをビルドする

CocoaPods経由でもできるはずですが,ビルドがどうも通らないためクローンして作っていくことにします。証明書を作る段階でリポジトリにあるシェルスクリプトが必要になるので,こちらの方が手っ取り早いかもしれません。
Sparkleをクローンして,そのディレクトリに移動します。
まずはmake releaseです。一時フォルダにSparkle-[version].tar.bz2が作られ,Finderに表示されます。このアーカイブを適当な場所に展開し,展開先でターミナルを開きます。

Objective Sharpie

Objective Sharpieをインストールすると,/usr/local/bin/sharpieが使えるようになります。これでまずはバインディング情報を生成します。自分でP/Invoke宣言書いてると死ぬので。

% sharpie bind --output=Sparkle.XamMac --namespace=Sparkle --sdk=macosx10.11 ./Sparkle.framework/Headers/*.h

--sdkに指定できるSDKは,sharpie xcode -sdksで取得できます。手元ではこんな感じ。

% sharpie xcode -sdks   
sdk: appletvos9.0    arch: arm64   
sdk: iphoneos9.1     arch: arm64   armv7   
sdk: macosx10.11     arch: x86_64  i386    
sdk: watchos2.0      arch: armv7  

成功すると,Sparkle.XamMac ディレクトリ以下にApiDefinitions.csStructsAndEnums.csが生成されます。
これをつかってバインディングライブラリを作っていくわけですが・・・ もちろんこのままでは使えません。

ApiDefinitions.cs の修正

sharpieコマンドはBinding Analysisレポートで,自動ではいまいち拾いきれなかった場所を明示してくれます。
ApiDefinitions.cs[Verify]属性がついているものは,すべて修正が必要です。Sparkleの場合は属性を削除するだけで大丈夫です。後述のbmacでエラーになった箇所を直していく形になります。
それなりに罠が潜んでいるので,githubに置いてあるApiDefinitions.csを参考になさってください。

バインディングライブラリのビルド

bmacを使ってライブラリをビルドしていきます。こんな感じのMakefileでいけます。

BUILD_FLAGS=-unsafe -target:library -nowarn:436 -nowarn:219
LIBRARY=./Sparkle.framework/Versions/A/
BMAC=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/bin/
BASE=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/Xamarin.Mac/
BINDING=./Sparkle.XamMac/

all: prepare osx

prepare:
    cp -R ./Sparkle.framework $(BINDING)

osx:
    $(BMAC)bmac --ns=Sparkle -o $(BINDING)Sparkle.dll \
    --baselib=$(BASE)Xamarin.Mac.dll --api=$(BINDING)ApiDefinitions.cs \
    -s=$(BINDING)StructsAndEnums.cs \
    --compiler=mcs --unified-mobile-profile

bmacがビルドを行っている部分です。
ここでビルドが失敗した場合は,ApiDefintions.csの記述に誤りがありますので直していきます。頻出エラーとその対策を少しだけ。

  • CS0246 参照解決できない。自動生成された型名を見直す。g.csでエラーが出た場合は,BaseType指定が足りない。[BaseType(typeof(NSObject))]などをつける。
  • CS0111 同じ引数を持つメソッドがある。名前を適当に見直す。
  • CS0579 StaticAttributeは一回しか使えない。たいていは消してよい。

もう後は Build&Pray です。
うまくできるとこんな感じ。
RevealFinder

ライブラリを使う

まずはXamarin.Macプロジェクトを作っていきます。今回はStoryboardを使いたくないので,ターゲットをOSX 10.9に設定しました。
CreateProject

参照に作ったSparkle.dllを追加します。
AddSparkleDll

プロジェクトのビルド後イベントを追加します。実行ファイルと同じディレクトリに,ネイティブのSparkle.frameworkをコピーします。
ウィンドウやローカライズといったリソースがない場合はダイナミックライブラリだけコピーしても構いません。Sparkleの場合はリソースファイルが必要なので,全てコピーします。
PostBuildEvent

Native Referencesというのもありますが,(現時点では)これは です。特に何も起きませんので,使わなくていいです。
NativeReferences

まずはこのライブラリが読み込めるか確かめましょう。Main.csを編集します。

Main.cs
using AppKit;
using System;
using System.IO;
using System.Reflection;
using ObjCRuntime;

namespace SparkleSample
{
    static class MainClass
    {
        static string GetCurrentExecutingDirectory()
        {
            string filePath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;
            return Path.GetDirectoryName(filePath);
        }

        static void Main(string[] args)
        {
            if (Dlfcn.dlopen(Path.Combine(GetCurrentExecutingDirectory(), "Sparkle.framework", "Sparkle"), 0) == IntPtr.Zero)
            {
                Console.Error.WriteLine("Unable to load the dynamic library.");
                Environment.Exit(1);
            }
            NSApplication.Init();
            NSApplication.Main(args);
        }
    }
}

Dlfcn.dlopenは,必ずNSApplication.Init()前に呼びます。
ここまでできたら,デバッグ実行してウィンドウが表示されることを確認します。これでライブラリを使うことができるようになりました。

Sparkleで更新を確認

前掲した記事を参考に,証明書ファイルとフィードURLの設定をInfo.plistに追加します。
Info.plist

適当にコントロールを貼っておきます。
Controls

で,MainWindowControllerに適当に書きます。

MainWindowController.cs
using System;
using Foundation;
using AppKit;
using Sparkle;
using System.Globalization;

namespace SparkleSample
{
    public partial class MainWindowController : NSWindowController
    {
        public MainWindowController(IntPtr handle)
            : base(handle)
        {
        }

        [Export("initWithCoder:")]
        public MainWindowController(NSCoder coder)
            : base(coder)
        {
        }

        public MainWindowController()
            : base("MainWindow")
        {
        }

        private static DateTime nsRef = new DateTime(2001, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

        public override void AwakeFromNib()
        {
            base.AwakeFromNib();
            updater = new SUUpdater();
            updater.AutomaticallyChecksForUpdates = false;
            UpdateDateLabel();
        }

        public new MainWindow Window
        {
            get { return (MainWindow)base.Window; }
        }

        SUUpdater updater;

        partial void CheckUpdates(NSObject sender)
        {
            updater.CheckForUpdates(new NSObject());
            UpdateDateLabel();
        }

        void UpdateDateLabel()
        {
            var lastUpdated = nsRef.AddSeconds(updater.LastUpdateCheckDate?.SecondsSinceReferenceDate ?? 0);
            DateLabel.StringValue = TimeZoneInfo.ConvertTimeFromUtc(lastUpdated, TimeZoneInfo.Local).ToString("u");
        }
    }
}

デバッグビルドで実行した場合は署名がないので警告が。
InsecureError

ふんふん。Check updatesしましょう?
SoftwareUpdate
よくできました。

Conclusion

自分で作成したライブラリもこの方法でかなり楽にバインディングできます。
特にまだXamarinバインディングのないAPIについて,ラッパーライブラリを書いてネイティブ参照で使う,というのはよくやる手だと思います。

というわけでMacの人から贈る,Advent Calender 3日目でした。

7
6
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6