mrubyを読む

はじめに

当初のmrubyはMakefileによるビルドシステムだったのですが、「Rubyは最高のDSL」の信条を持つ増井さんによりRakeを用いたビルドシステムに書き直されました。また、mrbgemsの仕組みも導入されています。今回はここら辺について読んでいきたいと思います。

ちなみに、mrubyにはオリジナルのRakeを元にしたminirakeが同梱されています。増井さんによると、

matzからのリクエストは、RubyGemsは使わず素のCRuby 1.8, 1.9で動く事でした。
なので、rakeの古いバージョンを元にminirakeを作ってます。

ということだそうです。

Rakefile

まず、Rakefileをざーっと眺めます。すると、MRubyというオブジェクトがビルドの核となっているように見えます。

tasks/mruby_build.rake

Rakefile本体にはMRubyの定義はないのでloadされているmruby_build.rakeを眺めてみます。すると、MRubyモジュールとその内側にいくつかクラスが定義されていることがわかります。

build_config.rb

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(tasks/mruby_build.rake)

ではMRuby::Build#initializeを見てみましょう。デフォルトのビルド設定を生成し、MRuby.targetsに設定しています。このことからtargetというのは生成するバイナリのアーキテクチャであることがわかります。

MRuby::LoadGems#gem(tasks/mruby_build_gem.rake)

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のビルドは二段構成になっており、

  1. まず、コアライブラリの構築(src/*.cをコンパイルしてlibmruby_coreを作る)
  2. コアライブラリを使ってmrbcをビルド
  3. mrbcを使ってRubyで書かれたライブラリ(mrblibにあります)をバイトコード(irep)にコンパイル
  4. コアライブラリとバイトコードをlibmrubyにまとめる

という流れになっています。

という流れだったのですが、mrbgemsの導入により、ステップ3と4の間にmrbgemsをコンパイルするという処理が追加されました。

mrbgemsのコンパイル

余談ですがCRubyでは標準ライブラリであるTimeとかMathとかもmrbgemになっておりビルド設定により外すことができます。mrbgemsが導入される前はCプリプロセッサにより組み込むかを制御していました。

さて、mrbgemsをコンパイルするにあたって見るべきファイルはtasks/mrbgems.rakeとtasks/mrbgem_spec.rakeです。

MRuby::Gem::Specification#setup(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つの処理を行っています。

  • Cで書かれたmrbgemコードがある場合はmrb_<mrbgem名>_gem_init(mrb_state *mrb)を呼び出す
  • Rubyで書かれたmrbgemコードがある場合はバイトコードにコンパイルした上でバイトコードをloadする

mrb_<mrbgem名>_gem_init(mrb_state *mrb)はmrbgem作者が作成する関数で、mruby APIを使ってクラスを定義したりメソッドを定義したりを行います。

tasks/mrbgems.rake

個々の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化より前だった気がするのだけど、当時はどうやってたのだろう・・・


*1 モジュールはオブジェクトですし、オブジェクト的に使っているので以降はMRubyオブジェクトと書きます
*2 他にもcpp、m、asm、Sも検索対象に入っています

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-04-06 (土) 14:42:41 (2444d)