Ruby 2.6.1 に含まれる Bundler 1.17.2 に不具合があるという話

不具合
----

先日知った以下の件。

- [Ruby 2.6.1 の Bundler の不具合のお知らせ](https://www.hsbt.org/diary/20190205.html#p02)

この問題は `gem update --system` によって default gems としての Bundler を 1.17.3 にすれば回避可能でしょうか？

Ruby 2.6.1 は Ruby 2.5.3 と比較して、メモリ使用量が大きく(数百 MB 単位で)削減される(ケースがある)ことが確認できているため、簡単な Workaround で回避可能であれば個人的には積極的に利用したいところです。


比較
----

default gems としての Bundler のバージョンと、通常の gem としての Bundler のバージョンの組み合わせについて、実行に問題がなさそうなのかを確認。確認内容があっているのか、あまり自信がない。

### Bundler 1.17.3 で作った Gemfile.lock

|  #  | default gems | gem install | `ruby -rbundler/setup`   | `bundle exec ruby -rbundler/setup`  |
| --- | ------------ | ----------- | ------------------------ | ----------------------------------- |
|  A  | 1.17.2       | None        | 指定バージョンを利用可能 | default gems のものを利用してしまう |
|  B  | 1.17.2       | 1.17.3      | bundler の取り違い       | 指定バージョンを利用可能            |
|  C  | 1.17.3       | None        | 指定バージョンを利用可能 | 指定バージョンを利用可能            |
|  D  | 1.17.3       | 2.0.1       | 指定バージョンを利用可能 | 指定バージョンを利用可能            |

### Bundler 2.0.1 で作った Gemfile.lock

|  #  | default gems | gem install | `ruby -rbundler/setup`                                  | `bundle exec ruby -rbundler/setup`  |
| --- | ------------ | ----------- | ------------------------------------------------------- | ----------------------------------- |
|  E  | 1.17.2       | None        | `bundle install --deployment` 不可                      | `bundle install --deployment` 不可  |
|  F  | 1.17.2       | 2.0.1       | `You must use Bundler 2 or greater with this lockfile.` | 指定バージョンを利用可能            |
|  G  | 1.17.3       | 2.0.1       | 指定バージョンを利用可能                                | 指定バージョンを利用可能            |


補足(1):  Bundler 1.17.3 で作った Gemfile.lock
----

```
$ bundle -v
Bundler version 1.17.3
$ bundle init
$ echo 'gem "csv", "3.0.3"' >> Gemfile
$ bundle install
$ cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    csv (3.0.3)

PLATFORMS
  ruby

DEPENDENCIES
  csv (= 3.0.3)

BUNDLED WITH
   1.17.3
```

### A

```
$ docker run --rm -it -v(pwd):/mnt centos:7 bash
# yum install -y https://github.com/feedforce/ruby-rpm/releases/download/2.6.1/ruby-2.6.1-1.el7.centos.x86_64.rpm
# ruby -v
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-linux]
# gem -v
3.0.1
# bundle -v
Bundler version 1.17.2
# gem list bundler

*** LOCAL GEMS ***

bundler (default: 1.17.2)
# cp /mnt/Gemfile* .
# bundle install --deployment
# ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
# bundle exec ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.4
```

### B

```
$ docker run --rm -it -v(pwd):/mnt centos:7 bash
# yum install -y https://github.com/feedforce/ruby-rpm/releases/download/2.6.1/ruby-2.6.1-1.el7.centos.x86_64.rpm
# gem install bundler -v 1.17.3 --no-document
# bundle -v
Bundler version 1.17.3
# gem list bundler

*** LOCAL GEMS ***

bundler (1.17.3, default: 1.17.2)
# cp /mnt/Gemfile* .
# bundle install --deployment
# ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
Traceback (most recent call last):
        9: from /usr/lib64/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        8: from /usr/lib64/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        7: from /usr/lib64/ruby/2.6.0/bundler/setup.rb:10:in `<top (required)>'
        6: from /usr/lib64/ruby/gems/2.6.0/gems/bundler-1.17.3/lib/bundler.rb:107:in `setup'
        5: from /usr/lib64/ruby/gems/2.6.0/gems/bundler-1.17.3/lib/bundler/runtime.rb:26:in `setup'
        4: from /usr/lib64/ruby/gems/2.6.0/gems/bundler-1.17.3/lib/bundler/runtime.rb:26:in `map'
        3: from /usr/lib64/ruby/2.6.0/forwardable.rb:230:in `each'
        2: from /usr/lib64/ruby/2.6.0/forwardable.rb:230:in `each'
        1: from /usr/lib64/ruby/gems/2.6.0/gems/bundler-1.17.3/lib/bundler/runtime.rb:31:in `block in setup'
/usr/lib64/ruby/gems/2.6.0/gems/bundler-1.17.3/lib/bundler/runtime.rb:319:in `check_for_activated_spec!': You have already activated bundler 1.17.3, but your Gemfile requires bundler 1.17.2. Prepending `bundle exec` to your command may solve this. (Gem::LoadError)
# bundle exec ruby -rbundler/setup -e 'puts Bundler::VERSION'
1.17.3
# bundle exec ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
```

### C

```
$ docker run --rm -it -v(pwd):/mnt centos:7 bash
# yum install -y https://github.com/feedforce/ruby-rpm/releases/download/2.6.1/ruby-2.6.1-1.el7.centos.x86_64.rpm
# gem update --system
# bundle -v
Bundler version 1.17.3
# gem list bundler

*** LOCAL GEMS ***

bundler (default: 1.17.3)
# cp /mnt/Gemfile* .
# bundle install --deployment
# ruby -rbundler/setup -e 'puts Bundler::VERSION'
1.17.3
# ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
# bundle exec ruby -rbundler/setup -e 'puts Bundler::VERSION'
1.17.3
# bundle exec ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
```

### D

```
$ docker run --rm -it -v(pwd):/mnt centos:7 bash
# yum install -y https://github.com/feedforce/ruby-rpm/releases/download/2.6.1/ruby-2.6.1-1.el7.centos.x86_64.rpm
# gem install bundler --no-document
# gem update --system
# gem -v
3.0.2
# bundle -v
Bundler version 2.0.1
# gem list bundler

*** LOCAL GEMS ***

bundler (2.0.1, default: 1.17.3)
# cp /mnt/Gemfile* .
# bundle install --deployment
# ruby -rbundler/setup -e 'puts Bundler::VERSION'
1.17.3
# ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
# bundle exec ruby -rbundler/setup -e 'puts Bundler::VERSION'
1.17.3
# bundle exec ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
```


補足: Bundler 2.0.1 で作った Gemfile.lock
----

```
$ bundle -v
Bundler version 2.0.1
$ bundle init
$ echo 'gem "csv", "3.0.3"' >> Gemfile
$ bundle install
$ cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    csv (3.0.3)

PLATFORMS
  ruby

DEPENDENCIES
  csv (= 3.0.3)

BUNDLED WITH
   2.0.1
```

### E

```
$ docker run --rm -it -v(pwd):/mnt centos:7 bash
# yum install -y https://github.com/feedforce/ruby-rpm/releases/download/2.6.1/ruby-2.6.1-1.el7.centos.x86_64.rpm
# ruby -v
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-linux]
# gem -v
3.0.1
# bundle -v
Bundler version 1.17.2
# gem list bundler

*** LOCAL GEMS ***

bundler (default: 1.17.2)
# cp /mnt/Gemfile* .
# bundle install --deployment
Traceback (most recent call last):
        2: from /usr/bin/bundle:23:in `<main>'
        1: from /usr/lib64/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
/usr/lib64/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.0.1) required by your /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.0.1`
```

