therubyracer 0.11.0 問題まとめ

Rails 3.1 以降で良く使われるようになった therubyracer ですが、最近、これを含む bundle install がやたらと時間がかかったり、あるいはそもそも失敗してしまう、という事象が多数報告されています。

解決方法

以下の三つの方法が基本的な解決方法です。

その0 (※ 2013/1/11 追記)

Gemfile にて、libv8 3.11 系を使用することを明記します*1 *2

gem 'therubyracer', '0.11.1'
gem 'libv8', '~> 3.11.8.13'
その1

Gemfile にて、 therubyracer のバージョンを 0.11.0beta8 に戻します

gem 'therubyracer', '0.11.0beta8'
gem 'libv8'
その2

Gemfile にて、 therubyracer のバージョンを 0.10.2 に戻します

gem 'therubyracer', '0.10.2'
その3

Gemfile にて、 libv8 のバージョンを 3.11.8.3 に戻します

gem 'therubyracer'
gem 'libv8', '3.11.8.3'

これらのうちどれでも解決しない場合は、もともと therubyracer がうまく動かない環境ですので、新しいバージョンが出るのを待つか、あるいは自前で v8 のエンジンを導入してください。

なお、環境ごとの therubyracer および libv8 のバイナリ提供バージョンを確認されたい場合、 https://github.com/cowboyd/libv8/issues/62 こちらの表が便利です。

何が起きているの?

therubyracer は、JavaScript のエンジンである v8 *3Ruby から使えるようにする gem です。therubyracer では、v8 エンジンがシステム上で利用できる場合はそのエンジンを、また利用できない場合には libv8 という gem を取得して、各環境ごとに利用できるバイナリの v8 エンジンをこの libv8 gem から取得するようになっていました。賢いです。

ところがこの「バイナリの v8 エンジンを利用できる」という前提が、 0.11.0 に入って崩されてしまいました。therubyracer 0.11.0 が依存している最新の libv8 3.11.8.4 は、 v8 エンジンをソースコードからコンパイルしようとします。ソースコードからコンパイルすると、環境次第ですが、2分〜15分程度かかったという報告もありますし、そもそも環境が整っていないとコンパイル自体失敗します。

therubyracer 0.10 系では何も考えずに(ものの3秒で)インストールできていたものが、どうしてこんなことに…なってしまったのでしょーか。

どうしてこうなった

therubyracer の作者 @cowboyd は以前より、とある問題に頭を悩ませていました。
「therubyracer は特定のバージョンの libv8 のバイナリを入れることを強制してしまっている…。これは多くの環境では上手くいくけれど、このバイナリが動かないような環境では therubyracer を使う方法がなくなってしまう」
この問題への対応として彼は、0.11系でバージョン依存性を弱くしようとしました。また、新しいバージョンの libv8 へと依存するよう関係を変更しました。
ところがこの新しい libv8 が作成するのに失敗しており、バイナリが壊れている状態でリリースされてしまいました*4。この結果 therubyracer はあまりに多くの環境で動作しなくなってしまいました。
そして彼は、緊急でとある方策をとりました:
 
「とりあえずソースコードからコンパイルさせればいっか!」
 
い い わ け な い
 
先ほども言いましたが、v8のコンパイルは、要はひとつの言語処理系のコンパイルなのでそれなりに時間がかかります。Rails3.1 初期状態の bundle install はおおよそ1分くらいですが、libv8のコンパイルにはそれこそ10分とるわけなので、この影響は甚大でした。

@cowboyd にとっては、バージョン依存による環境縛りを外せるという意味で、若干いけてないけれどもまあ意味のあるワークアラウンドを出したなーと、この時点では思っていたように思います。
しかし、多くの利用者が欲しているのは「何も考えなくても入っていた therubyracer という gem」でした。
そのことを指摘した発言が出て、 @cowboyd はそのことの価値を明確に意識するようになります。

I'm very curious how 0.10.x versions of therubyracer managed to "just work" on nearly everyone's system (I never encountered or heard of anyone encountering a problem)(中略), and what changed in 0.11.x to make this no longer work. I am very familiar with "unix dependency hell"(中略) -- but therubyracer prior to 0.11.0 managed to avoid this and be an awesomely great install experience which just worked for everyone.

issues/215 @jrochkind

You make a valid point, but since this is the first positive feedback about the old system I have received , I guess I didn't really realize its value up until now. (and thank you by the way)

issues/215 @cowboyd

誰からのお礼やフィードバックがなかったがゆえ、彼は、自分の作った therubyracer が「簡単にインストールできる」ということがいかに大きな価値を持っていたか十分に認識していなかったのですね。応援とフィードバックは大事ですね。だれか私も応援してください。わぁい。

