Debian Ruby1.9会議の詳細のお知らせ

公開日時 akira Sat, 20 Jun 2009 10:19:00 GMT

先日お知らせしたDebian Ruby1.9会議の開催日時と場所が以下に決まりました。

参加される方へのお願い

参加される方との連絡・情報交換のためにGoogleグループに場を設けています。ATNDで参加登録された方は、私(akira.yamada gmail.com)まで以下の二点をお知らせください。

  • 登録するメールアドレス
  • ATNDでのユーザ名

Debian Ruby1.9会議のお知らせ 2

公開日時 akira Thu, 04 Jun 2009 13:20:00 GMT

きたる2009-07-05(日)にDebian Ruby1.9会議を開催することになりました。Debianにおける主要なRubyパッケージ開発者のうちの実に二名が参加することが決定しています…… とか言っても、お気付きの通り、そのうちの一人は私です。

今回の開催はRubyパッケージやRubyGemsのパッケージの開発者であるdaigoさんから声をかけていただいたのがきっかけです。現在、構成変更などの議論を進めている関係で、Debian/sidにおけるRuby 1.9のパッケージのリリースが遅れています。その状況を改めて確認し、解決しなければならないことや決定しなければならいことを話し合い、物事を先に進めていこうというのが主旨です。

かなり奥まったところにある話題なので、興味を持つ人は限られるかもしれませんが、話を聞いてみたい、意見がある、パッケージ開発者を見てみたい、といった方がおられましたらご参加ください。Debian Ruby1.9会議の参加登録はATNDで受け付けています。

開催地は川崎〜横浜のどこかになることが決定しています。ただし具体的な会場などは未定で、二人以外の参加者がどれくらいあるかに応じて会場を確保したいと考えています。

私個人としては、参加者がある程度集まって、時間に余裕があるようなら、おまけ的にRuby on Debian談義をするのも悪くないかなと思っていますが、これは単に思い付きレベルの話で未定です。(それにしても奥まった話題のような気もしますが。)

とちぎRuby会議01に行ってきた 3

公開日時 akira Sat, 21 Feb 2009 13:54:00 GMT

迷った迷った。直前まで行こうかどうしようか迷った。が、行ってみて良かった。

この手の集まりに行くのはほんとうに久しぶり。久しぶりの参加が新幹線に乗っていくことになるとは我ながら驚きなんだけれども、ともかくいつもよりも早起きをして、寝過ごさないことだけにひたすら集中して移動した。車中では迷いの元であった原稿をパチパチとはじいたり。

とちぎRuby会議は二部構成となっていた。一部は原さんとごとけんさんによる招待講演とLightning Talksが行われ、二部は定期的に開催されている勉強会toRubyの特別版が行われた。

原さんは「博士の愛したRubyと数学」で、いかにRubyを身体の一部として使ってきたか、使っていくか、そしてその経験から未来のRubyの姿を予想するという内容だった。「未来にはもしかするとグローバル変数がなくなるかもしれない。その分クールになった。『むかしは良かった、グローバル変数なんてのがあってね』『グローバル変数を使うときにはちょっとドキドキしたもんだ』なんてことから『復刻版Ruby』が出るかもしれない!」なんて話も。

加えて、(脱線して?)ペンへの愛着についての話題がも盛り込まれていたのだが、その中であった「計算をするとき、紙との間にあるのはペン。ムキーとなってきたとき、それが良いものであることによって、投げ出すまでにあと10秒をもたせることができる。その10秒で先に進める。だから手に気持ち良いものが良い」というのが印象に残った(だいたいこのようなことだったと思う)。講演内容としては「Rubyもそのようなものに」と続くのだが、それはそれとして、ペン以外に紙やキーボードへの愛着もあるのかななんてことを思った。後で尋ねてみるのを忘れてた。

ごとけんさんは「20世紀Ruby」として、なつかしのPerl/Ruby Conferenceから現在までのごとけんさん自身のRubyとの関わり方をベースにした、Rubyをどう楽しむかというお話だった。思いがけずなつかしい名前が出てきてうんうんとうなずきながら聞いていた。WEBrickもさることながら、leakproxy.rbにはずいぶんとお世話になったなあ、なんて。

