
Managing Versions with the Bundler
- Transfer
The rake update was recently released from version 0.8.7 to version 0.9.0, which caused a lot of noise in the community and once again revealed a version control problem. I would like to clarify the situation and reiterate the main points that I already mentioned during the release of Bundler 1.0. First, I will talk about simple rules of work, and then go a little deeper into the details.
When you install the gem, Rubygems creates wrappers for all the executables that come with the gem. When the wrapper starts, Rubygems starts, which connects the gem itself using the standard activation mechanism. Rubygems will launch the latest version of the gem installed on the system , even if a different version is specified in Gemfile.lock. In addition, the latest (compatible) versions of all gem dependencies will be activated , even if other versions are specified in Gemfile.lock.
This means that running executable files as simple commands simply bypasses the Bundler and all its dependencies. Sometimes this is not a problem, since developers most likely have just the right versions of all gems. For a long time, Rake was an excellent example of such a gem with the “correct” version, since most of Gemfile.lock was locked to version 0.8.7, which was the last available version and was installed for everyone.
As a result, there was a misconception that direct launch of executable files is compatible with the Bundler and uses it to resolve dependencies. In case of problems, it was usually recommended to use gemsets from RVM, since a gemset compiled from Gemfile.lock essentially meant that all executable environment files worked with the necessary dependency versions.
Unfortunately, because of this crutch, people continue to ignore the advice from the documentation to always use
There is no reason to believe that typing in the console
To at least somehow ease all these dances with that creates a bin directory in which it puts the executable files of all the gems used in the application. Thus, launching
The bin directory creates “portable” wrappers for executable files, so you can safely add it to the repository.
Command
The only exception to the above rules is the command
The contents of the file,
In short, the executable
This behavior is impossible for most applied gems, and the question remains whether it is worth using this hack at all and whether the fact that it
Simple versioning
- Add Gemfile.lock to the repository after it is done
bundle
. - After changing the Gemfile, always do the first thing
bundle install
- it will “conservatively” update Gemfile.lock. Only the gems that you changed in the Gemfile will change. Everything else will remain as it was. - If a “conservative” update is not possible, Bundler will prompt you to do so
bundle update [somegem]
. This command will update only the specified gem and all the dependencies necessary for it. The remaining gems will remain intact. - If you need to re-resolve all dependencies from scratch, you need to do it
bundle update
. - When you run executable files, ALWAYS do it through
bundle exec [command]
. A quote from the documentation: In some cases, starting files withoutbundle exec
might work if this file is part of a gem that is installed on your system and does not load any dependencies that could conflict with your bundle. However, this method is extremely unreliable and is the source of many problems. Even if everything seems to work, it is not a fact that it will work tomorrow or on another server. The next section, “Executables,” is about this. - Remember that you can always return your old Gemfile.lock with a command
git checkout Gemfile.lock
or similar to other SCMs.
Executables
When you install the gem, Rubygems creates wrappers for all the executables that come with the gem. When the wrapper starts, Rubygems starts, which connects the gem itself using the standard activation mechanism. Rubygems will launch the latest version of the gem installed on the system , even if a different version is specified in Gemfile.lock. In addition, the latest (compatible) versions of all gem dependencies will be activated , even if other versions are specified in Gemfile.lock.
This means that running executable files as simple commands simply bypasses the Bundler and all its dependencies. Sometimes this is not a problem, since developers most likely have just the right versions of all gems. For a long time, Rake was an excellent example of such a gem with the “correct” version, since most of Gemfile.lock was locked to version 0.8.7, which was the last available version and was installed for everyone.
As a result, there was a misconception that direct launch of executable files is compatible with the Bundler and uses it to resolve dependencies. In case of problems, it was usually recommended to use gemsets from RVM, since a gemset compiled from Gemfile.lock essentially meant that all executable environment files worked with the necessary dependency versions.
Unfortunately, because of this crutch, people continue to ignore the advice from the documentation to always use
bundle exec
. There is no reason to believe that typing in the console
rake foo
you run the code for the Bundler sandbox, because in reality it is not involved in any way and does not start anywhere. Bundler should be activated at the very beginning of the download and be able to replace the bootloader by slipping it the necessary versions of gems prescribed in Gemfile.lock. By launching executables directly, you execute the ruby code before the Bundler could intervene in the dependency connection process. As a result, the code that you are counting on is not connected at all. Once this happened, everything becomes very unpredictable.bundle install --binstubs
To at least somehow ease all these dances with
bundle exec
, Bundler 1.0 offers a special flag --binstubs
bin/cucumber
, for example, is equivalent to a command bundle exec cucumber
. The bin directory creates “portable” wrappers for executable files, so you can safely add it to the repository.
Command rails
The only exception to the above rules is the command
rails
. Starting with version 3.0, this command first tries to run script/rails
from the current directory. And script/rails
in turn, the first thing that launches the Bundler#This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
The contents of the file,
boot.rb
in turn, are very nontrivial:require 'rubygems'
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
In short, the executable
rails
specifically does everything possible to ensure that the Bundler sandbox startup logic is triggered at the very beginning and is used Kernel#exec
to overload the current process if any gems still load. This behavior is impossible for most applied gems, and the question remains whether it is worth using this hack at all and whether the fact that it
rails
can be launched without bundle exec
confusing is even stronger.