当初のmrubyはMakefileによるビルドシステムだったのですが、「Rubyは最高のDSL」の信条を持つ増井さんによりRakeを用いたビルドシステムに書き直されました。また、mrbgemsの仕組みも導入されています。今回はここら辺について読んでいきたいと思います。
まず、Rakefileをざーっと眺めます。すると、MRubyというオブジェクトがビルドの核となっているように見えます。
Rakefile本体にはMRubyの定義はないのでloadされているmruby_build.rakeを眺めてみます。すると、MRubyモジュールとその内側にいくつかクラスが定義されていることがわかります。
Rakefileに戻って、Rakefile中ではMRubyオブジェクト*1はtargets、およびeach_targetが呼び出されています。とすると次の関心はtarget達がいつ設定されるかです。というわけで次にコンフィグファイルであるbuild_config.rbを見てみましょう。
build_config.rbではMRuby::Buildオブジェクトがnewされています。newにはブロックを受け取り、ビルド設定が入っていると思われるconfを引数に渡されたブロックを呼び出すようです。デフォルトのbuild_config.rbではmrbgemの組み込みが記述されています。
ではMRuby::Build#initializeを見てみましょう。デフォルトのビルド設定を生成し、MRuby.targetsに設定しています。このことからtargetというのは生成するバイナリのアーキテクチャであることがわかります。
build_config.rbではgemメソッドを使って組み込むmrbgemを指定しています。ちなみに、mrbgemは通常のRubyのGemとは異なり、実行時に読み込まれるのではなくコンパイル時に組み込まれます。組み込むmrbgemですがローカルに置いてあるもののほか、git経由で取得するということも可能なようです。
さて、gemメソッドがどこに書かれているかですが、mruby_build_gem.rake中のMRuby::LoadGemsモジュールに書かれており、MRuby::BuildはLoadGemsをincludeすることでMRuby::Buildオブジェクトでgemメソッドが使えるようになっています。gemメソッドを実行することによりMRuby::Gem::Specificationオブジェクトが生成され、MRuby::Buildオブジェクトのgems配列に追加されています。MRuby::Gem::Specificationクラスの定義はtasks/mrbgem_spec.rakeに書かれています。
Rakefileに戻ってファイルの後ろの方を眺めると依存関係を設定しています。Rakefile本体に書かれているのはlibmrubyとbins(mruby, mrbc, mirb)のみでそれ以外は「# load custom rules」と書かれている下でloadされてる個々のrakeファイルに書かれています。
他でも書きましたが、mrubyのビルドは二段構成になっており、
という流れになっています。
という流れだったのですが、mrbgemsの導入により、ステップ3と4の間にmrbgemsをコンパイルするという処理が追加されました。
余談ですがCRubyでは標準ライブラリであるTimeとかMathとかもmrbgemになっておりビルド設定により外すことができます。mrbgemsが導入される前はCプリプロセッサにより組み込むかを制御していました。
さて、mrbgemsをコンパイルするにあたって見るべきファイルはtasks/mrbgems.rakeとtasks/mrbgem_spec.rakeです。
tasks/mrbgems.rakeでは先ほどgemメソッドで組み込むように設定した個々のMRuby::Gem::Specificationオブジェクトのsetupメソッドが呼び出されます。setupメソッドでは各mrbgemのsrc/*.c*2をlibmrubyのアーカイブ対象に追加しています。また、mrblib/*.rb(Rubyで書かれたmrbgemコード)があるかの確認をしています。
最後にgem_init.cを生成しています。gem_init.cではGENERATED_TMP_mrb_<mrbgem名>_gem_init(mrb_state *mrb)という名前で以下の2つの処理を行っています。
mrb_<mrbgem名>_gem_init(mrb_state *mrb)はmrbgem作者が作成する関数で、mruby APIを使ってクラスを定義したりメソッドを定義したりを行います。
個々のmrbgemのビルド設定ができたので、まとめ役のmrbgems.rakeに移ります。mrbgems.rakeでは先ほど生成した個々のGENERATED_TMP_mrb_<mrbgem名>_gem_init(mrb_state *mrb)を呼び出すmrb_init_mrbgems(mrb_state *mrb)が含まれたgem_init.cを生成しています。mrb_init_mrbgems(mrb_state *mrb)はmrbgemsが有効になっていればmrb_open()時に呼び出されます。これでmrbgemsによりmrubyの機能拡張をする仕組みがつながりました。
去年(2012年)の春にmrubyを読んだときに比べ、mrbgemsの導入によりコアが本当にコアだけになったと感じます。また、ビルドシステムのRake化はmrgemsとの親和性が高く感じます。確かmrbgemsが導入されたのはビルドシステムのRake化より前だった気がするのだけど、当時はどうやってたのだろう・・・