kiyokaのブログアーカイブ

Archive of old blog posts

Rubygems.orgから取得したgemでエラーが出るのはなぜか

img ずっと前から自分が作ったgemが環境によってエラーになる原因が解析できずにいた。 やっと、tenderloveことAaron Patterson氏が書かれたブログエントリでメカニズムが理解できた。

Aaron Patterson氏の説明を読み解く

Shaving a YAML Yack | RubyGems.org Have you ever seen this error?

$ gem install rails –pre ERROR: While executing gem … (NameError) uninitialized constant Psych::Syck $

Yes, I have too. Today we’re going to discuss the source of this error, and what we need to do to fix it.

やっぱり、誰もが同じエラーに遭遇していたのか。 自分の環境が悪いのかと思っていた。自分の環境はちょっと違うエラーメッセージだけど、原因は同梱のようだ。 結局RubyのYamlライブラリの互換性問題だった。 gemspecファイルの依存規則のところに’=’イコールを使うとダメらしい。

Gem::Specification.new do |s|
  ...
  s.add_dependency('activesupport', '= 3.1.0')
  ..
end

上記のようなgemspecファイルを元に、gemパッケージが生成されるが、gemの中に格納されるmetafileはyaml形式となる。 gemパッケージ生成環境がPsychを使っている場合と、Syckを使っている場合で生成されるmetafile中の ‘=’の扱いが異なる。

再現確認

メカニズムについては上記のAaron氏のブログエントリに書かれている通りなので、NGとOKパターンだけまとめると次のようになる。 (1) OK … Syckで作ったgem => SyckライブラリがインストールされたRuby環境でmetafile(yaml)をパース (2) OK … Syckで作ったgem => PsychライブラリがインストールされたRuby環境でmetafile(yaml)をパース (3) OK … Psychで作ったgem => PsychライブラリがインストールされたRuby環境でmetafile(yaml)をパース (4) NG … Psychで作ったgem => SyckライブラリがインストールされたRuby環境でmetafile(yaml)をパース

NGの確認は自分のOSSパッケージsekka-0.9.3のパッケージをPsych環境で作った。gemspecには次のような行がある。

  gemspec.add_dependency( "nendo", "= 0.5.3" )

なお、gemをインストールした環境はlibyamlの入っていないRuby環境だ。そこではyaml parserはPSychではなくSyckが使われる。 このようにエラーメッセージが出る。

/tmp $ gem --version
Invalid gemspec in */usr/local/stow/ruby-1.9.2-p290/lib/ruby/gems/1.9.1/specifications/sekka-0.9.3.gemspec*: Illformed requirement *"#<Syck::DefaultKey:0x00000102ca5258> 0.5.3"*
1.8.10

念のためSyck環境であることを確認。

/tmp $ ruby --version
ruby 1.9.2p290 (2011-07-09 revision 32553) *x86_64-darwin10.8.0*
/tmp $ irb
irb(main):001:0> require 'yaml'
require 'yaml'
=> true
irb(main):002:0> YAML.dump * '=' *
YAML.dump * '=' *
=> "--- \n- \"=\"\n"
irb(main):003:0> YAML.load "---\n- =\n"
YAML.load "---\n- =\n"
=> *#<Syck::DefaultKey:0x00000101259de0>*
irb(main):004:0> 

rubygems.orgとの関係

Aaron氏のブログエントリでも書いてあるように、rubygems.orgはmarshalされたオブジェクトをユーザーに配布しているらしい。 上記の私の再現確認では、ローカルにあるgemファイルを使ってgem install していたが、rugygemsに登録した場合はrubygems.org内部のyamlパーサでパースしたmarshalオブジェクトが配布されるということだ。 現在rubygems.orgはSyckだそうで、これからPsychに差しかえるべく動いているとのこと。 つまり、Psyckでgemsでパッケージを作りrubygems.orgに登録すると上記のパターンで書いた(4)に該当する。 このとき、クライアント環境がどのような環境であろうとエラーが出るはず。これはマズい。

Shaving a YAML Yack | RubyGems.org How can we fix this?

We have two ways to deal with this issue. The first way to deal with this issue is to upgrade rubygems. Rubygems contains code to work around the issue when installing gems. But it does not fix the issue.

The only way we can fix this error for all users is to upgrade rubygems.org to use psych as the YAML parser. Upgrading rubygems.org will prevent the strange objects from entering marshal data sent to users.

回避方法 案1

gemspecはSyck環境でgemパッケージを作ってrubygems.orgに登録する。 [Sekka]ではこれまで、この方法を使っていた。

回避方法 案2

gemspecに、’=’を使わない。かわり’>=’を使う。

* SekkaのRakefile

現状

  gemspec.add_dependency( "nendo", "= 0.5.3" )

回避策

  gemspec.add_dependency( "nendo", ">= 0.5.3" )

かなり姑息というか、本来行いたい指定とは違うのでちょっと不満がある。 [Nendo]のほうで下位互換性を壊すバージョンアップがあった場合[Sekka]が動かなくなるという直接的な問題がある。 ただし、どんな環境でgemパッケージを作ってもよい。

まとめ

gemのinstall時のエラーは多くの人が遭遇しているはず。 メカニズムを理解すれば環境に依存しないgemを作れるが、スマートな解決方法は無い。

追記

実際に複数の環境でgemパッケージを作り、内容を貼りつけた。 「 2011-09-30Ruby* Rubygems.orgから取得したgemでエラーが出るのはなぜか(追記)」