gemのバージョンアップで依存関係が壊れるのを調べやすくした

要約

  • gemのdependencyに < 5.2.0と書いてあると、 5.2.0.rc2までインストールできる
    • 5.2.0がリリースするまでインストールできないと気がつきにくい
  • 任意のgem依存関係を上書きして、bundle install/updateを可能にするgemを作った
    • https://github.com/ota42y/virtual_gem
    • 5.2.0.rc2を元に5.2.0のgemを作れる
    • 任意のgemの依存関係を書き換えられる
    • 事前に依存関係が壊れるのを調べやすくなる

gemの依存関係によるインストール失敗

現在、Rails 5.2.0.rc2がリリースされています。
ですが、仮に現状で上手く動いていたとしても5.2.0のリリース時に一部のgemの依存関係が5.2.0.rc2までしか受け入れておらず、インストールできなくなる可能性があります。

gemには依存関係を設定できますが、'< 5.2.0' と書いた場合は5.2.0未満のgemを指定したこととなるため、5.2.0.rc2は対象となります。
ですが、5.2.0は範囲外となるため、5.2.0をインストールすることはできません。

そのため、rc版で問題なく動いていたとしても正式リリースされると依存関係を解決できずにアップデートできず、依存するgemの更新を待たないといけません。
依存するgemが依存しているgemが同じような問題を含んでいた場合は、それらが全て解決するのを待つ必要があります。

事前に依存関係を修正しておくことでこの問題を回避できますが、事前に全てのgemの依存関係を調べることはかなり難しいです。 かつ正式リリースされるまで問題は起こらないため先に対策するのは困難です。
また、gemによっては正式リリースまで依存関係を修正しないポリシーもあり得るため、複雑な依存関係の場合には調べるのがいっそう困難になります。

この問題は2つの原因から成り立っています。

  • gemが正式リリースされるまでbundle installで新しいバージョンを試せない
  • bundle install時の依存関係の解消にはgemの設定を直す必要がある

問題のあるgemを全てforkして、バージョンを書き換えた上でそれを使うことでこれらの問題は解決できます。
ですが、継続的にmasterを追従していくことや、変更をするgemが大量にあった場合はそれを続けるのは困難です。

virtual gemによる解決

この問題に対して、bundle install/update時にgemのバージョンと依存関係を書き換え、問題のある設定になっていないかを調べられるgemを作りました。 https://github.com/ota42y/virtual_gem

仮想的なgemを作る

以下のようにGemfileで設定を行うと、特定バージョンのgemとして振る舞う仮想的なgemを作成して、依存関係の解決を行えます。

require 'virtual_gem'
::VirtualGem.register_virtual_gem(name: 'ota42y_test_gem',new_version: '0.3.0', original_version: '0.2.0')

source "https://rubygems.org"
gem 'ota42y_test_gem', '0.3.0'

この例では、‘ota42y_test_gem’のバージョン0.2.0を元に、バージョン0.3.0として振る舞う仮想的なgemを作成しています。
0.3.0はこの世に存在していないので、通常ではbundlerは見つけられずに失敗します。
ですが、virtual_gemによって仮想的なgemがインストールされている状態になるため、bundle installは成功します。

これにより、rails 5.2.0.rc2をrails 5.2.0として扱うことができるため、依存関係の解決を試せます。

依存関係を書き換える

virtual_gemではまだ存在しないgemで依存関係の解決を試して見ることができますが、依存関係が衝突する可能性は十分にあります。 例えばrails 5.2.0.rc2はactiverecordの5.2.0.rc2に依存しているため、virtual_gemで5.2.0のrailsとactiverecordを作っても依存関係が衝突してしまい、インストールに失敗します。

依存関係が複雑な場合、何を直せば正しくインストールできるかを調べるのは大変です。
virtual_gemでは任意のgemの依存関係も変更できるため、この問題も解決できます。

例えば以下の場合、ota42y_dependent_test_gemの0.2.0はota42y_test_gemへの依存関係が '< 0.3'となっており、ota42y_test_gemの0.3.0はインストールできません。 一方で明示的に0.3.0をrequireしているため、通常は依存関係が衝突してエラーになります。

virtual_gemのregister_requirements_changesメソッドでは指定したgemの依存関係を変更できます。 そのため、ota42y_dependent_test_gemのota42y_test_gemへの依存関係を '< 0.3' から '<= 0.3' に書き換えることができます。 この書き換えにより依存関係の解決が成功するようになるため、bundle installを成功させられます。

require 'virtual_gem'
::VirtualGem.register_virtual_gem(name: 'ota42y_test_gem',new_version: '0.3.0', original_version: '0.2.0')
::VirtualGem.register_requirements_changes(name: 'ota42y_dependent_test_gem', version: '0.2.0', new_requirements: { 'ota42y_test_gem': ['<= 0.3'] })

source "https://rubygems.org"

gem 'ota42y_dependent_test_gem', '0.2.0'
gem 'ota42y_test_gem', '0.3.0'

これらの機能を使うことで、gemの依存関係を全て解決してbundle installを成功させる事がでるため、 gemのアップデートを待たずに依存関係の問題を調べられます。

まとめ

  • virtuag_gemでリリースされていないgemで依存関係がおかしくならないかを調べられる
  • 任意のバージョンのgemの作成ができる
    • 依存関係の解決を試せる
  • 指定したgemの依存関係を上書きできる
    • 依存関係の衝突を回避できる
  • 直さないといけないものがあったら事前にPRを送ってスムーズにアップデートを

なお、rails 5.2.0.rc2をrails 5.2として変更するには以下のように書くことでできます。
railsはかなり厳しく依存設定をしているので、書き換えが大変ですが…
https://gist.github.com/ota42y/6254d327612c05f21d68923601d38bde