くりにっき

フルスタックキュアエンジニアです

gemspecにRUBY_VERSIONによるif文書くのは意味がないので今すぐやめるべき

自戒です

tl;dr

  • gemspecの中でRubyのバージョンによってインストールしたいgemのバージョンを変えたい時は、gemspecではなくGemfileでif文書くのがおそらく正解

発端

先月くらいのFacebook内のちょっとした会話がきっかけでした *1

f:id:sue445:20160901233138p:plain

間違った対処法

f:id:sue445:20160901233158p:plain

  if Gem::Version.create(RUBY_VERSION) >= Gem::Version.create("2.2.2")
    spec.add_dependency "activesupport", ">= 4.0.0"
  else
    # NOTE: activesupport 5.x supports only ruby 2.2.2+
    spec.add_dependency "activesupport", ">= 4.0.0", "< 5.0.0"
  end

https://github.com/sue445/rubicure/blob/v0.4.7/rubicure.gemspec#L23-L28

gemspecでこんなif文を書いておけば、ruby 2.2.2未満の時はactivesupport 4系がインストールされる、、、












そんなふうに考えていた時期が俺にもありました

f:id:sue445:20160901234657p:plain

Travis CIのビルドの各Rubyのバージョンのログを見ても意図したバージョンがインストールされていたし、間違いなんてあるわけないと思っていました。

だがしかし

f:id:sue445:20160901233532p:plain

https://rubygems.org/gems/rubicure/versions/0.4.7 の表示、てっきり表示だけこうなってて gem install はいい感じに分岐してくれるのかと思ってた。。。

sonots先生曰く

f:id:sue445:20160901233846p:plain

rubygemsjruby と cruby (platform) で別々の gem をリリースすることはできるんですが、残念なことに cruby のバージョンで別々の gem をリリースすることはできないのですよねぇ


リリースする gem 自体の依存は緩くして、アプリ側の Gemfile であなたの rubyバージョンに合わせてactivesupport 絞ってね、というしかないのかなぁと思ってました。rubygems に機能が足りてないと思うんですよねぇ。


travis を通す時も、gemspec は緩くしておいて、Gemfile 二つ用意して、build matrix を頑張る

検証結果

f:id:sue445:20160901234124p:plain

ruby 2.1.9で上記gemspecの分岐が入ったrubicureのバージョンをインストールしようとすると、「activesupport requires Ruby version >= 2.2.2.」とエラーが出ているのが分かると思います

gist.github.com

上記エラーは rubygems.orgからのインストールログですが、geminaboxホスティングされた社内gemでも検証をしましたが同様の結果でした。

所感

  • add_dependencyadd_runtime_dependency )で書くruntime dependency(アプリの実行時に必要なgem)に関しては上記の理由で意味が無い
    • もしバージョンを絞りたい場合はgemspecではゆるくしておいてGemfileでバージョンを絞る
    • 必要ならドキュメントにもそれを明記する。*2
  • add_development_dependency で書くdevelopment dependency(gemの開発時に必要なgem)に関してはgem installでインストールされないため、if文を書いても意味なくはない
  • とはいえ、add_development_dependency はOKで add_dependency はNGというのは普通分からないし混在すると紛らわしいため、gemspecに RUBY_VERSION のif文は一切書かずに Gemfileに書くよう統一するのが今の自分の中では正解だと思ってます。(異論は認める)

Rubyのバージョンによる分岐を全部Gemfileに寄せた結果

こんな風になりました

source "https://rubygems.org"

# Specify your gem's dependencies in rubicure.gemspec
gemspec

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.1.0")
  # NOTE: build is failed when use ruby 2.0 and rspec-parameterized 0.3.0+
  #   https://travis-ci.org/sue445/rubicure/jobs/114266855
  gem "rspec-parameterized", "< 0.3.0"
end

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.1.0")
  # NOTE: unparser v0.2.5 drop support ruby < 2.1
  gem "unparser", "< 0.2.5"
end

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.2.2")
  # NOTE: activesupport 5.x supports only ruby 2.2.2+
  gem "activesupport", ">= 4.0.0", "< 5.0.0"
end

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.3.0")
  gem "backport_dig"
end

https://github.com/sue445/rubicure/blob/v0.4.9/Gemfile

rubicureもそろそろRuby 2.0系サポートきってもいいかなぁ。。。

謝辞

上記Facebookの会話の転載を快諾してくださった id:koic さんと id:sonots さん、ありがとうございます m(_ _)m

*1:FBキャプチャ転載に関しては事前にお二人の許可はとっています

*2: rubicureだとこんな感じ https://github.com/sue445/rubicure/blob/v0.4.9/README.md#installation