therubyracer の問題

以上の問題をまとめると、次の三つの要素が絡み合っていることになります。

  • gem 'therubyracer' としたら何も考えなくてもインストール成功してほしい
  • libv8 はバイナリで入ってほしい
  • バイナリバージョンが上手くいかない人のために、v8 エンジンを選択的に利用できてほしい
gem 'therubyracer' としたら何も考えなくてもインストール成功してほしい

今回はバージョンアップに伴って therubyracer の依存性が壊れたわけですが、ユーザは以前のバージョンを指定することで回避は可能でした。ただ、ライブラリ提供者としては、そういった回避手段をユーザ誰もに期待することはできません。特に therubyracer は、Rails 3.1 以降では標準的に使用され、多くの初心者が利用するライブラリですからなおさらです。
と言うわけで、gem 'therubyracer' その一行だけで全てが問題なく動くということは、とても大きな価値なのでした。

libv8 はバイナリで入ってほしい

これは言わずもがなですね。バイナリバージョンが入ってくれないと、数分〜十分以上の待ち時間が出てきます。しかも、このインストールはデプロイのたびに発生します。これはちょっとした恐怖ですよ。バイナリバージョンの libv8 は therubyracer では今や必須です。

バイナリバージョンが上手くいかない人のために、v8 エンジンを選択的に利用できてほしい

@cowboyd が大胆な変更をして実現しようとしていたのがこちらの機能です。似たようなケースとして、セキュリティリスクに敏感なため、その libv8 のバージョンは利用できない、という人がいるということもあるようです。ともあれ、ここで達成したい目的は、libv8 のバージョン指定をより自由にしたい、と言うことです。そしてそれは、一つ目の「gem 'therubyracer' が成功してほしい」という願いとわりと衝突しやすいものです。

この三つの問題をどうまとめて解決策を出すか、それを考えるのに @cowboyd は二週間悩みました。そして12/18にとうとう、今後の解決策を提案したのです。

解決策、その後

@cowboyd は、次の解決策を示しました。

  • libv8 と therubyracer とが互いに依存するようにする
  • libv8 は v8 への proxy としてふるまう
  • ソースバージョンとバイナリバージョン、両方をリリースする

前半二つは実装の話なので省略、最後の一つが面白いのでご紹介します。
今後の libv8 のリリースでは、「最新バージョンは常に binary 入り、そのひとつ前に source 版の libv8 を準備する」というリリース方針になるようです。

3.11.8.4 -> source
3.11.8.5 -> binary

こんな感じです。バイナリ版の libv8 がうまく動かないようだったら、バージョンを一つ戻してソース版をインストールすればいい、そういう設計になる予定のようです。
この変更はそれほど時間がかからない予定だそうですが、本家のコミットに動きが一切ないのでちょっと心配です。

ともあれ、今後も therubyracer は「インストールは簡単で、サクサク動くよ便利!」という方向は持ち続けたままメンテナンスされていくようなので、一安心です。私は次のリリースが来るまでは素直に therubyracer 0.10.2 を使い続けることにします。

ひとこと

問題は、 gem update とか bundle install で最新バージョンを無理やり取ってきてしまうとか、バージョン番号が四ケタになっているやつは '~>' でのバージョン指定でマイナーバージョンが上がってしまうとか、そういうところな気もしますけど、かといって私には、バージョン全指定以外に確実な方法なんて思い浮かばないので、この rubygems 式指定法とどう付き合っていくかなんでしょうね。

まとめ

がんばれ。
 
あとソフトウェア製作者への応援大事。
 

追記

あ、応援ブクマありがとうございます、普段がいかにもオメガブロガーなもので初の10ブクマ越えになりました。
わぁいブクマ suu-g ブクマ大好き

追記2 (2013/09/08)

therubyracer 0.12.0 でも同じ問題があるとのブクマコメを頂きました。
0.12.0 では、therubyracer は libv8 3.16.14 系に依存するように変更が加えられました。いまの最新の 3.16.14.3 は x86_64-darwin-12 および x86_64-linux に対するバイナリパッケージが提供されていますので、これらの環境では libv8 のビルドは行われずに済みそうです。言い方を変えると、それ以外の環境、たとえば mingwdarwin-10、32bit Linux といった環境では、バイナリパッケージが入らずに困る可能性があります。

*1:これを書かないと、未対応なのに投入された 3.15 系が入るかもしれません

*2:以前に何度か bundle install を試している場合は一度 bundle clean を行った方がよいです

*3:http://code.google.com/p/v8/ V8 JavaScript Engine

*4:issues/215 を読む限り。多分