30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

bundler を使った RubyGems の管理について

環境。
OSはmacOS High Sierra(10.13.5)
Ruby自体のバージョン管理はrbenv(1.1.1)で行う。
rbenvについては以下を参照。
numb86-tech.hatenablog.com

グローバルインストール

RubyではRubyGemsという仕組みを使ってパッケージ管理を行う。扱われる一つ一つのパッケージをgemと呼ぶ。
バージョン1.9以降のRubyにはこの仕組みが標準で入っているため、特に何もしなくてもgemコマンドを使える。

$ gem -v
2.7.6

$ gem install パッケージ名でインストールできる。

インストール先は$ gem environmentで確認できる。
色々と情報が出てくるが、INSTALLATION DIRECTORYがインストール場所。
自分の環境だと/Users/$USER/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0と表示された。
このディレクトリ内のgemsのなかに、インストールしたgemが入っている。

$ ls /Users/$USER/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0
build_info  cache       doc     extensions  gems        specifications
$ ls /Users/$USER/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems
(インストール済みのgemがここに表示される)

$ gem listでも一覧を確認できる。
先程のディレクトリに入っていないgemも表示されているが、これは最初からインストールされているgemである。
bigdecimal (default: 1.3.4)のようにdefaultと書かれているものが、これに該当する。

この方法でインストールしたgemは、npmでいうところのグローバルインストールになる。
つまり、どのディレクトリやプロジェクトからでも使うことが出来る。

$ gem listすると*** LOCAL GEMS ***と表示されるが、これはグローバルの対義語ではなく、*** REMOTE GEMS ***の対となるという意味での「ローカル」だと思われる。

Rails のインストール

例として、Railsをグローバルインストールしてみる。

最初に、インストールされていないことを確認。

$ rails -v
Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.

インストール。

$ gem install rails

インストールされているか確認してみる。

$ ls /Users/$USER/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems | grep rails
rails-5.2.0
rails-dom-testing-2.0.3
rails-html-sanitizer-1.0.4
sprockets-rails-3.2.1
$ gem list | grep rails
rails (5.2.0)
rails-dom-testing (2.0.3)
rails-html-sanitizer (1.0.4)
sprockets-rails (3.2.1)

インストールできていることを確認できた。
早速Railsを使ってみる。

$ rails -v
Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.

実行できない。実は、頭にrbenv execを付ける必要がある。

$ rbenv exec rails -v
Rails 5.2.0

rbenv execの正確な意味は分かっていないが、「rbenvで管理しているRuby」を明示するような意味らしい。
gemのインストール先のパスが/Users/$USER/.rbenv/以下であることから分かるように、インストールしたgemはrbenvに紐付いている。
そのため、rbenv execが必要なのだろう。

しかし、いちいちrbenv execを付けるのは面倒である。
そのため、付けなくても動作するように設定する。
具体的には、$ rbenv rehashを使えばよい。

$ rbenv rehash
$ rails -v
Rails 5.2.0

こうすると、どのディレクトリからでもrailsコマンドを使える。
つまり、グローバルインストールされた状態になる。

なお、前述のようにrbenvに紐付いているため、Rubyのバージョンを変えると利用することは出来ない。
他のバージョンのRubyでも利用したい場合は、そのバージョンに切り替えている状態で改めてgem installする。

$ rbenv local 2.5.0
$ rails -v
rbenv: rails: command not found

