Управляем версиями с помощью Bundler

http://yehudakatz.com/2011/05/30/gem-versioning-and-bundler-doing-it-right/
  • Перевод
Недавно вышло обновление rake с версии 0.8.7 до версии 0.9.0, которое наделало много шума в сообществе и в очередной раз выявило проблему управления версиями. Мне бы хотелось прояснить ситуацию и снова проговорить основные моменты, которые я уже упоминал во времена релиза Bundler 1.0. Вначале я расскажу о простых правилах работы, а затем слегка углублюсь в детали.

Простое управление версиями


  1. Добавляйте Gemfile.lock в репозиторий после того, как сделали bundle.
  2. После изменения Gemfile, всегда первым делом делайте bundle install — это «консервативно» обновит Gemfile.lock. Изменятся только те гемы, которые вы изменили в Gemfile. Все остальное останется как было.
  3. Если «консервативный» апдейт невозможен, Bundler предложит вам сделать bundle update [somegem]. Эта команда обновит только указанный гем и все необходимые для него зависимости. Остальные гемы останутся нетронутыми.
  4. Если надо заново разрешить все зависимости «с нуля», нужно сделать bundle update.
  5. Когда запускаете исполняемые файлы, ВСЕГДА делайте это через bundle exec [command]. Цитата из документации: В некоторых случаях запуск файлов без bundle exec может сработать, если этот файл — часть гема, который установлен в вашей системе и не грузит никаких зависимостей, которые могли бы конфликтовать с вашим бандлом. Тем не менее, этот способ крайне ненадежен и является источником множества проблем. Даже если все вроде бы работает, не факт, что оно будет работать завтра или на другом сервере. Следующий раздел «Исполняемые файлы» как раз про это.
  6. Помните, что всегда сможете вернуть свой старый Gemfile.lock командой git checkout Gemfile.lock или аналогичной для других SCM.


Исполняемые файлы


Когда вы устанавливаете гем, Rubygems создает обертки для всех исполняемых файлов, идущих в комплекте с гемом. При запуске обертки, запускается Rubygems, который подключает сам гем с помощью стандартного механизма активации. Rubygems запустит самую свежую версию гема, установленную в системе, даже если в Gemfile.lock указана другая версия. Кроме того, будут активированы последние (совместимые) версии всех зависимостей гема, даже если в Gemfile.lock указаны другие версии.

Это означает, что запуск исполняемых файлов как простых команд просто минует Bundler и все залоченые им зависимости. Иногда это не проблема, поскольку у разработчиков скорее всего стоят как раз нужные версии всех гемов. Долгое время Rake был отличным примером такого гема с «правильной» версией, поскольку большинство Gemfile.lock были залочены на версию 0.8.7, которая являлась последней доступной версией и была установлена у всех.

В результате образовалось заблуждение, что прямой запуск исполняемых файлов совместим с Bundler и использует его для разрешения зависимостей. В случае проблем обычно рекомендовалось использовать гемсеты из RVM, поскольку гемсет, собранный из Gemfile.lock, по сути подразумевал, что все исполняемые файлы окружения работали с нужными версиями зависимостей.

К сожалению из-за этого костыля люди продолжают игнорировать совет из документации всегда пользоваться bundle exec.

Нет никаких оснований полагать, что набирая в консоле rake foo вы запускаете код песочнице Bundler, поскольку на самом деле он никак не задействован и нигде не запускается. Bundler должен активироваться в самом начале загрузки и иметь возможность подменить загрузчик подсунув ему нужные версии гемов прописанные в Gemfile.lock. Запуская исполняемые файлы напрямую вы исполняете руби-код до того, как Bundler мог бы вмешаться в процесс подключения зависимостей. В итоге подключается совсем не тот код, на который вы расчитываете. Как только это произошло, все становится весьма непредсказуемым.

bundle install --binstubs


Чтобы хоть как-то облегчить все эти пляски с bundle exec, Bundler 1.0 предлагает специальный флаг --binstubs, который создает директорию bin, в которую помещает исполняемые файлы всех используемых в приложении гемов. Таким образом, запуск bin/cucumber, например, эквивалентен команде bundle exec cucumber.

Директория bin создает «переносимые» обертки для запускаемых файлов, поэтому ее смело можно добавлять в репозиторий.

Команда rails


Единственным исключением приведенных выше правил является команда rails. Начиная с версии 3.0 эта команда первым делом пытается запустить script/rails из текущего каталога. А script/rails в свою очередь первым делом запускает 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'


Содержимое файла boot.rb в свою очередь весьма нетривиально:
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'])


Короче, исполняемый файл rails специально делает все возможное, чтобы гарантировать, что логика запуска песочницы Bundler срабатывает в самом начале и использует Kernel#exec, чтобы перегрузить текущий процесс, если какие-то гемы все же успели загрузиться.
Такое поведение невозможно для большинства прикладных гемов и еще остается открытым вопрос, стоит ли вообще использовать этот хак и не будет ли факт того, что rails можно запускать и без bundle exec запутывать еще сильнее.
  • +36
  • 11,1k
  • 4
Поделиться публикацией
Комментарии 4
    +3
    Спасибо за оперативный перевод, оригинал ведь сегодня был опубликован) И действительно в тему, сам столкнулся с проблемой rake-0.9.0)
      +1
      Спасибо за bundle install --binstubs, надеюсь теперь проблема с rake не повторится)
        0
        Для меня это тоже стало открытием. Поэтому, собственно, и решил статью перевести.
        0
        «rake foo вы запускаете код песочнице»

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое