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

参考資料

rbenv を使った Ruby のバージョン管理について

最初に環境を書いておくと、OSはmacOS High Sierra(10.13.5)
homebrewインストール済み。

RubyRailsを触り始めたのだが、初心者なのでとにかく躓く。
bundle installでエラー、rails newでエラー、rails sでエラー、foreman startでエラー……。
その都度調べて解決していくが、とにかく非効率。しかも、ネットの記事を見よう見真似でやっているだけで、何が起きているかよく分かっていない。

遠回りに思えても、ちゃんとRubyやパッケージ管理の仕組みを勉強しないとダメだなと感じた。
そもそもUnixの仕組み(ディレクトリ構成やパスやコマンドなど)をろくに分かっていないのが問題だが、それはまた別の話。

今回は、Rubyのバージョン管理について書いていく。
上述のエラーも、こういった環境構築が上手くいっていないことに起因するものが多いようだった。

rbenv

Macであれば、Rubyは最初から入っていることが多いはず。$ ruby -vでバージョンが表示されれば、Rubyが使える。
だが実際には、バージョン毎に仕様が違うため、それをそのまま使うのではなくRuby自体のバージョンを管理して開発することになるだろう。

今回はrbenvというツールを使ってRubyのバージョン管理を行う。

セットアップ

homebrewでインストールし、~/.bash_profileeval "$(rbenv init -)"という記述を追加。
$ source ~/.bash_profileで設定を反映させれば、完了。
ruby-buildも一緒にインストールする必要がある。

$ brew install rbenv ruby-build
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

$ rbenv --versionでバージョンが表示されれば成功である。ちなみにこれはRubyのバージョンではなくrbenvのバージョンなので注意。

ちなみに、インストールされたrbenv/usr/local/Cellar/rbenv/に入っている。
エラーメッセージなどでこのパスが出てきたときは、rbenvが関係しているかもしれない。

使い方

インストール可能なRubyの一覧は、$ rbenv install --listで確認できる。
大量に出てくるので、grepを使ったほうがいいかもしれない。

$ rbenv install --list | grep 2.5
  2.2.5
  2.5.0-dev
  2.5.0-preview1
  2.5.0-rc1
  2.5.0
  2.5.1
  rbx-2.2.5
  rbx-2.5.0
  rbx-2.5.1
  rbx-2.5.2
  rbx-2.5.3
  rbx-2.5.4
  rbx-2.5.5
  rbx-2.5.6
  rbx-2.5.7
  rbx-2.5.8

$ rbenv install バージョン名でインストールできる。

$ rbenv install 2.5.1
ruby-build: use openssl from homebrew
Downloading ruby-2.5.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.1.tar.bz2
Installing ruby-2.5.1...
ruby-build: use readline from homebrew
Installed ruby-2.5.1 to /Users/$USER/.rbenv/versions/2.5.1

$ rbenv versionsとしたときにそのバージョンが表示されれば、インストール成功。
だがこの時点では、マシンにインストールしたというだけで、実際にそのバージョンが使用されるわけではない。

使用するバージョンを指定するには、$ rbenv global バージョン名とすればよい。
この状態で$ rbenv versionsとすると、指定したバージョンの前に*がついているはず。

$ rbenv versions
  system
* 2.5.1 (set by /Users/$USER/.rbenv/version)

が、これはあくまでもrbenvでの指定に過ぎない。設定が上手くいっていないこともあるので、$ ruby -vで確認したほうがよい。

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]

正しくインストールされていることを確認できた。

ちなみに、Rubyのプログラムのなかでバージョンを取得したい場合は、RUBY_VERSIONを使う。

$ echo 'print RUBY_VERSION' > version.rb && ruby version.rb && rm version.rb
2.5.1

バージョンの適用

rbenvでは、ディレクトリ毎にRubyのバージョンを指定することが出来る。
$ rbenv global バージョン名で指定したのはマシン全体の設定。ディレクトリ毎の設定は$ rbenv local バージョン名で出来る。

予め$ rbenv install 2.5.02.5.0をインストールしておき、$ rbenv local 2.5.0としてみる。

$ rbenv local 2.5.0
$ rbenv versions
  system
* 2.5.0 (set by $PWD/.ruby-version)
  2.5.1
$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]

2.5.0になっている。
と同時に、.ruby-versionというファイルが作られており、そこには2.5.0と書かれているのを確認できる。

$ ls -a
.       ..      .ruby-version
$ cat .ruby-version
2.5.0

実はrbenvでは、カレントディレクトリに.ruby-versionがないか探し、見つかれば、そこに書かれているバージョンを使う仕組みになっている。

見つからなかった場合、その親ディレクトリにないか確認する。これをルートディレクトリに辿り着くまで繰り返し、見つかった場合はその.ruby-versionに書かれているバージョンを適用する。

そして最後まで.ruby-versionが見つからなかった場合は/Users/$USER/.rbenv/versionに書かれているバージョンを適用する。
そしてそこには、$ rbenv globalで指定したバージョンが書かれている。

$ cat /Users/$USER/.rbenv/version
2.5.1

つまり$ rbenv loaclとは、指定したバージョンが書かれた.ruby-versionをカレントディレクトリに作成するコマンドであり、$ rbenv globalは指定したバージョンを/Users/$USER/.rbenv/versionに書き込むコマンドであると言える。

RBENV_VERSION

実はlocalでの指定よりもさらに優先される設定があり、それが環境変数RBENV_VERSIONである。
デフォルトは空で、$ rbenv shell バージョン名で指定できる。空に戻すには$ rbenv shell --unset

$ echo $RBENV_VERSION

$ rbenv shell 2.5.0
$ echo $RBENV_VERSION
2.5.0
$ rbenv shell --unset
$ echo $RBENV_VERSION

RBENV_VERSIONが指定されていると、その値が最優先される。

$ rbenv versions
  system
* 2.5.0 (set by RBENV_VERSION environment variable)
  2.5.1

だがこの値が設定されていると常にそのバージョンが採用されてしまい、ディレクトリ毎に設定することが出来なくなる。
基本的にはデフォルト設定である空のままでいいように思う。

参考資料