Conclusion
bundle gem
やrails plugin new
などでgemひな形を作るときGemfileと.gemspecファイルができる.
どちらを使っても依存関係を記述できるように見えてしまう.
結論を言うと他の様々なサイトでも指摘されている通り、gemライブラリの依存関係はgemspecに記述し、Gemfileはgemspec1行書くにとどめておく.
以下のようにgemに依存するgemライブラリを作成し、Gemfile, .gemspecそれぞれに依存関係を記述して挙動をたしかめた.
■今回テストするコード
require "dependent1/version"
module Dependent1
def self.version
VERSION
end
end
require "dependent1"
require "dependent2/version"
module Dependent2
def self.dependent1_version
::Dependent1.version
end
def self.version
::Dependent2::VERSION
end
end
dependent2(gemに依存するgem)にdependent1のバージョンを出力するdependent1_versionメソッドを定義し、それぞれのパターンでどのdependent1バージョンが返されるか確認していく.
Gemfileに依存記述する場合
Gemfileはgemライブラリの依存を定義するものとしては使えません!!
source 'https://rubygems.org'
# Specify your gem's dependencies in dependent2.gemspec
gemspec
gem "dependent1", "0.0.1"
dependent2下でbundle install
bundle install
すれば正常にdependent1を使えるようになる
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.version
=> "0.1.1"
irb(main):003:0> Dependent2.dependent1_version
=> "0.0.1"
ruby app下でbundle install
しかし、ruby app側でdependent2をbundle installすると、dependent1は読み込まれない.
これはつまり、再帰的にgemの依存解決するときにgemが持っているGemfileの情報は使われないということ.
source 'https://rubygems.org'
gem "dependent2", "0.1.1"
dependent2内でdependent1を使おうとしているとLoadErrorになる.
% bundle exec irb
irb(main):001:0> require "dependent2"
LoadError: cannot load such file -- dependent1
from /usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/dependent2-0.1.1/lib/dependent2.rb:1:in `require'
from /usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/dependent2-0.1.1/lib/dependent2.rb:1:in `<top (required)>'
from (irb):1:in `require'
from (irb):1
from /usr/local/ruby/bin/irb:11:in `<main>'
ruby app下でdependent1,dependent2を両方記述してbundle install
ruby app側でdependent1もGemfileに記述すれば、dependent2内で使おうとしているdependent1も使えるようになるが、当然ruby app側で指定したバージョンが有効になる.
dependent2がdependent1のバージョン整合性を担保できないということになる.
source 'https://rubygems.org'
gem "dependent2", "0.1.1"
gem "dependent1", "0.0.2"
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.dependent1_version
=> "0.0.2" # <- ruby appのGemfileで指定したバージョン!!
gemspecに依存記述する場合
# 省略
Gem::Specification.new do |s|
# 省略
s.add_dependency "dependent1", "0.0.1"
end
dependent2下でbundle install
特に問題なし.
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.dependent1_version
=> "0.0.1"
ruby app下でbundle install
gemspecに依存が記述されていればrubyappがbundle installされたとき依存するgemが再帰的に取得される.
結果dependent2の中でdependent1が使えるようになり、dependent2がバージョンをコントロールできるようになる.
source 'https://rubygems.org'
gem "dependent2", "0.0.1"
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.dependent1_version
=> "0.0.1"
ruby app下でdependent1,dependent2を両方記述してbundle install
ruby app側にもdependent2が依存するgemを明示的に記述すると、きちんと競合が検知されてbundle install
がエラーになる.
source 'https://rubygems.org'
gem "dependent2", "0.0.1"
gem "dependent1", "0.0.2"
% bundle install
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
You have requested:
dependent1 = 0.0.1
The bundle currently has dependent1 locked at 0.0.2.
Try running `bundle update dependent1`
% bundle update dependent1
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Bundler could not find compatible versions for gem "dependent1":
In Gemfile:
dependent2 (= 0.0.2) ruby depends on
dependent1 (= 0.0.2) ruby
dependent1 (0.0.1)
その他メモ
Gemfileにはgitから直接コードを取得するようにgitリポジトリパスを指定することができるが、この書き方はgemspecではできないらしい. なので、ちゃんとgem化してrubygemsにアップロードするか、gemのプライベートホスティングなどする必要がある.
## この書き方はGemfileでしかできない
gem 'dependent2', '0.0.1', git: 'git@github.com:metheglin/dependent2.git', branch: 'master'
また、gemspecでバージョンを固定すると、ruby app側で使いたいライブラリのバージョンが制限されるためgemを作る場合バージョン制約はなるべく緩いほうがよさそう.
Environment
% gem -v
2.2.2