bundlerでgemバージョンを束ねる
Rails1.1.6の環境でどうにかscaffoldはできるようになった。その時の実行環境は以下の状態だった。
$ rbenv version 1.8.7-p375 (set by /Users/zari/.rbenv/version) $ gem list *** LOCAL GEMS *** actionmailer (1.2.5) actionpack (1.12.5) actionwebservice (1.1.6) activerecord (1.14.4) activesupport (1.3.1) rails (1.1.6) rake (0.7.1) sqlite3 (1.3.10)
- この実行環境というのは非常にデリケートである。
- 例えば、今の時代にRails-1.1.6をgemに従うままにインストールすると、最新のRake-10.3.2がインストールされる。
- しかし、Rake-10.3.2ではRails-1.1.6はうまく動作しない。
- rake db:migrateでエラーが出てしまう...。
- このエラーに散々悩んで、ようやくrakeのバージョンを下げれば良いことに気付いた。
- そこで、わざわざgem uninstall rakeで一旦rakeを削除して、その後、Rake-0.7.1を再インストールしている。面倒くさい。
- しかも、動かしたい古いRails環境のバージョンは1.1.6だけではない。
- バージョン1.2.6や2.0.5などのRails環境も動かしたいのだ。
- それらのバージョンをインストールした瞬間に、Rails-1.1.6のプロジェクトは正常に動かなくなってしまう。
- gemコマンドの操作は手軽さが売りなのに、これでは気楽にインストールできない。
- gem updateなんて、もってのほかである。
- プロジェクトはgemのバージョンと密接な関係があるはずなのに、インストールされた最新バージョンしか使えない所に問題がある。
- 必要なgemとバージョンを自由に選択して、実行環境を素早く作り上げる仕組みが必要なのだ。
それを実現してくれるのがbundlerなのだった。
-
-
- そう、以前gemを開発する時に使った(bundle gem)、あのbundlerなのだ。
- bundle gemは何をしてくれるのか? - ザリガニが見ていた...。
- gemプロジェクトの雛形生成と管理はbundlerの機能の一部であり、本来の目的はgemとそのバージョンを取りまとめる役割を担っていたのだ。
-
基本
bundlerをインストール
- bundlerもまたgemなので、gemコマンドでインストールするのだ。
$ gem install bundle Fetching: bundler-1.7.4.gem (100%) Fetching: bundle-0.0.1.gem (100%) Successfully installed bundler-1.7.4 Successfully installed bundle-0.0.1 2 gems installed Installing ri documentation for bundler-1.7.4... Installing ri documentation for bundle-0.0.1... File not found: lib ERROR: While generating documentation for bundle-0.0.1 ... MESSAGE: exit ... RDOC args: --ri --op /Users/zari/.rbenv/versions/1.8.7-p375/lib/ruby/gems/1.8/doc/bundle-0.0.1/ri lib --title bundle-0.0.1 Documentation --quiet
- ドキュメントの生成でエラーが出ているけど、気にしないことにした。
- Rubyのバージョンが古いためのエラーか?
gemとバージョンの指定
- Gemfileを初期化する。
$ cd ~/Desktop/rails116 $ bundle init $ cat Gemfile # A sample Gemfile source "https://rubygems.org" # gem "rails"
- 作業ディレクトリ直下にGemfileが生成された。
- Gemfileに必要なgemとバージョンを書き込む。
$ cat <<EOS >> Gemfile gem "rails", "1.1.6" gem "rake", "0.7.1" gem "sqlite3" EOS
指定したgemバージョンをインストール
- Gemfileで指定したgemバージョンをインストールする。
$ bundle install
- installは省略可能なので、bundleのみでもOK。
$ bundle Fetching gem metadata from https://rubygems.org/........... Resolving dependencies... Installing rake 0.7.1 (was 10.3.2) Installing activesupport 1.3.1 (was 4.1.7) Installing actionpack 1.12.5 (was 4.1.7) Installing actionmailer 1.2.5 (was 4.1.7) Installing activerecord 1.14.4 (was 4.1.7) Installing actionwebservice 1.1.6 Installing rails 1.1.6 (was 4.1.7) Installing sqlite3 1.3.10 Using bundler 1.7.4 Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
- Gemfileで指定したgemバージョンとそれに依存するgemがインストールされた。
- bundle install済のgemバージョンのセットは、bundle listで確認できる。
$ bundle list Gems included by the bundle: * actionmailer (1.2.5) * actionpack (1.12.5) * actionwebservice (1.1.6) * activerecord (1.14.4) * activesupport (1.3.1) * bundler (1.7.4) * rails (1.1.6) * rake (0.7.1) * sqlite3 (1.3.10)
- と同時に、このリストはbundlerの管理下で利用されるgemバージョンでもある。
- たとえRails-4.1.7やRake-10.3.2が追加インストールされたとしても、
- bundlerの管理下で実行する限り、上記gemの利用が保証されるのだ。
- ちなみに、bundle install(オプション指定無し)は、gemデフォルトの場所にインストールされる。
$ gem env home /Users/zari/.rbenv/versions/1.8.7-p374/lib/ruby/gems/1.8 $ bundle show rails /Users/zari/.rbenv/versions/1.8.7-p374/lib/ruby/gems/1.8/gems/rails-1.1.6 $ bundle show rake /Users/zari/.rbenv/versions/1.8.7-p374/lib/ruby/gems/1.8/gems/rake-0.7.1 $ bundle show sqlite3 /Users/zari/.rbenv/versions/1.8.7-p374/lib/ruby/gems/1.8/gems/sqlite3-1.3.10
bundlerの管理下で実行
- bundlerの管理下で実行するためには、bundle execを付加してコマンド実行する必要がある。
- rails-1.1.6のscaffoldなら、以下のようなコマンド操作になる。
-
-
- -d sqlite3 = データベースにsqlite3を指定
-
$ bundle exec rails todo -d sqlite3 $ cd todo # config/boot.rbを修正する $ bundle exec script/generate model todo # db/migrate/001_create_todos.rbを修正する $ bundle exec rake db:migrate $ bundle exec script/generate scaffold todo $ bundle exec script/server
- 上記コマンド操作の途中で修正するファイルは、以下のとおり。
# config/boot.rbを修正する @@ -25,7 +25,7 @@ rails_gem = Gem.cache.search('rails', "=#{version}").first if rails_gem - require_gem "rails", "=#{version}" + gem "rails", "=#{version}" require rails_gem.full_gem_path + '/lib/initializer' else STDERR.puts %(Cannot find gem for Rails =#{version}: @@ -35,7 +35,7 @@ exit 1 end else - require_gem "rails" + require "rails" require 'initializer' end end
# db/migrate/001_create_todos.rbを修正する class CreateTodos < ActiveRecord::Migration def self.up create_table :todos do |t| t.column :body, :string t.column :due, :date t.column :done, :boolean end end def self.down drop_table :todos end end
- ブラウザでアクセスしてみると...
bundlerとたった4行のGemfileで、確実に動作するRails-1.1.6環境を素早く作れた!
# Gemfile source "https://rubygems.org" gem "rails", "1.1.6" gem "rake", "0.7.1" gem "sqlite3"
デフォルトのgem
- 以上のように、利用するgemのバージョンまで指定できるbundlerは非常に便利。
- gemコマンド同様、常にbundleコマンドも使えるようにしておきたい。
- 今後は、gem installに代わって、bundle installを使いたいくらい。
- ところでbundlerもgemなので、最初にgem install bundlerが必ず必要になる。
- OSX標準のRubyしか使っていない時なら、gem install bundlerは1回で済む。問題ない。
- ところが、今やrbenvで必要なRuby環境をいくつでも、素早くインストール可能になった。
- 場合によっては、実験的にインストールとアンインストールを何度も繰り返すこともある。
- 今のままではその度にgem install bundlerを繰り返すことになってしまう...。面倒くさい。
その面倒を解決してくれるのが、rbenv-default-gemsなのだ。
- 前回、rbenvをインストールする時にrbenv-default-gemsもインストールしておいた。
- というより、rbenv-default-gemsの依存関係によって、rbenvもインストールしたのだった。
- よって、~/.rbenv/default-gemsファイルに、"bundler"と書き込んでおくだけで幸せになれる。
$ echo bundler >> ~/.rbenv/default-gems $ cat ~/.rbenv/default-gems bundler
- ~/.rbenv/default-gemsに書かれたgemは、rbenv installする時に、同時にインストールされる。
- 現在のRuby-1.8.7-p375を一旦削除して、試してみた。
$ rbenv uninstall 1.8.7-p375
rbenv: remove /Users/zari/.rbenv/versions/1.8.7-p375? y
- すかさず、再インストールしてみると...
$ rbenv install 1.8.7-p375 Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 Checking out http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8_7... Installing ruby-1.8.7-p375... Installed ruby-1.8.7-p375 to /Users/zari/.rbenv/versions/1.8.7-p375 Downloading rubygems-1.6.2.tgz...
- > http://dqw8nmjcqpjn7.cloudfront.net/cb5261818b931b5ea2cb54bc1d583c47823543fcf9682f0d6298849091c1cea7
- bundlerもインストールされた!
$ rbenv global 1.8.7-p375 $ gem list *** LOCAL GEMS *** bundler (1.7.4)
素晴らしい!
- ~/.rbenv/default-gemsには、bundlerに限らず、あらゆるgemを指定できる。
- Ruby標準になって欲しいと思うgemがあれば、それも書いておくと良さそう。
- 以前のRuby-1.8.7-p375環境も素早く復元できた。
$ cd ~/Desktop/rails116 $ bundle Fetching gem metadata from https://rubygems.org/.......... Installing rake 0.7.1 Installing activesupport 1.3.1 Installing actionpack 1.12.5 Installing actionmailer 1.2.5 Installing activerecord 1.14.4 Installing actionwebservice 1.1.6 Installing rails 1.1.6 Installing sqlite3 1.3.10 Using bundler 1.7.4 Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed. $ gem list *** LOCAL GEMS *** actionmailer (1.2.5) actionpack (1.12.5) actionwebservice (1.1.6) activerecord (1.14.4) activesupport (1.3.1) bundler (1.7.4) rails (1.1.6) rake (0.7.1) sqlite3 (1.3.10) $ cd todo $ bundle exec script/server ./script/../config/boot.rb:25:Warning: Gem::cache is deprecated and will be removed on or after August 2011. Use Gem::source_index. ./script/../config/boot.rb:25:Warning: Gem::SourceIndex#search support for String patterns is deprecated, use #find_name => Booting WEBrick... => Rails application started on http://0.0.0.0:3000 => Ctrl-C to shutdown server; call with --help for options [2014-11-05 09:58:00] INFO WEBrick 1.3.1 [2014-11-05 09:58:00] INFO ruby 1.8.7 (2013-12-22) [i686-darwin13.4.0] [2014-11-05 09:58:00] INFO WEBrick::HTTPServer#start: pid=41225 port=3000
- 何の問題もなく、さきほど作ったtodoプロジェクトが起動した。
Gemfileの書き方
バージョン指定
- Gemfileのバージョンは、もう少し柔軟に指定できる。
# rails 1.1.6を利用する gem "rails", "1.1.6" # rails 1.1.2以上を利用する gem "rails", ">=1.1.2" # rails 1.1.2以上 && 1.1系で最新のものを利用する gem "rails", "~>1.1.2"
:source =>
- ソース(gemをダウンロードするサーバー)を個別に指定する。
gem "rails", "1.1.6", :source => https://gems.example.com
:git =>
- Gitリポジトリを指定してインストールする。
# masterの最新コミットをインストール gem 'jpdate', :git => 'https://github.com/zarigani/jcal.git' # :tag, :branch, :refなどを指定してインストール gem 'jpdate', :git => 'https://github.com/tenderlove/nokogiri.git', :branch => '1.4'
- ちなみに、gemコマンドのデフォルトとは異なる場所にインストールされた。
$ bundle show jpdate /Users/zari/.rbenv/versions/1.8.7-p375/lib/ruby/gems/1.8/bundler/gems/jcal-cf7b86d88e08
:path =>
- インストール済のgemをbundlerの管理に含める。
# ダウンロードフォルダに展開されたmecab-ruby-0.994をbundlerの管理下におく gem "mecab-ruby", :path => '~/Downloads/ruby/1.8/gems/mecab-ruby-0.994'
詳細
- さらなる詳細な設定は、bundler本家を参照。
- http://bundler.io/man/gemfile.5.html
プロジェクト配下にgemをインストール
- 今まではbundle installの時、一切のオプションは指定せず、実行してきた。
- その場合、bundlerはgemコマンドデフォルトの場所(gem env home)にインストールする。
- 例えば、rbenvが管理するRuby-1.8.7-p375のgem env homeは、以下のように設定されている。
$ rbenv version 1.8.7-p375 (set by /Users/zari/.rbenv/version) $ gem env home /Users/zari/.rbenv/versions/1.8.7-p375/lib/ruby/gems/1.8
- 様々なプロジェクトでbundle installを繰り返していると、当然ながらgem env homeの中はgemとバージョンで溢れ返る。
- 溢れ返ったとしても、bundlerが必要なgemとバージョンを取りまとめているので、問題はないはず。
- 但し、溢れ返って混沌とした状態のgem env homeは、何が必要で、何が不要か、もはや理解不能な状態になってくる。
- あるプロジェクトで不要なgemも、他のプロジェクトでは必要かもしれない。また、その逆もあり得る。
- 実験的にインストールして、もはや使わなくなったgemもあるはずなのに、削除するのが怖い...。
- gem env homeにすべてのgemを詰め込んでしまうのは、あまり良い状態とは言えないのではないか?
- 特に実験的なプロジェクトでgemを試用する場合は、gem env homeにはインストールしたくない。
そう言った気持ちを察してか、bundlerにはプロジェクト配下にインストールするオプションがある!
- さっそく、todoプロジェクト配下にgemをインストールしてみる。
- まずは以前と同様にGemfileを作成しておく。
$ cd ~/Desktop/rails116/todo $ bundle init Writing new Gemfile to /Users/zari/Desktop/rails116/todo/Gemfile
$ cat <<EOS >> Gemfile gem "rails", "1.1.6" gem "rake", "0.7.1" gem "sqlite3" EOS
- そして、bundle installの時に--pathオプションを指定する。
- オプションの値には、インストール先のパスを指定するのだ。
- 通常は、--path=vendor/bundleが推奨されている。
$ bundle install --path=vendor/bundle Fetching gem metadata from https://rubygems.org/........... Resolving dependencies... Installing rake 0.7.1 Installing activesupport 1.3.1 Installing actionpack 1.12.5 Installing actionmailer 1.2.5 Installing activerecord 1.14.4 Installing actionwebservice 1.1.6 Installing rails 1.1.6 Installing sqlite3 1.3.10 Using bundler 1.7.4 Your bundle is complete! It was installed into ./vendor/bundle
- todoプロジェクトのvendor/bundle以下に、gemがインストールされた!
$ find vendor/bundle/ruby/1.8/gems/* -d 0 vendor/bundle/ruby/1.8/gems/actionmailer-1.2.5 vendor/bundle/ruby/1.8/gems/actionpack-1.12.5 vendor/bundle/ruby/1.8/gems/actionwebservice-1.1.6 vendor/bundle/ruby/1.8/gems/activerecord-1.14.4 vendor/bundle/ruby/1.8/gems/activesupport-1.3.1 vendor/bundle/ruby/1.8/gems/rails-1.1.6 vendor/bundle/ruby/1.8/gems/rake-0.7.1 vendor/bundle/ruby/1.8/gems/sqlite3-1.3.10
- 試しに、gem env homeのgemをbundler以外、すべて削除してみる。
$ gem list --no-version|grep ^[a-z]|grep -v bundler|xargs gem uninstall -aIx Successfully uninstalled actionmailer-1.2.5 Successfully uninstalled actionpack-1.12.5 Successfully uninstalled actionwebservice-1.1.6 Successfully uninstalled activerecord-1.14.4 Successfully uninstalled activesupport-1.3.1 Removing rails Successfully uninstalled rails-1.1.6 Removing rake Successfully uninstalled rake-0.7.1 Successfully uninstalled sqlite3-1.3.10 $ gem list *** LOCAL GEMS *** bundler (1.7.4)
- これで、gem env homeにはbundler以外、もう何も残っていない。
- サーバーを起動してみると...
$ bundle exec script/server ./script/../config/boot.rb:25:Warning: Gem::cache is deprecated and will be removed on or after August 2011. Use Gem::source_index. ./script/../config/boot.rb:25:Warning: Gem::SourceIndex#search support for String patterns is deprecated, use #find_name => Booting WEBrick... => Rails application started on http://0.0.0.0:3000 => Ctrl-C to shutdown server; call with --help for options [2014-11-05 14:52:56] INFO WEBrick 1.3.1 [2014-11-05 14:52:56] INFO ruby 1.8.7 (2013-12-22) [i686-darwin13.4.0] [2014-11-05 14:52:56] INFO WEBrick::HTTPServer#start: pid=45168 port=3000
- gem env homeの中は空っぽだけど、
- それでもtodoプロジェクトは動いた!
bundle execによって、vendor/bundleのgemが利用されているのだ!
--path=vendor/bundleを保持する仕組み
- 一旦--path=vendor/bundleオプションを指定すると、
- 作業ディレクトリには、.bundle/configが作成される。
$ cat .bundle/config
-
- -
-
-
- BUNDLE_PATH: vendor/bundleは、--path=vendor/bundleオプションの保存。
- BUNDLE_DISABLE_SHARED_GEMS: '1'は、既存のgemが存在しても共有せず、vendor/bundleへコピーする設定。
-
- .bundle/configの内容を確認するbundle configコマンドもある。
$ bundle config Settings are listed in order of priority. The top value will be used. path Set for your local app (/Users/zari/Desktop/rails116/todo/.bundle/config): "vendor/bundle" disable_shared_gems Set for your local app (/Users/zari/Desktop/rails116/todo/.bundle/config): "1"
- 上記のように、--pathオプションを指定すると.bundle/configに保持されるので、
- その後はbundle installのみでも、--path=vendor/bundleへのインストールとなる。
$ bundle install Using rake 0.7.1 Using activesupport 1.3.1 Using actionpack 1.12.5 Using actionmailer 1.2.5 Using activerecord 1.14.4 Using actionwebservice 1.1.6 Using rails 1.1.6 Using sqlite3 1.3.10 Using bundler 1.7.4 Your bundle is complete! It was installed into ./vendor/bundle
--pathオプションを解除する
- では、プロジェクトごとのbundle管理をやめて、再びgem env homeでbundle管理するには?
- --systemオプションを指定すると、gem env homeにインストールされるのだ。
$ bundle install --system Fetching gem metadata from https://rubygems.org/.......... Installing rake 0.7.1 Installing activesupport 1.3.1 Installing actionpack 1.12.5 Installing actionmailer 1.2.5 Installing activerecord 1.14.4 Installing actionwebservice 1.1.6 Installing rails 1.1.6 Installing sqlite3 1.3.10 Using bundler 1.7.4 Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed. $ gem list *** LOCAL GEMS *** actionmailer (1.2.5) actionpack (1.12.5) actionwebservice (1.1.6) activerecord (1.14.4) activesupport (1.3.1) bundler (1.7.4) rails (1.1.6) rake (0.7.1) sqlite3 (1.3.10)
- そして、.bundle/configの内容は削除された。
$ cat .bundle/config --- {} $ bundle config Settings are listed in order of priority. The top value will be used.
- 以降は、--pathオプションを指定しない限り、常にgem env homeへのインストールとなるのだ。
Railsプロジェクトの遅延作成
遅延というキーワードは崇高な気がするが、とっても単純な話。
- 例えば、rails todoコマンドでプロジェクトを始めようとする時の悩み。
- Railsを、自身が生成するプロジェクト内に含めてbundle管理することはできないのだろうか?
以下の手順でRails自身のプロジェクト内でbundle管理できた!
$ mkdir todo $ cd todo $ bundle init Writing new Gemfile to /Users/zari/Desktop/rails116/todo/Gemfile $ cat <> Gemfile gem "rails", "1.1.6" gem "rake", "0.7.1" gem "sqlite3" EOS $ bundle install --path=vendor/bundle Fetching gem metadata from https://rubygems.org/........... Resolving dependencies... Installing rake 0.7.1 Installing activesupport 1.3.1 Installing actionpack 1.12.5 Installing actionmailer 1.2.5 Installing activerecord 1.14.4 Installing actionwebservice 1.1.6 Installing rails 1.1.6 Installing sqlite3 1.3.10 Using bundler 1.7.4 Your bundle is complete! It was installed into ./vendor/bundle $ bundle exec rails . -d sqlite3
- 以降の手順は今までどおり。
# config/boot.rbを修正する $ bundle exec script/generate model todo # db/migrate/001_create_todos.rbを修正する $ bundle exec rake db:migrate $ bundle exec script/generate scaffold todo $ bundle exec script/server