toRuby特別版ではdRubyを使ってみなでつなぎあってみるというのが行われた。無線LANのトラブルがあったりもしながら、あらかじめ用意されたサーバに接続するだけでなく、各人のPCに対して接続しあうなどできていたようだ。MS-Windowsだとうまく動かなかったり、1.9.1だと動作がおかしいかもしれないという話が出たりと、必ずしもすんなりとはいかないようだったが、終わるころにはみなそれなりに動かすことができたようだったし、時間中はなごやかな雰囲気であったこともあり、参加者はみな楽しめたようだ。やはりつながって動くものを目の前にすると楽しい。

私はというと、OS X上のmacportsで1.8.7と1.9.1を用意しておいたのだが、自ホストのアドレスを逆引きできないせいか(追求してない)DRb.start_serviceで例外が上がるなどして少し手間取った。自アドレスを明示することで例外を避けられたし、1.9.1では例外が上がらなかった。

その後、会場の片付けを少し手伝って、懇親会の会場へ移動。電車の時間を早めにしてしまったので途中あわてて抜け出す感じになってしまったが、直接にはお話ししたことのない人々といろいろとお話できて楽しかった。駅に着いてみれば時間に余裕があったので、せめてちゃんと挨拶をしてくればよかったと待ち合い室のベンチで思った。

勉強会というのに興味があったので、実際に参加してみようと年初に考えたのがきっかけの一つで、こうして実際に見られたのは、少し無理してでもいったかいがあった。運営の雰囲気くらいはつかめたかな。別の勉強会にも参加してみようと思う。その上で、何かネタがあれば近くで勉強会みたいなものを考えるのにも実は興味がある。横浜〜関内駅あたりで企画したらのってくれる人はいるかしら。

toRubyの最後にKEEP-PROBLEM-TRYがあったのでなぞらえてみると、KEEP: こうしたイベントや勉強会への参加を続ける、PROBLEM: せっかくなので行きたかったカフェが営業してなかった(確認不足)、TRY: しゃべる側にまわれるようネタ出し、というところかな。

少しだけど写真を撮ってきたのでよければどうぞ: とちぎRuby会議01

Capistranoであえて非並列にコマンド実行する

公開日時 akira Thu, 27 Nov 2008 12:01:00 GMT

システム管理系でCapistranoを使おうとする、なんでもかんでも同時並列実行されると困るというようなことがなくもない。ちょっと考えてみたんだけど、どんなものだろうか。もう少しなんとかなりそうな気もする。

def serialize_for_hosts
  task = current_task

  hosts = find_servers_for_task(task)
  if task.options.include?(:hosts)
    save_hosts = task.options[:hosts]
  end
  begin
    hosts.each do |host|
      task.options[:hosts] = [host]
      yield
    end
  ensure
    task.options[:hosts] = save_hosts if defined?(save_hosts)
  end
end

task :bar do
  run "echo bar"
end

task :foo do
  run "echo foo start"
  serialize_for_hosts do
    run "sleep 2"
    run "echo date"
    hogehoge
  end
  run "echo foo end"
end

某メモより。

gonzuiをrackupしてみた

公開日時 akira Thu, 18 Sep 2008 15:00:00 GMT

気になりながらもちゃんと中までは見ていなかったRackを触ってみることにした。「Hello, World!」的なコードはすでにあるので何かをRackに載せてみようと思い、最近身のまわりでちょっとしたブームであるgonzuiを選んだ。Rackのバージョンは0.4.0。

gonzuiに含まれるwebインタフェースであるgonzui-serverはWEBrickを使って書かれている。検索エンジンとwebインタフェースの間には中間層のようなものはないようなので、効率は悪くなりそうだがwrapperを入れることにした。最悪のケースだと[gonzui-engine]<==>[gonzui-server]<--(webrick api)=(rack spec)-->[rack]=[webrick handler]<==>[webrick]のようになる。その代わりに足まわりを変えられる利点が得られる。

とまどったところはいくつかある。

一つはURLMapの使い方で、コードを書き足しながらいろいろ試していたため、ある時点でこんな状態になった。

map "/foo" do
  run App_foo.new
end
run App.new

これはrackup用のスクリプトで、最初はrun App.newだったところにmapを足したもの。意図は/fooへのアクセスをApp_fooで処理させるコードを足したつもりだった。結果は以下のようになる。

$ rackup test.ru
.../gems/rack-0.4.0/lib/rack/builder.rb:49:in `to_app': undefined method `call' for {"/foo"=>#}:Hash (NoMethodError)

mapはアプリケーションのリストの最後にハッシュを入れて、そこでURLと振り先のアプリケーションとの対応付けを作る。一方runはアプリケーションのリストの最後に入れる。rackupはこのリストに対してto_appを送り、内部のオブジェクト構造を作る。その際、リストの最後にあるハッシュについてだけは特別な扱いをしていて、URLMapオブジェクト(アプリケーションと呼ぶべき?)に変換する。しかし中間にあるハッシュについてはケアされないため上のような例外が発生する。正しくはAppに対してもmapを使う。

map "/foo" do
  run App_foo.new
end
map "/" do
  run App.new
end

もう一つのとまどいはRack::CommonLoggerの動作で、ちょうど上のコードを与えたときにログに記述録されるアクセス先のURLが空になってしまうことがある。

$ rackup test.ru
[2008-09-19 11:36:25] INFO  WEBrick 1.3.1   
[2008-09-19 11:36:25] INFO  ruby 1.8.7 (2008-08-11) [i486-linux]
[2008-09-19 11:36:30] INFO  WEBrick::HTTPServer#start: pid=25843 port=9292
127.0.0.1 - - [19/Sep/2008 11:36:33] "GET / HTTP/1.1" 200 3 0.0007
127.0.0.1 - - [19/Sep/2008 11:36:35] "GET  HTTP/1.1" 200 7 0.0007

具体的には/以外のマップへのアクセスの際のログについてベース部分、つまりmapの引数に指定した部分がログの上では消えてしまう。アプリケーションはきちんと動くのですごく困るわけではなかったのだけど、Rackのコードを見るとこの部分(PATH_INFOの内容が出力されている)を意図して削っているため悩んでしまった。結果からいうとRack::Recursiveを使えばこの問題を回避できるようだ。Rack::Recursiveは内部回送をする際に使うものなので、今回は必要ないと思うのだが、このようなわけで入れておくことにした。

ところで、このPATH_INFOを削っているのが、実は、よくできているところの一つだと後で気付いた。というのは上のコードをCGIスクリプトとして実行した場合などでもCGIのPATH_INFOを見て調整してくれるようで、たとえば/cgi-bin/test.ru/fooにアクセスするとちゃんとApp_fooが応答する。

ちょっと困ったのはgonzui-serverが生成するリンクが絶対パスになっていること。gonzuircの:base_mount_pointの値(デフォルトは/)を使って生成されている。Rackにgonzuiを載せるときにもこの値を参照しておいたほうが良さそうなのだけど、WEBrickやMongrelとCGIやFastCGIで、このあたりの関係がややこしいことになる。

たとえば:base_mount_point/codesearchとしたとする。導かれる絶対パスは/codesearchとなり、下位のサーブレットへのパスは/codesearch/statなどとなる。このときWEBrickなどでは絶対パスでURLMapを作っておけば特に問題はない。CGIなどでは一般に/cgi-bin/gonzui.cgiがアプリケーションのトップになり、下位サーブレットへは/cgi-bin/gonzui.cgi/statなどでアクセスさせることになる。この場合、HTTPサーバのエイリアス機能によって/cgi-bin/gonzui.cgi/codesearchに対応付けておかなければならないものの、ひとまずは問題ない。

ここでpublic_htmlに置くことを考える。こういう状況では前述のようなエイリアスを設定するのは難しいことが少なくない。この場合、:base_mount_pointを設定しないでおけばよさそうに思えるのだが、デフォルト値が/であるため/~foo/gonzui.cgiによって生成されるリンクがたとえば/statなどとなってしまう。では:base_mount_point/~foo/gonzui.cgiとするとどうか、というと、今度はURLMap側で問題が起きる。前述のように絶対パスでURLMapを作っている場合、この指定は/~foo/gonzui.cgi/~foo/gonzui.cgiがトップであることになってしまう。となるとURLMapを作るときに:base_mount_pointを参照しなければ良さそうなのだが、そうすると今度はWEBrickやMongrelでしぶいことになる。

そんなようなわけで、しょうがないのでadhocに:base_mount_pointを変更して動作するようなwrapperにした。

