Managing Versions with the Bundler

Original author: Yehuda Katz
  • 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.

Simple versioning


  1. Add Gemfile.lock to the repository after it is done bundle.
  2. 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.
  3. 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.
  4. If you need to re-resolve all dependencies from scratch, you need to do it bundle update.
  5. When you run executable files, ALWAYS do it through bundle exec [command]. A quote from the documentation: In some cases, starting files without bundle execmight 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.
  6. Remember that you can always return your old Gemfile.lock with a command git checkout Gemfile.lockor 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 fooyou 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 --binstubsthat creates a bin directory in which it puts the executable files of all the gems used in the application. Thus, launching 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/railsfrom the current directory. And script/railsin 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.rbin 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 railsspecifically does everything possible to ensure that the Bundler sandbox startup logic is triggered at the very beginning and is used Kernel#execto 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 railscan be launched without bundle execconfusing is even stronger.

Also popular now: