Ruby 2.7 で Bundler がらみの不可解な現象に遭遇している。
「何やねん,早よ言え」という方は「総括」の節を見てくだされ。
前提
この記事で述べる実験は 2020-01-17 時点のもの。
Ruby 2.6.5 と Ruby 2.7.0 の二つについて実験を行った。
Windows と macOS と CentOS で実験した。
Windows は RubyInstaller for Windows でインストールしたもの。他は rbenv でインストール。
Ruby 2.6.5 も 2.7.0 も,デフォルトの bundler gem は 2.1.2 だが,
gem update bundler
して,bundler 2.1.4 をインストールした。
したがって,どの OS,どの Ruby でも,2.1.2 と 2.1.4 が入った状態。
実験 1
まず,OS と Ruby バージョンに関わらず,端末で
bundle -v
とやったら
Bundler version 2.1.4
と表示された(当然)。
次に,
gem list bundler
とやったときの結果が意外だった。
macOS と CentOS の Ruby 2.7.0 でだけ
bundler (2.1.4)
と表示され,その他の OS,Ruby バージョンの組み合わせでは
bundler (2.1.4, default: 2.1.2)
と表示されたのだ。後者のほうがしかるべき表示のような気がする。
Ruby のバージョンだけで違いが生じるなら「Ruby 2.7 で仕様が変わったのかな」と思うところだが,Windows の場合,バージョンによる違いはない。
追記 2020-01-24
Ruby 2.7 で,CentOS および macOS でのみ 2.1.2 が見えなかったのは,rbenv のプラグイン rbenv-communal-gems のせいかもしれない。確証はないけど。
実験 2
以下のスクリプトを実行した。
gem "bundler"
require "bundler"
p Bundler::VERSION
すると,すべての組み合わせにおいて,「"2.1.4"
」が表示された。
これは期待通り。
インストールされている最新版が require されるはずだから。
実験 3
前の実験をちょっと改変して,
require "bundler"
p Bundler::VERSION
としてみた。
すると今度は,どの OS でも,Ruby 2.6.5 では「"2.1.4"
」と表示されるが,Ruby 2.7.0 では「"2.1.2"
」と表示される。
え? 最新版じゃない?
これは何か Ruby 2.7 で仕様が変わったとした思えない。うーむ。
実験 4
まず,Gemfile に以下のようにだけ書く。
source "https://rubygems.org"
(なんか gem を指定してもいいのだが,何も書かなくても実験は可能)
そして,コマンドライン上で
bundle check
する。
そうすると,Gemfile.lock が出来る。
Ruby 2.6.5 でやっても,Ruby 2.7.0 でやっても,その末尾には
BUNDLED WITH
2.1.4
と書かれる。
これは納得できる話だ。実験 1 で,bundle コマンドは最新版である 2.1.4 が使われることを確認しているから。
しかし,この状態で以下のスクリプトを動かすとどうなるか。
require "bundler"
Bundler.require
Ruby 2.6.5 では何事もなく終わるが,Ruby 2.7.0 ではドバドバっとメッセージが出る。
先頭はこれ。
Warning: the running version of Bundler (2.1.2) is older than the version that created the lockfile (2.1.4). We suggest you to upgrade to the version that created the lockfile by running `gem install bundler:2.1.4`.
これはエラーではなく警告。
「Gemfile.lock に書いとる(Bundler の)バージョンは 2.1.4 なのに,今走っとる Bundler は 2.1.2。古いやんけ」
と怒っているようだ。
親切にも
「gem install bundler:2.1.4
ってやってアップグレードしたらよろし」
て suggest してくれてるのだが,もう入っとるっちゅーに。
末尾はこれ。
/Users/hogehoge/.rbenv/versions/2.7.0/lib/ruby/2.7.0/rubygems.rb:275:in `find_spec_for_exe': Could not find 'bundler' (2.1.4) required by your /Users/hogehoge/temp/xxx/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.1.4`
こっちは警告じゃなくてエラー。
「Gemfile.lock で要求されている bundler 2.1.4 が,み,見つからない」(がくっ)
という dying message を残して死亡している。
これはどう解釈できるのか?
実験 3 の結果と整合的,という気はする。
Ruby 2.7.0 では単純に bundler を require すると,なぜか最新版ではなく 2.1.2 が読み込まれるのだった。
実験 5
実験 4 と同じファイルを使い,
bundle exec ruby hoge.rb
のような形でスクリプトを動かすと正常だった。
bundle exec
は,(よく分からんけど)Gemfile.lock の内容を見て使う gem のバージョンを決定してからスクリプトを動かすらしいので,これで正常化するのは納得できる気がする。
ruby コマンドでなく,Rake タスクを実行したいときは要注意。
いままで
rake hoge
とやっていたのを
bundle exec rake hoge
とするためには,Gemfile に gem 'rake'
を入れてやらないといけない。
総括
一言でまとめると,どうやら Ruby 2.7 では bundler のバージョンを指定する仕組みを使わない限り,最新版の bundler が require されることはない,ということのようだ。
それによって各種の問題が起こる,と。
対策
結局どうすればいいのか。
おそらく以下の二つしかないと思う。
-
bundle exec
を付けて実行 -
require "bundler"
の前にgem "bundler"
を書く
前者の場合,Rake タスクなら Gemfile を書き換える必要がある。確かめてはいないが Thor タスクでも同様だろう。
後者の場合も当然スクリプトを書き換える必要がある。
既存のコードを大量に書き換えなければならないことになりそう。
え〜っ
解決(追記 2020-04-23)
何だかよく判らないのですが,以下の記事
【Rails6】rails new で bundler: failed to load command: spring が出た。問題はbundler2.1.4かRuby2.7にあるようだが..... - Qiita
に書かれている
$ gem update --system 3.0.8 && gem update --system
を試したところ直りました。