3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Bundler.require でのエラーが変わった

Last updated at Posted at 2016-04-12

(この記事でもともと書いていた解決法はよく考えたらトンデモナイので,「追記:よりマシな解決法(2017-01-21)」をご参照ください)

Ruby のプログラムで gem を読み込むとき,Bundler を使えば gem のバージョンが管理しやすくて便利なのだけど。
ライブラリーの中に require でエラーを出すものがあった場合の挙動が,Bundler 1.10 → 1.11 で変わったようなので報告する。

経緯

gtk2 を require するときのエラーを無視したい

gtk2 という gem に含まれる機能を使いたいのだが,gtk2 は本来 GUI 環境で何かするものなので, X Window などの GUI 環境が無いシステムで

require 'gtk2'

とすると,

Cannot open display:   (Gtk::InitError)

という例外が発生する。いまの場合,このエラーは単純に無視してもかまわないので,

 begin
   require 'gtk2'
 rescue RuntimeError
 end

にすればいい。

Bundler を使うときはどうするか

Bundler を使うときは,Gemfile に

Gemfile
gem 'gtk2'

と書くわけだが,実際の require

Bundler.require

で行う。

さて,Bundler 1.10.2 の頃は,Bundler.require したときに Gtk::InitError が出ていたので,ちょっと乱暴だけれども

begin
  Bundler.require
rescue RuntimeError
end

と書いていれば用は足りていた。(Gtk::InitErrorRuntimeError の下位クラス)

ある日突然死んだ

ところが,ある日突然,プログラムが動かなくなった。その前後でプログラムに変更は加えていないはず。

いろいろな可能性を探っていたところ,Bundler のバージョンが 1.10.5 から 1.11.2 に上がったことが怪しいと思われた。

原因と対策

原因は,Bundler 1.11 で,Bundler.require 時に gem が出した例外の扱い方が変わったことだった。

Bundler 1.11 では,gtk2 が Gtk::InitError を出してもそのままにせず,

There was an error while trying to load the gem 'gtk2'. (Bundler::GemRequireError)

を出してくれるようになったらしい。

この Bundler::GemRequireErrorRuntimeError の下位クラスではないので,前述の rescue では拾ってくれなかったんである。

そこで,

begin
  Bundler.require
rescue StandardError
end

に変えてみたところ,従来通りの動作になった。(Bundler::GemRequireErrorStandardError の下位クラス)

感想

焦った~

Bundler を使うと require する gem のバージョンが合理的に制御できていいんだけども,bundler gem 自体は Gemfile には書かず,バージョンを管理しない(していなかった)。落とし穴だったなあ。

追記:よりマシな解決法(2017-01-21)

よく考えたら,bundler の仕様についての記述は問題ないと思うが,問題の解決法としては良くないと思ったので,ここで訂正したい。

まず,Bundler の仕様変更前に使っていた解決法は,

begin
  Bundler.require
rescue RuntimeError
end

だったわけだけど,これだと atk2 が引き起こすエラー以外も全部無視することになって,他の gem の require で問題が起きていても気づかない。

実際に発生していた例外は Gtk::InitError なわけだから,

rescue RuntimeError

でなく

rescue Gtk::InitError

にしておけばマシだったと思う。

Bundler の仕様変更後に対応する解決法でも,

rescue StandardError

ではなく,

rescue Bundler::GemRequireError

とすべきだった。ただ,これでも広すぎる。ほかの gem が同じような例外を出しても Bundler::GemRequireError になっちゃうので。

それから,書くのを忘れていたけど,Bundler.require での例外を無視するこのような解決法を取る場合,

gem 'atk2'

は Gemfile の一番最後に書かないといけない。
でないと,atk2 の require で例外が発生したあと,それ以降の gem の require が行われないからだ。

これは実際ハマったんだけど,記事に入れるのを忘れていた。

結局,いまのところ,一番マシな解決法は,atk2 の require を Bundler にはさせないということだと考えている。

つまり,Gemfile では

Gemfile
gem 'gtk2', require: false

としておいて,自動 require をやめさせる(Bundler.require では require されない)

そして,

Bundler.require

したあとに

begin
  require 'gtk2'
rescue Gtk::InitError
end

とする。

これならば gtk2 による Gtk::InitError だけを無視することになって問題が少ない。
また,Gemfile 中で gem 'atk2' を書く位置が自由になる。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?