Bundler が Ruby 2.7 で変な件


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 バージョンに関わらず，端末で

```rb
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

以下のスクリプトを実行した。

```rb
gem "bundler"
require "bundler"

p Bundler::VERSION
```

すると，すべての組み合わせにおいて，「`"2.1.4"`」が表示された。
これは期待通り。
インストールされている最新版が require されるはずだから。

# 実験 3 

前の実験をちょっと改変して，

```rb
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 に以下のようにだけ書く。

```rb: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 が使われることを確認しているから。

しかし，この状態で以下のスクリプトを動かすとどうなるか。

```rb
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](https://qiita.com/chabudai/items/4051c21ca9161a301bfb#%E5%BE%8C%E6%9B%B8%E3%81%8D)
に書かれている

```shell
$ gem update --system 3.0.8 && gem update --system
```

を試したところ直りました。