### F

```
$ docker run --rm -it -v(pwd):/mnt centos:7 bash
# yum install -y https://github.com/feedforce/ruby-rpm/releases/download/2.6.1/ruby-2.6.1-1.el7.centos.x86_64.rpm
# gem install bundler -v 2.0.1 --no-document
# bundle -v
Bundler version 2.0.1
# gem list bundler

*** LOCAL GEMS ***

bundler (2.0.1, default: 1.17.2)
# cp /mnt/Gemfile* .
# bundle install --deployment
# ruby -rbundler/setup -e 'puts Bundler::VERSION'
You must use Bundler 2 or greater with this lockfile.
# ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
You must use Bundler 2 or greater with this lockfile.
# bundle exec ruby -rbundler/setup -e 'puts Bundler::VERSION'
2.0.1
# bundle exec ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
```

### G

```
$ docker run --rm -it -v(pwd):/mnt centos:7 bash
# yum install -y https://github.com/feedforce/ruby-rpm/releases/download/2.6.1/ruby-2.6.1-1.el7.centos.x86_64.rpm
# gem install bundler --no-document
# gem update --system
# gem -v
3.0.2
# bundle -v
Bundler version 2.0.1
# gem list bundler

*** LOCAL GEMS ***

bundler (2.0.1, default: 1.17.3)
# cp /mnt/Gemfile* .
# bundle install --deployment
# ruby -rbundler/setup -e 'puts Bundler::VERSION'
2.0.1
# ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
# bundle exec ruby -rbundler/setup -e 'puts Bundler::VERSION'
2.0.1
# bundle exec ruby -rbundler/setup -rcsv -e 'puts CSV::VERSION'
3.0.3
```