最後に、CGIやFastCGIでのRackの運用方法がもう一つよくわからなかった。FastCGIについては外部プロセスとして起動してしまうということはもちろん考えられるのだが、動的運用ではどうすると良いのか。rackupを実行するシェルスクリプトを置いておけば動くことは動く。でもなんとなく釈然としない。検索などしてもうまくヒットさせられなかったので、ひとまずはgist:28033のような構成とした。

gonzui-rack.rbの中ではRack::Builderを使ってmapなどしている。Rack::Builderはrackupの中でも使われているが、これは(他のRackミドルウェアと同様に)ネストできるので特に問題ないようだ。

ちなみにabでちょっとだけ比較してみたところ、こんな感じだった。まあ、あまり意味はないかも。

 type          conc  Reqs per Sec  Time per Req  Time per Req (all)
 ------------  ----  ------------  ------------  ------------------
 WEBrick         1       52.71       18.972[ms]      18.972[ms]
 WEBrick         3       46.95       63.902[ms]      21.301[ms]
 Rack/WEBrick    1       54.13       18.475[ms]      18.475[ms]
 Rack/WEBrick    3       45.85       65.425[ms]      21.808[ms]
 Rack/CGI        1        3.43      291.723[ms]     291.723[ms]
 Rack/CGI        3        6.37      470.852[ms]     156.951[ms]
 Rack/FCGI       1       50.93       19.635[ms]      19.635[ms]
 Rack/FCGI       3       95.80       31.316[ms]      10.439[ms]

URI::Parser

公開日時 akira Tue, 29 Jul 2008 15:00:00 GMT

URIモジュールをいじくって、RFC的にはダメなんだけどちょっとだけ大目に見てよ、というようなことが出来るようにしてみた。というのは、その種のリクエストを何度か受け取ったことがあるため。リクエストというよりも、バグ報告として受け取ることが多かったと思うけど。

