MacRuby 0.5 で AOT を試す その2

先日は MacRuby 0.5 をビルドして、AOT(ネイテイブバイナリへのコンパイル)を試してみました。簡単なサンプルはうまくいったけど NSSound を使うスクリプトコンパイルしてみたら思ったように動作しなかったのでした。

まず、メーリングリストのこちらの投稿が参考になりました。

http://lists.macosforge.org/pipermail/macruby-devel/2009-July/002062.html

この中で明示はされていませんけど、いろいろ試したところ require を使うとそこで LoadError 例外が発生してしまっていたようです。その原因はロードパス($: または $LOAD_PATH)が空っぽになっていたためでした。何も表示せずに終了してしまっていたのは、捕捉されない例外がトップレベルまで遡ってしまった時に通常の rubymacruby での実行であれば例外のメッセージとバックトレースが出力されますが、それが出力されない為ではないかと思われます。

そこでちょっと力技ですが、以下のようにして macruby の本来のロードパスを取り出してスクリプトの先頭で $: に全部追加してしまってから require するようにしてみました。

まず macruby 自身にロードパスを尋ねます。長いので結果は削ってます。

$ macruby -e "p $:"
["/Library/Frameworks/MacRuby.framework/Versions/0.5/usr/lib/ruby/site_ruby/1.9.0", ...]

そしてその結果を nssound_sample.rb の先頭に貼り付けて、以下のようにしてロードパスに追加します。こちらも省略していますので実際はもっとたくさんあります。ここは上の結果で置き換えてください。また例外が表示されないので全体を全ての例外を捕捉して表示するように rescue 節を追加してあります*1

#!/usr/local/bin/macruby

begin
  ["/Library/Frameworks/MacRuby.framework/Versions/0.5/usr/lib/ruby/site_ruby/1.9.0", ...(略)...].each do |path|
    $:.push path
  end

  framework "cocoa"
  require "hotcocoa"

  audiofile = ARGV.shift
  sound = HotCocoa.sound file:audiofile

  sound.play
  sleep sound.duration
rescue Exception
  p $!
  puts $@
end

これを macrubyc でコンパイルして mp3 ファイルを渡すと、再生できました!!

$ macrubyc -o nssound_sample nssound_sample.rb
$ ./nssound_sample test.mp3

さらに動作を確かめるために、いくつか実験してみたのではりつけます。

(a.rb)

class A
  def test
    p :test
  end
end

(b.rb)

a = A.new
a.test
$ macrubyc -o test a.rb. b.rb
$ ./test
:test

一緒にコンパイル&リンクしてしまえば、require しなくても a.rb で定義したクラスは利用できます。ただし macrubyc の引数に渡す順番で実行されるので、この順番を逆にすると b.rb の内容を実行する時点で A が未定義なのでエラーになります。

require を使ってみます。a.rb の内容は上と一緒です。
(b.rb)

$LOAD_PATH.push "."
require "a"
a = A.new
a.test
$ macrubyc -o test b.rb
$ ./test
:test

macrubyc に渡している引数に注目してください。b.rb だけをコンパイルしているけどちゃんと動いています。a.rb は事前にコンパイルされていませんが、実行時に require することで動的に読み込まれるので同じように動くのだと思います。パフォーマンスには違いがあるだろうと考えられます。
ちなみに a.rb も一緒にリンクして、なおかつ require "a" も行うと a.rb の内容が2回実行されています。a.rb にメッセージ出力する文を追加してやると確認できます。

まとめるとこんなことが分かってきました。なお使用したのは trunk の revision:2690 (9/30)のソースからビルドしたものです。MacRuby の開発は活発で日々更新が加えられているのですぐに事情が変化するかもしれません。

  1. 現状はコンパイルした結果は $:/$LOAD_PATH が空になっているので追加してあげないと require が LoadError で失敗する
  2. 例外がトップレベルまで遡った時のエラーメッセージが出力されない
  3. 複数のファイルをリンクする時には macrubyc に渡す順番に気をつける
  4. Ruby スクリプトは事前にコンパイル&リンクしてもいいし、動的に require してもいいらしい
  5. 例外処理に不完全なところがある??(脚注1の点)

1. はインタプリタの初期化の処理をリンク時に追加するのを忘れてるだけだと思うのできっと直るでしょう。2. はもしかしたら何か理由があるかも。

ここまで触ってみて、今 ruby スクリプトで書いてあるものをすぐさまコンパイルして動かそうと思うと不安もありますが、これから macrubyc での compile 向けに ruby で書く、つまり ruby の機能でメタプログラミングを駆使してみようとしたりしないのならば、意外と使いものになりそうだなという印象です。もっとおもちゃレベルのものなんじゃないかと思っていました。まだソケット等の拡張ライブラリ部分や Thread の動作など確認したいところはたくさんあるので、あくまでざっくりした印象ですけど。

それにしても、個人的には MacRuby がとてもアツいです。今のうちにみなさんも MacRuby 生活を始めてみてはいかがですか。

*1:ただし、 $: に再代入すると NameError が発生するはずなのですが、作業中そのミスをしても捕捉できませんでした。例外処理がまだ不完全なのかもしれません