この記事は「なんか分かんないけどbundle exec
つけないと動かないからつけてるけど、つけなきゃいけない理由が知りたい」という方向けに説明を試みるものです。
TL;DR
一言で言うと、bundle exec
をつけるとGemfile.lock
に書いてあるとおりにrequire
するようになります。
Gemfile.lock
に記載されているgemは、お互いの依存関係を満たすようにバージョンが選ばれているので、Gem:: ConflictError
を防ぐことができます。
もうちょっと詳しく
Bundlerがないと何が起きるのか
具体例で説明します。以下のような依存関係を持つgem, "GemA", "GemB"があるとしましょう。両方ともインストール済みとします。
module GemA
Version = "1.0.0"
end
require 'gem_a'
module GemB
Version = '1.0.0'
def self.say
puts "I'm using GemA ver. #{::GemA::Version}!"
end
end
# gem list
*** LOCAL GEMS ***
gem_a (1.0.0)
gem_b (1.0.0)
もちろんどちらもrequire
できます。
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
irb(main):005:0> GemB::say
I'm using GemA ver. 1.0.0!
=> nil
ここで、GemAを2.0.0にアップデートします。
# gem update gem_a
Updating installed gems
Updating gem_a
Fetching: gem_a-2.0.0.gem (100%)
Successfully installed gem_a-2.0.0.gem
Gems updated: gem_a
# gem list
*** LOCAL GEMS ***
gem_a (2.0.0, 1.0.0)
gem_b (1.0.0)
アップデートできました。irbでさっきと同じことをしてみます。
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"
ちゃんとアップデートされています。GemBも使ってみましょう。
irb(main):003:0> require 'gem_b'
Traceback (most recent call last):
8: from /usr/local/bin/irb:11:in `<main>'
7: from (irb):3
6: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:39:in `require'
5: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:128:in `rescue in require'
4: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems.rb:217:in `try_activate'
3: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems.rb:224:in `rescue in try_activate'
2: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/specification.rb:1438:in `activate'
1: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/specification.rb:2325:in `raise_if_conflicts'
Gem::ConflictError (Unable to activate gem_b-1.0.0, because gem_a-2.0.0 conflicts with gem_a (= 1.0.0))
おおっと…困りました。GemBが壊れてしまいました。既にGemA (2.0.0)をrequire
しているので、GemBに必要なGemA (1.0.0)を読み込むことができず、エラーになってしまいました。1
どうなると嬉しいのか
GemBを使うプロジェクトでは、アップデートしたGemA (2.0.0)を使わず、GemA (1.0.0)がそのまま使えればよかったのです。
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
一方で、GemBを使わないプロジェクトでは、最新のGemA (2.0.0)を使いたいですね。
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"
これを実現するのがBundlerであり、bundle exec
というコマンドです。
Bundlerがあるとどうなるのか
GemAとGemBを使いたいプロジェクトに、こんな感じのGemfileを用意します。
gem 'gem_a'
gem 'gem_b'
そして、bundle
を実行すると、Gemfile.lock
が生成されます。
# bundle
Fetching gem metadata from https://rubygems.org/.
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using bundler 1.16.2
Using gem_a 1.0.0
Using gem_b 1.0.0
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
# cat Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
gem_a (1.0.0)
gem_b (1.0.0)
gem_a (= 1.0.0)
PLATFORMS
ruby
DEPENDENCIES
gem_a
gem_b
BUNDLED WITH
1.16.2
この状態でbundle exec irb
してみましょう。
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
irb(main):005:0> GemB::say
I'm using GemA ver. 1.0.0!
=> nil
ちゃんと動きます!
普通にirbを起動すると、新しいバージョンのGemA (2.0.0)もちゃんと使えます。
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"
Gemfile
からgem_b
を取り除いてbundle
すれば、GemA (2.0.0)が使えます。
gem 'gem_a'
# bundle
Using bundler 1.16.2
Using gem_a 2.0.0
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
# cat Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
gem_a (2.0.0)
PLATFORMS
ruby
DEPENDENCIES
gem_a
BUNDLED WITH
1.16.2
Bundlerってすばらしいですね。
まとめ
Bundlerがあれば、いろんなバージョンのgemをたくさんインストールしても、コンフリクトしないようにrequire
することができます。
(余談ですが、上記のような動きなので、基本的にはbundle --path=vendor/bundle
とかしないでグローバルインストールしてもちゃんとプロジェクトごとの独立性は保たれるはずです)
-
「なんでGemBはGemA (2.0.0)を使わずにGemA (1.0.0)を読み込もうとすることができるの?それってBundlerの仕事なんじゃないの?」と思う方は、おそらくGemとBundlerの機能を混同しています。gemの依存関係はgemごとに定義してあります (gemの中に.gemspecという定義ファイルがあります)。このため、gemを1つ使うだけなら普通に
require
すれば、依存しているgemも一緒に適切なバージョンが読み込まれます。一方で、Bundlerは「複数のgemを同時に使うとき、どう依存関係を解決すればGem::ConflictError
を避けられるか?」という問題を解決するためのものです。 ↩