2012-10-02
_ OS X ネイティブなアプリケーションを含んだ gem を作成してみる
ちょっとしたツールはたいていシェルスクリプトや Ruby なんかでカバーできるんだけど,ときどき OS ネイティブなコードが必要な場面が出てきたりする.そこで,Mac OS X ネイティブなアプリケーションを gem のインストール時にビルドして利用したりできるのだろうかと思い,一応それっぽいことができたようなので,その手順を残しておく.
スケルトンの作成
bundle コマンドを利用して rubygem のスケルトンを作成する
$ bundle gem hello_world_notification --bin
指定した名前でディレクトリが作成されるので,その中に Mac アプリのコードを格納する ext ディレクトリを作成する.
$ mkdir hello_world_notification/ext
Mac アプリの作成
Xcode を利用して Cocoa Application の新規プロジェクトを先ほど作成した ext 以下に作成する.作成されたスケルトンで実行すると空のウィンドウが表示される.サンプルなのでこのままでもいいし,なんでもいいんだけど,ちょっとコードを変更して,起動すると通知センターへの通知を表示して終了するアプリにする.
#import "AppDelegate.h" @implementation AppDelegate - (void)dealloc { [super dealloc]; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSUserNotification *userNotification = [[NSUserNotification alloc] init]; userNotification.title = @"HelloNotification"; userNotification.informativeText = @"Hello World!!"; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; [NSApp terminate:self]; } @end
実行してみるとちゃんと通知センターで通知される.
Mac アプリをコマンドラインからビルドする
Mac アプリは xcodebuild コマンドを利用することで Xcode を使わなくてもアプリをビルドすることができる.また,xcconfig ファイルを利用するとビルド設定を定義することができるので必要な項目を記述して ext 以下に例えば hello_notification.xcconfig として作成する.ここではビルド先のディレクトリや出力先を定義している.
DSTROOT = ../../ INSTALL_PATH = /vendor/hello_world_notification SKIP_INSTALL = NO SYMROOT = build OBJROOT = $(SYMROOT)
GitHub を探してみると,rb-fseventで xcodebuild を利用してビルドを行うrakefile.rbがあったので,これを元に設定値を変えて利用してみる.
実際にビルド可能か,ext 以下で rake コマンドを実行してみると,トップディレクトリの vendor 以下にビルドされた Mac アプリがあることを確認できる.アプリを実行すると通知センターでの通知が表示されて正しく動作している.
Ruby から Mac アプリを呼び出す
呼び出しは単純に外部コマンドを実行する形にする.
#lib/hello_world_notification.rb require "hello_world_notification/version" module HelloWorldNotification BIN_PATH = File.expand_path( '../../vendor/hello_world_notification/HelloNotification.app/Contents/MacOS/HelloNotification', __FILE__ ) def self.execute `#{BIN_PATH}` raise "HelloNotification failure" unless $?.success? end end
#bin/hello_world_notification #!/usr/bin/env ruby require "hello_world_notification" HelloWorldNotification.execute
これで bin/hello_world_notification を実行すると通知センターで通知されることが確認できる.
gemspec の変更
これまで作成したファイルは git add していれば,gemspec の gem.files には必要なファイルは適切に追加される.追加で必要なのは gem.extensions の項目.ここで Mac アプリのビルド用タスクの rakefile.rb を指定する.あとは TODO の項目も修正する.
gem のビルドとインストール
ここまできたらあとは,実際に gem をビルド,インストールしてみてうまく動作するか確認するだけ.
$ rake build $ rake install $ hello_world_notification
rbenv を利用している場合は rehash が必要.gem のインストール時にちゃんと Mac アプリもビルドできていれば,作成した Mac アプリが起動する.あとはリリースするのみ.
そもそも,なんでこんなことをしたかというと,常駐するスクリプトは Ruby で書いたけど,特定の条件になったときは別途作成した Mac アプリを起動したくて,インストールは gem 経由でまとめてやりたいなぁと思ったことと,Mountain Lion になってからはダウンロードしたアプリは Developer ID での署名がないと,環境によっては単純に動作させられなかったりするけど,自分でビルドした限りではこの制限は影響を受けないようだということ.
また,RubyCocoa や MacRuby を使えばネイティブの Mac アプリが作れるけど,常駐させたときに何もしていないのに数%だけど,CPU リソースを食ってしまっているのが気になった(これは自分のコードが悪いんだろうけど)ので,このアプローチを検討してみたのでした.
あと Ruby から単純に通知センター使って通知するだけなら terminal-notifier を使えば簡単にできる.ただ,通知センターはアプリ単位で区分されるので,terminal-notifier 経由のものはすべてそこに集約されてしまうので,呼び出すアプリ側で通知の設定が変えたいとかなったときは,この方法だとうまく使えそうな気がする.