Notes to self

Working with Bundler from your Ruby code

Have you ever wanted to use Bundler’s internals in your codebase? Here is how how to start by creating your Gemfile.lock, locking the environment and reading back the locked dependencies.

Creating Gemfile

Creating Gemfile is nothing harder than creating a regular file called Gemfile. Bundler creates Gemfile only through the CLI, which is probably not ideal for direct use from your code, but if you are wondering, here’s how to do it:

irb(main):001:0> require 'bundler/cli'
=> false
irb(main):002:0> require 'bundler/cli/init'
=> true
irb(main):003:0> Bundler::CLI::Init.new({ gemspec: 'actionmailer.gemspec' }).run
Writing new Gemfile to /home/strzibny/tmp/Gemfile
=> #

This creates a Gemfile based on .gemspec. Specifying no options will simply create a blank Gemfile. A better way how to do it is to use simple Ruby directly:

require 'rubygems'
require 'bundler'
spec = Gem::Specification.load('actionmailer.gemspec')
File.open('Gemfile', 'wb') do |file|
  file << "# Generated from #{gemspec}\n"
  file << spec.to_gemfile
end

Gem::Specification.load comes from RubyGems, but the #to_gemfile method is added by Bundler, therefore the Bundler requirement. Also note that you probably want to mention RubyGems.org as a source and not :gemcutter in your Gemfile.

Creating Gemfile.lock and installing dependencies

Once we have our Gemfile, we can create Gemfile.lock. This is essentially bundle install.

require 'bundler'
gemfile = 'Gemfile'
definition = Bundler::Definition.build(gemfile, "#{gemfile}.lock", false)
Bundler::Installer.install('.', definition, { "local" => true })

Gemfile.lock is here an empty file, but can be existing one as well. The third parameter of Bundler::Definition.build method is called unlock. This defines whether some gems should be updated or not. You can pass it a hash of gems to update or set it to true if all gems should be updated.

Bundler::Installer.install then creates our new Gemfile.lock after it installs required gems and their dependencies. As you can see the first argument is a directory path and we can also pass some additional options such as --local (this will use already installed gems only).

Locking the dependencies with bundle exec

Locking down the dependencies can be done easily by Bundler.setup and you can also specify a certain groups from your Gemfile.lock:

require 'bundler'
begin
  Bundler.setup(:default, :my_group)
# What if we don't have some gems installed?
rescue Bundler::GemNotFound
end

But remember! Bundler handles $LOAD_PATH for you, but can’t change $LOADED_FEATURES – the code that you required before running Bundler.setup is already in memory and not going away!

Alternatively you can just require 'bundler/setup'.

Reading the Gemfile.lock

If you want to get the dependencies out of Gemfile.lock, perhaps to recreate the Gemfile, you can use Bundler’s LockfileParser:

require 'bundler'
gemfile_lock = Bundler::LockfileParser.new(Bundler.read_file("Gemfile.lock"))

# To get the specification objects
gemfile_lock.specs

So, that was the basics on how to start with Bundler from your Ruby scripts and programs. Read more in the documentation.

Check out my book
Deployment from Scratch is unique Linux book about web application deployment. Learn how deployment works from the first principles rather than YAML files of a specific tool.
by Josef Strzibny
RSS