おまけ
----

`gem install bundler` と `gem update --system` の実行順序によって、実行ファイル(ラッパー) `bundle` が conflict する様です。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ruby 2.6.1 に対して、 bundler 2.0.1 → gem update --system (=rubygems 3.0.2) の順で入れると問題ないけど、逆にやると bundler 2.0.1 のインストール時にラッパースクリプトが conflict する。</p>&mdash; さく (@sakuro) <a href="https://twitter.com/sakuro/status/1093008426370662400?ref_src=twsrc%5Etfw">2019年2月6日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


### bundler → rubygems

特に警告が出ることなく、また `/usr/bin/bundle` の内容が変わることはなかった。

```
# gem install bundler --no-document
...
# gem update --system
...
```

### rubygems → bundler

`gem update --system` によって `/usr/bin/bundle` が更新され、 `gem install bundler` で上書きすると Ruby 同梱同等の内容に戻った。

```
# gem update --system
...
# gem install bundler --no-document
...
bundler's executable "bundle" conflicts with /usr/bin/bundle
Overwrite the executable? [yN]  y
...
```

### RPM でインストールした `/usr/bin/bundle`

```ruby
#!/usr/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'bundler' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0.a"

str = ARGV.first
if str
  str = str.b[/\A_(.*)_\z/, 1]
  if str and Gem::Version.correct?(str)
    version = str
    ARGV.shift
  end
end

if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('bundler', 'bundle', version)
else
gem "bundler", version
load Gem.bin_path("bundler", "bundle", version)
end
```

### `gem update --system` で暗黙的に上書きされた `/usr/bin/bundle`

Bundler の [`exe/bundle`](https://github.com/bundler/bundler/blob/9cbc7dd8f77bcc12fd7e4265ebbd948f0e19b7f7/exe/bundle) とほぼ同等。違いは shebang のみ。

```ruby
#!/usr/bin/ruby
# frozen_string_literal: true

# Exit cleanly from an early interrupt
Signal.trap("INT") do
  Bundler.ui.debug("\n#{caller.join("\n")}") if defined?(Bundler)
  exit 1
end

require "bundler"
# Check if an older version of bundler is installed
$LOAD_PATH.each do |path|
  next unless path =~ %r{/bundler-0\.(\d+)} && $1.to_i < 9
  err = String.new
  err << "Looks like you have a version of bundler that's older than 0.9.\n"
  err << "Please remove your old versions.\n"
  err << "An easy way to do this is by running `gem cleanup bundler`."
  abort(err)
end

require "bundler/friendly_errors"
Bundler.with_friendly_errors do
  require "bundler/cli"

  # Allow any command to use --help flag to show help for that command
  help_flags = %w[--help -h]
  help_flag_used = ARGV.any? {|a| help_flags.include? a }
  args = help_flag_used ? Bundler::CLI.reformatted_help_args(ARGV) : ARGV

  Bundler::CLI.start(args, :debug => true)
end
```

### `gem install bundler` で上書きした `/usr/bin/bundle`

```ruby
#!/usr/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'bundler' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0.a"

str = ARGV.first
if str
  str = str.b[/\A_(.*)_\z/, 1]
  if str and Gem::Version.correct?(str)
    version = str
    ARGV.shift
  end
end

if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('bundler', 'bundle', version)
else
gem "bundler", version
load Gem.bin_path("bundler", "bundle", version)
end
```

### `rbenv install 2.6.1` で作られた `versions/2.6.1/bin/bundle`

```ruby
#!/home/koshigoe/.rbenv/versions/2.6.1/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'bundler' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0.a"

str = ARGV.first
if str
  str = str.b[/\A_(.*)_\z/, 1]
  if str and Gem::Version.correct?(str)
    version = str
    ARGV.shift
  end
end

if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('bundler', 'bundle', version)
else
gem "bundler", version
load Gem.bin_path("bundler", "bundle", version)
end
```