BUNDLED WITH で Gemfile.lock が更新されてしまう件

### `BUNDLED WITH` って何？

`bundler 1.10.0` 以降で作成された `Gemfile.lock` では、`BUNDLED WITH` というセクションがあって、使用された `bundler` のバージョンがトラックされるようになっている [[3485]](https://github.com/bundler/bundler/pull/3485) 。

例えば `bundler 1.9.0` で作成された `Gemfile.lock` のあるプロジェクト上で、 最新の `bundler 1.10.3` を使用して `bundle install --path vendor/bundle` すると、 `Gemfile.lock` には以下の差分ができる。

```diff
+
+BUNDLED WITH
+   1.10.3
```

これをもう一度 `1.9.0` で `bundle install` すると、上の文字はきれいさっぱり消える。

### `BUNDLED WITH` の何が困るか

多人数で開発するときには、この変更は微妙に邪魔で、意図しない `Gemfile.lock` 変更が出てしまうことで `Gemfile.lock` にコンフリクトが出たり、本番環境の `bundler` とのバージョン違いが顕在化してデプロイが失敗したり、あるいはローカルで `bundle install` したせいで `git pull` ができなかったりしそうに見える。まあ `git checkout -- Gemfile.lock` すればいいけど、逐一やらねばならなくなってめんどくさい。

で、この変更が原因で、本家の issue がプチ炎上することになってしまったのだけど、その本家の issue を見ると今回の変更の意味がよくわかった。

## 本家が伝えたかったこと

https://github.com/bundler/bundler/issues/3697#issuecomment-108299463

  - まず、`bundler`開発者としては、ユーザがどのバージョンで `bundle install` したのかという情報は、大昔からトラックしたかったものだった。今回、多大な努力のうえでようやく実装することができた
  - 本家としては、以下のようなユースケースのフローを想定している
    - `bundle install` すると、`Gemfile.lock` 内の `BUNDLED WITH` を更新する（ただし、より新しい場合のみ）
    - 更新したら、他の `gem` をアップデートした場合とまったく同じように、 `Gemfile.lock` の更新をコミットする
    - `BUNDLED WITH` よりも古いバージョンの `bundler` を使っている人は、`BUNDLED WITH`の記述を更新しない。が、`bundler` を更新するように注意書きが出る
  - …というフローを想定していたが、現在は移行期のため以下のような問題が出ている
    - `1.9.9` 以前の `bundler` では `Gemfile.lock` 内の `BUNDLED WITH` を削除してしまう
    - だから `1.9.9` 以前の人と `1.10.0` 以後の人とで `git` のやり取りをしていると、しょっちゅうファイルの変更が衝突してしまう
    - この問題は、全員が `1.10.0` 以降になれば起こらなくなる
  - 今回の変更はワークフローにこそ影響はあるかもしれないものの、 `bundler` それ自体としては互換性を失う変更をしていないし、SemVerにも反していない
  - bundler開発者はめっちゃ頑張って開発者のためになるよう作り込んでるわけで、今回それで迷惑かけちゃってごめんね？
  - `1.9` 系のアップデートは出さない。出したところで `1.10.X` 以降にアップグレードしたほうが根本的解決になるから、`1.9`系を利用し続ける意味はない
  - これでもなお問題となるものがあるようであれば、きちんとした形で教えてほしい

おこでした。

あ、ちなみに、『`BUNDLED WITH` で指定されたバージョンの `bundler` を起動する』機能は実装される可能性があるらしい。できたらめっちゃ素敵。


## @suu_g の推奨対応

`bundle install` に `--frozen` オプションをつけよう。役に立つわりに長年ほとんど注目されることもなかった不遇のオプション[^1]だが、ここに来てようやく報われるときが来たようだ。レリゴーだ。

[^1]: 今回、本家で言及すらされていない…不遇すぎる…

`bundle install` に `--frozen` オプションをつけると、その手元では `Gemfile.lock` を変更することはなくなる。このオプションは `.bundle/config` に保存されるので最初の一回だけ指定すればいい。
凍結されたディレクトリでは、`Gemfile.lock` に変更は行われないし、行われるような変更を許容しなくなるので、**確実に `Gemfile.lock` を適用したい**というとき（デプロイ時）には非常に便利。
この状態で `Gemfile` を変更しようとすると、「フリーズさせているので `Gemfile.lock` が更新できません」と言ってくるようになる。開発環境では `Gemfile` を変更したときには凍結を解除しなければならない、のだけど、その手段はちゃんと準備されていないのでちょっと不便。

```
You are trying to install in deployment mode after changing
your Gemfile. Run `bundle install` elsewhere and add the
updated Gemfile.lock to version control.

If this is a development machine, remove the Gemfile freeze
by running `bundle install --no-deployment`.
```

上は `Gemfile` に変更を加えて `bundle install` を試したときの公式のメッセージ。 `bundle install --no-deployment` でいいと書いているが、これは**大嘘**。素直に従ってこれを実行すると、以前に設定してあった `BUNDLE_PATH` などのオプションをガン無視して system ruby 上に gem を install してしまう。危険。

だからこの場合は、`.bundle/config` に書かれた設定を手動で削除するのがベストな方法だ。

```yaml
---
BUNDLE_BIN: ".bundle/bin"
BUNDLE_FROZEN: '1'    ## この行を消す
BUNDLE_PATH: ".bundle/gems"
BUNDLE_DISABLE_SHARED_GEMS: '1'
```

コマンドで設定を削除することもできる。こっちの方が簡単か。

```shell-session
$ bundle config --delete frozen
```

それと、 `bundler` のバージョンを可能な限り `1.10` 以降に上げよう。現在問題が起きているのは `1.9` 以前と `1.10` とが混在しているからなので、それをすべて上げてしまえば問題は起こらなくなる。
`1.10` で追加された `BUNDLED WITH` だけど、これは `1.9` 以前を通すと消えてしまう。それで編集コンフリクトが多発し、問題が大きく見えていたってだけのことだった。…というのは先述の通り。

さて、それでは開発者や本番環境に対して適用するべき具体的なものは何か、以下で説明していく。

### 各開発者のするべき対応

 - `bundle install` 時には**とにかく `gem update` をする**
 - 他の人のアップデートを見たら自分もアップデートする
 - `Gemfile.lock` の `BUNDLED WITH` 行でコンフリクトしたら `git checkout -- Gemfile.lock; gem update bundler` する
 - `Gemfile.lock` のコミットには気を使う。`git add .` とかせず、コミット単位を分けておく
 - `Gemfile.lock` は、内容をアップデートする意思のあるとき以外は極力変更しない。変更する気のないときは、手元環境でも `bundle install` に `--frozen` オプションをつけておく
 - `frozen` させた `Gemfile.lock` を変更するときは手動。 `.bundle/config` 内にある `BUNDLE_FROZEN` の行を削除する

みんながこれやれば平和な開発チームになる。

### 本番環境でとるべき対応

 - `bundle install` に `--frozen` オプションを追加する

これに限る。無駄な更新をデプロイ先で行わせないこと、大事。
`bundler` の[オプション一覧](http://bundler.io/bundle_install.html)を見ると `--deployment` も気になるかもしれない。これは `--frozen` と `--path` とを組み合わせたオプションで、つまり以下のような挙動をしている：

 - `vendor/bundle` 以下に gem をインストールする（`--path=vendor/bundle`）
 - `Gemfile.lock` を変更しないようになる（`--frozen`）

初回ならこれを使っていくので問題ないが、 `--path` 指定を上書きされてしまう点には注意が必要。[^2]

[^2]: 2015/06/21 記述修正。 `--no-deployment` の挙動がちょっと衝撃的だったので併せて勘違いしてしまいました。`vendor/bundle` はドキュメント通りの挙動ですね。すみません。（編集内容ご覧になる場合は履歴からどうぞ）

## 個人的な感想

`BUNDLED WITH`に書かれたバージョンは増えこそすれ巻き戻ることはない仕様だそうなので、世の中の bundler がすべて `1.10.0` 以降になれば状況はまた変わるでしょう。いまは狭間のタイミングだから混乱が起きているわけで。
それに、デプロイ先に `--frozen` を使うようにすれば、現状これはほとんど問題にならなくなる。みんな手元の bundler をアップデートしたうえでデプロイ先では `--frozen` しよう。`Gemfile.lock` should never bother you anymore.

あとソフトウェア製作者への応援大事だし、自分の期待通りに動かなくなったからといってOSS作者に不満や修正要求をぶつけちゃダメ。自分で直してプルリク作ろう。まじ本家issue読んでると作者たちかわいそうになる。

そんな感じ。誰か私も応援してください。わぁいストック。

----

###### 著者紹介
`@suu_g`。バンドラーの記事をたまに書く人。まれに無線とかする。