The `rails' command exists in these Ruby versions:
  2.5.1

$ rbenv local --unset
$ rails -v
Rails 5.2.0

bundler

グローバルインストールではなくディレクトリ毎にgemをインストールしたい場合は、bundlerというツールを使う。
これ自身もgemの一種であり、これをグローバルインストールしておくとbundleコマンドを使える。
ツールの名前とコマンド名が異なるので注意。

まずはインストール。

$ gem install bundler
$ rbenv rehash
$ bundle -v
Bundler version 1.16.2

bundlerを使ってgemをインストールするにはまず、管理したいディレクトリに移動し、$ bundle initを実行する。
そうするとGemfileというファイルが作られる。

$ bundle init
Writing new Gemfile to /Users/$USER/Documents/tech/rbenv-blog/v3/Gemfile
$ ls
Gemfile
$ cat Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"

このGemfileに、インストールしたいgemを書いていく。
今回はv3.5.0sassをインストールしてみる。

--- a/Gemfile
+++ b/Gemfile
@@ -5,3 +5,4 @@ source "https://rubygems.org"
 git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
 
 # gem "rails"
+gem "sass", "3.5.0"

この状態でインストールするのだが、必ずpathオプションを指定する。オプションの値は慣例的にvendor/bundleが使われるようだ。

$ bundle install --path=vendor/bundle

こうすると、指定したパス以下にインストールされる。

$ ls vendor/bundle/ruby/2.5.0/gems
ffi-1.9.25      rb-fsevent-0.10.3   rb-inotify-0.9.10   sass-3.5.0      sass-listen-3.0.7

$ bundle listで、インストールされているgemを確認できる。

$ bundle list
Gems included by the bundle:
  * bundler (1.16.2)
  * ffi (1.9.25)
  * rb-fsevent (0.10.3)
  * rb-inotify (0.9.10)
  * sass (3.5.0)
  * sass-listen (3.0.7)

早速sassを使おうとするが、出来ない。
bundleでインストールしたgemをコマンドラインで使う場合、頭にbundle execを付ける必要がある。

$ sass -v
-bash: sass: command not found
$ bundle exec sass -v
Sass 3.5.0 (Bleeding Edge)

bundle execを省略する方法もあるのだが、それは後述する。

bundle installの結果作られたものとして他に、Gemfile.lock.bundle/configがある。

Gemfile.lockには、今回インストールしたsassの他に、sassが依存関係にあるgemについてバージョン情報と共に書かれてある。
このファイルがあることで、どの環境で$ bundle installを実行しても同じgemがインストールされ、冪等性が保たれる。
フロントエンドでいうところのpackage-lock.jsonyarn.lockのようなものだと思う。

.bundle/configの中には、--pathで指定した値が記録されている。

$ cat .bundle/config
---
BUNDLE_PATH: "vendor/bundle"

そしてこの情報がある場合、インストールの際にパスを指定する必要がなくなる。
例えば、Gemfileの末尾にgem "foreman"と追記して$ bundle installを実行してみる。

$ ls vendor/bundle/ruby/2.5.0/gems
ffi-1.9.25      rb-fsevent-0.10.3   sass-3.5.0      thor-0.19.4
foreman-0.85.0      rb-inotify-0.9.10   sass-listen-3.0.7

明示的に指定しなくてもvendor/bundleにインストールされているのを確認できる。

では、インストール時の指定も.bundle/configでの指定も無い場合は、どうなるのか。
新しくディレクトリを作り、そこで改めて$ bundle initから作業してみる。

$ bundle init
Writing new Gemfile to /Users/$USER/Documents/tech/rbenv-blog/v4/Gemfile
$ echo 'gem "sass", "3.5.6"' >> Gemfile
$ bundle install
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using bundler 1.16.2
Using ffi 1.9.25
Using rb-fsevent 0.10.3
Using rb-inotify 0.9.10
Using sass-listen 4.0.0
Fetching sass 3.5.6
Installing sass 3.5.6
Bundle complete! 1 Gemfile dependency, 6 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

こうすると、そのディレクトリではなく、/Users/$USER/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0にインストールされる。
つまり、グローバルインストールになる。

$ ls -a
.       ..      Gemfile     Gemfile.lock
$ ls /Users/$USER/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems | grep sass
sass-3.5.6
$ gem list | grep sass
sass (3.5.6)
$ rbenv rehash
$ sass -v
Ruby Sass 3.5.6

binstubs

上述のように、vendor/bundleにインストールしたgemを使うためには、bundle execをつける必要がある。
これを避けるには、binstubsという、rbenvプラグインを使う。

まず、~/.rbenv/pluginsに移動。無ければ作る。
そして以下のリポジトリをクローンすればよい。
https://github.com/ianheggie/rbenv-binstubs

$ cd ~/.rbenv/plugins
$ git clone https://github.com/ianheggie/rbenv-binstubs.git

あとはbundle installの際に--binstubs=vendor/binとして、このプラグインを使えばよい。
新しいディレクトリを作って試してみる。

$ bundle init
Writing new Gemfile to /Users/$USER/Documents/tech/rbenv-blog/v5/Gemfile
$ echo 'gem "sass", "3.5.1"' >> Gemfile
$ bundle install --path=vendor/bundle --binstubs=vendor/bin
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using bundler 1.16.2
Fetching ffi 1.9.25
Installing ffi 1.9.25 with native extensions
Fetching rb-fsevent 0.10.3
Installing rb-fsevent 0.10.3
Fetching rb-inotify 0.9.10
Installing rb-inotify 0.9.10
Fetching sass-listen 4.0.0
Installing sass-listen 4.0.0
Fetching sass 3.5.1
Installing sass 3.5.1
Bundle complete! 1 Gemfile dependency, 6 gems now installed.
Bundled gems are installed into `./vendor/bundle`
$ sass -v
Sass 3.5.1 (Bleeding Edge)

このマシンには3.5.6がグローバルインストールされているが、それではなくこのディレクトリにインストールした3.5.1を使用していることが分かる。

sassがインストールされていないディレクトリで実行すると、3.5.6を使う。

$ sass -v
Ruby Sass 3.5.6

参考資料