>> p = URI::Parser.new(:ESCAPED=>"(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
=> #<URI::Parser:0xb7872640>
>> u = p.parse("http://foo.bar.baz/%uABCD")
=> #<URI::HTTP:0xb78cf4f8 URL:http://foo.bar.baz/%uABCD>
>> URI.parse(u.to_s)
URI::InvalidURIError: bad URI(is not URI?): http://foo.bar.baz/%uABCD
	from /.../lib/uri/common.rb:126:in `split'
	from /.../lib/uri/common.rb:144:in `parse'
	from /.../lib/uri/common.rb:592:in `parse'
	from (irb):3
	from /usr/bin/irb1.9:12:in `<main>'
>> s = "http://foo.bar.baz/ABCD"
=> "http://foo.bar.baz/ABCD"
>> u1 = p.parse(s)
=> #<URI::HTTP:0xb78c3220 URL:http://foo.bar.baz/ABCD>
>> u2 = URI.parse(s)
=> #<URI::HTTP:0xb78b6d54 URL:http://foo.bar.baz/ABCD>
>> u1 == u2
=> true
>> u1.eql?(u2)
=> false

あとでruby-devに投げてみよう。

Ruby/GD2でGIFアニメ

公開日時 akira Sat, 26 Jul 2008 15:00:00 GMT

用事があってGIFのフォーマットだとかGIFアニメのことだとかを調べている。特許の話とかでてきて懐かしい。

GIFアニメについてはlibgd2の2.0.29で対応したようだ。が、言語バインディングではまだあまり対応していない様子。まだ、というか、libgd2の言語バインディングはあまりアクティブでないような感じもある。Ruby/GDもそんななかの一つ。今からコードを足すのもどうかなというところ。

そう思っていたらRuby/GD2という別の実装があることに気付いた。こちらもアクティブではないようだが、Ruby/DLで書かれているためちょっとだけ手を入れやすい。そういうわけで、関数をいくつか足し、Rubyレベルでのメソッドを三つほど足してGIFアニメを出力できるようにしてみた(差分)。

require 'gd2'
 
files = Dir.glob('/path/to/images/*.png')
 
prev = nil
open('/path/to/output.gif', 'w') do |io|
  img = nil
  files.each do |f|
    img = GD2::Image.import(f, :format => :png)
    img.gifanim_begin(io) if prev.nil?
    img.gifanim_add(io, 50, prev, true)
    prev = img
  end
  img.gifanim_end(io)
end

使っているlibgd2は2.0.36-RC1なのだけど、このバージョンのGIFアニメ機能にはバグがあるようだ。libgd2では、GIFアニメを構成するイメージの間での変更部分を抽出しようとするのだが、サイズが異なるイメージが与えられると、その処理の中でSEGVを起こす。ソース中のコメントには内部で調整するというようなことになっているのに、実際にはそういうことをやっているところがない。そのため、前述したメソッドでは前フレームのサイズに変換してしまうことにした。ま、そろえておけよっていうことではあるのだろうと思うのだけど。

追記(2008-10-04): 渡辺哲也さんの日記を見てパッチを置いていなかったことに(今になって)気付いた。それとも消してしまったんだったかな。

Rails 2.0.2とRuby 1.8.7のString#respond_to?

公開日時 akira Wed, 18 Jun 2008 15:00:00 GMT

に対してというのは、Ruby 1.8.7以前からMarshalが持っていたバグをRuby 1.8.7で直したところ、delegate.rbにも同様のバグがあったのが顕在化した、という話なのだったと思う。で、それを直しましたというのがであり、1.8.7-p17。

ところが(なのかな?)Rails 2.0.2にも同じバグがあって、これも顕在化した。よって、次のようになるのは1.8.7を1.8.7-p17にしても変わりない。

$ ./script/console
Loading development environment (Rails 2.0.2)
>> "" + ActiveSupport::Multibyte::Chars.new("")
ArgumentError: wrong number of arguments (2 for 1)
	from (irb):1:in `respond_to?'
	from (irb):1:in `+'
	from (irb):1
>>

つまりこれはActiveSupport::Multibyte::Charsで定義されているrespond_to?が引数を一つしか取らないからで、これを二つ取るようにすればよい。というわけで、config/initializers/fix_multibyte_chars_respond_to.rbというようなファイルを以下の内容で作ることで回避できるような気がする。

if defined?(ActiveSupport) &&
    defined?(ActiveSupport::Multibyte) &&
    defined?(ActiveSupport::Multibyte::Chars)
  mc = ActiveSupport::Multibyte::Chars.new("")
  begin
    "" + mc
  rescue ArgumentError
    raise unless mc.method(:respond_to?).arity == 1 
    class ActiveSupport::Multibyte::Chars
      def respond_to?(method, ip = false) 
        super || @string.respond_to?(method, ip) || handler.respond_to?(method, ip) ||
          (method.to_s =~ /(.*)!/ && handler.respond_to?($1, ip)) || false
      end
    end
  end
end

Ruby Enterprise Edition 1.8.6-20080507をbuildしてみた

公開日時 akira Wed, 11 Jun 2008 15:00:00 GMT

Ruby Enterprise Editionをbuildしてみた。

tarballをとってきて、展開して、installスクリプトを実行すれば良い、はずなのだが、手元の環境ではうまくいかなかった。どうやら(環境によって)google-perftools-0.97に含まれるlibtcmallocを使おうとして、先にインストールし、それを参照させるべくccにインクルードパスやライブラリパスを伝えようとしているようだ。ライブラリパスについてはRuby本体のbuildのときに-Lなどを使って指定しているのだが、インクルードパスにいついては環境変数C_INCLUDE_PATHやCPLUS_INCLUDE_PATHに値を設定することで指定しようとしている。で、このC_INCLUDE_PATHなどへの設定内容がどうもまずい。実環境に合っていないと必要なヘッダファイルなどを見付けられないことになる。

/bin/sh ./libtool --tag=CXX --mode=compile g++ -DHAVE_CONFIG_H -I. -I. \
-I./src  -I./src  -pthread -DNDEBUG -Wall -Wwrite-strings \
-Woverloaded-virtual -Wno-sign-compare   -g -O2 -c -o \
libtcmalloc_minimal_la-system-alloc.lo `test -f 'src/system-alloc.cc' || \
echo './'`src/system-alloc.cc g++ -DHAVE_CONFIG_H -I. -I. -I./src -I./src \
-pthread -DNDEBUG -Wall -Wwrite-strings -Woverloaded-virtual \
-Wno-sign-compare -g -O2 -c src/system-alloc.cc  -fPIC -DPIC -o \
.libs/libtcmalloc_minimal_la-system-alloc.o
In file included from /usr/include/c++/4.3/bits/postypes.h:47,
                 from /usr/include/c++/4.3/bits/char_traits.h:47,
                 from /usr/include/c++/4.3/string:47,
                 from ./src/base/commandlineflags.h:52,
                 from src/base/logging.h:48,
                 from src/system-alloc.cc:51:
/usr/include/c++/4.3/cwchar:52:24: error: wchar.h: No such file or directory

なんだ? と思って問題が起きているsource/vendor/google-perftools-0.97でmakeをたたくと処理がもう少し進んでしまったりもするのだが、実はconfigure中に同じエラーが起きているため、しばらくするとまたコケる。

installスクリプトの中でやっていることをよく見てみると、インクルードパスを指定する必要はどうもなさそうに思える。というわけで、以下の変更を加えてinstallスクリプトを動かしてみたところbuildが異常終了しなくなった。

--- ruby-enterprise-1.8.6-20080507.orig/installer.rb	2008-05-26 17:51:36.000000000 +0900
+++ ruby-enterprise-1.8.6-20080507/installer.rb	2008-06-12 12:06:48.000000000 +0900
@@ -123,8 +123,10 @@
 		File.open("source/.prefix.txt", "w") do |f|
 			f.write(@prefix)
 		end
+=begin
 		ENV['C_INCLUDE_PATH'] = "#{@prefix}/include:/usr/include:/usr/local/include:#{ENV['C_INCLUDE_PATH']}"
 		ENV['CPLUS_INCLUDE_PATH'] = "#{@prefix}/include:/usr/include:/usr/local/include:#{ENV['CPLUS_INCLUDE_PATH']}"
+=end
 		ENV['LD_LIBRARY_PATH'] = "#{@prefix}/lib:#{ENV['LD_LIBRARY_PATH']}"
 	end
 	

installスクリプトによらず、source/vendor/google-perftools-0.97でconfigure --prefix=<インストールパス> --disable-dependency-trackingを実行の上、make libtcmalloc_minimal.la && mkdir -p <インストールパス>/lib && cp .libs/libtcmalloc_minimal.so* <インストールパス>/lib/としておくというのでも回避できるだろうと思う(その後でinstallスクリプトを実行する)。

これでPassengerを動かしてみたいところだが、また後で。

$ ./ruby -v
ruby 1.8.6 (2008-03-03 patchlevel 114) [i686-linux]
$ ./ruby -e 'p GC.copy_on_write_friendly?'
true

Rails 2.0.2とRuby 1.8.7のString#chars

公開日時 akira Mon, 09 Jun 2008 15:00:00 GMT

Ruby 1.8.7からはString#charsが定義されるようになった。これがRails 2.0.2なんかではまずくって、たとえば"hello".firstNoMethodError: undefined method `[]' for #<Enumerable::Enumerator:0xb6f3f5b4>などとなる。これはRailsのように積極的に組み込みクラスを改変しているものの定めといえる。

Rails 2.1.0を見てみると以下のようになっていて、Ruby 1.8.7のString#charsを削除し、Rails(ActiveSupport)が定義しているcharsを使おうとしている。

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module String #:nodoc:
      unless '1.9'.respond_to?(:force_encoding)
        # Define methods for handling unicode data.
        module Unicode
          def self.append_features(base)
            if '1.8.7'.respond_to?(:chars)
              base.class_eval { remove_method :chars }
            end
            super
          end
[...]

Rails 2.0.2でも同じようにするには、Rails側に手を入れたほうがてっとり早いとは思うのだが、ひとまずそれはしないことにするならばconfig/initializers/remove_string_chars.rbを以下の内容で作っておくというやり方はどうだろう。

unless '1.9'.respond_to?(:force_encoding)
  String.class_eval do
    begin
      remove_method :chars
    rescue NameError
      # OK
    end
  end
end

script/consolescript/server(アプリケーションはTracks-1.6)で簡単な動作確認をしてみたところ、ひとまず前述の例外を回避できているようだ(Ruby 1.8.7、1.8.7-p17および1.8.6で確認した)。ただしそれ以外の環境やアプリケーションでどうなるかは確認していない。

参考: ruby 1.8.7 と rails 2.0.2