Help us understand the problem. What is going on with this article?

gemライブラリの依存はGemfileではなくgemspecに記述する理由

More than 5 years have passed since last update.

Conclusion

bundle gemrails plugin newなどでgemひな形を作るときGemfileと.gemspecファイルができる.
どちらを使っても依存関係を記述できるように見えてしまう.
結論を言うと他の様々なサイトでも指摘されている通り、gemライブラリの依存関係はgemspecに記述し、Gemfileはgemspec1行書くにとどめておく.

以下のようにgemに依存するgemライブラリを作成し、Gemfile, .gemspecそれぞれに依存関係を記述して挙動をたしかめた.

スクリーンショット 2015-05-01 12.27.22.png

■今回テストするコード

dependent1/lib/dependent1.rb
require "dependent1/version"

module Dependent1
  def self.version
    VERSION
  end
end
dependent2/lib/dependent2.rb
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に依存記述する場合

:exclamation: Gemfileはgemライブラリの依存を定義するものとしては使えません!!

dependent2/Gemfile
source 'https://rubygems.org'
# Specify your gem's dependencies in dependent2.gemspec
gemspec
gem "dependent1", "0.0.1"

dependent2下でbundle install

スクリーンショット 2015-05-01 12.36.43.png

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

スクリーンショット 2015-05-01 12.41.29.png

しかし、ruby app側でdependent2をbundle installすると、dependent1は読み込まれない.
これはつまり、再帰的にgemの依存解決するときにgemが持っているGemfileの情報は使われないということ.

rubyapp/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

スクリーンショット 2015-05-01 12.43.31.png

ruby app側でdependent1もGemfileに記述すれば、dependent2内で使おうとしているdependent1も使えるようになるが、当然ruby app側で指定したバージョンが有効になる.
dependent2がdependent1のバージョン整合性を担保できないということになる.

rubyapp/Gemfile
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に依存記述する場合

dependent2/dependent2.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

スクリーンショット 2015-05-01 14.11.31.png

gemspecに依存が記述されていればrubyappがbundle installされたとき依存するgemが再帰的に取得される.
結果dependent2の中でdependent1が使えるようになり、dependent2がバージョンをコントロールできるようになる.

rubyapp/Gemfile
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

スクリーンショット 2015-05-01 14.11.44.png

ruby app側にもdependent2が依存するgemを明示的に記述すると、きちんと競合が検知されてbundle installがエラーになる.

rubyapp/Gemfile
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
metheglin
敷布団カバーと掛布団カバーを逆につけて寝ています
https://metheglin.jp/#logical
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした