Ruby Bundler. How do I lock down all versions in Gemfile with working set from Gemfile.lock?

Viewed 1272

I want to set my Gemfile to use all the exact versions from a working Gemfile.lock.

What is the easiest way to do this?

I do not want to do it manually. Does bundler do this out of the box. If not, is there a gem for this?

To clarify, I have a Gemfile like this:

source 'https://rubygems.org'

gem 'pg'
gem 'puma'
gem 'rails'

I run bundle install, and I get a Gemfile.lock that works for me:

GEM
  remote: https://rubygems.org/
  specs:
    pg (0.21.0)
    puma (3.10.0)
    rails (5.0.5)
      actioncable (= 5.0.5)
      actionmailer (= 5.0.5)
      actionpack (= 5.0.5)
      actionview (= 5.0.5)
      activejob (= 5.0.5)
      activemodel (= 5.0.5)
      activerecord (= 5.0.5)
      activesupport (= 5.0.5)
      bundler (>= 1.3.0)
      railties (= 5.0.5)
      sprockets-rails (>= 2.0.0)

Now I want a command that updates my Gemfile so that versions are specified:

source 'https://rubygems.org'

gem 'pg', '0.21.0'
gem 'puma', '3.10.0'
gem 'rails', '5.0.5' 
3 Answers

If you are trying to reproduce your current Gemfile.lock in the Gemfile for another project, you can specify the exact version of your gems.

Let's assume that I have the following Gemfile in my Rails App:

source 'https://rubygems.org'

gem 'pg', '~> 0.18'
gem 'puma', '~> 3.0'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1' 

It generates the following Gemfile.lock:

GEM
  remote: https://rubygems.org/
  specs:
    pg (0.21.0)
    puma (3.10.0)
    rails (5.0.5)
      actioncable (= 5.0.5)
      actionmailer (= 5.0.5)
      actionpack (= 5.0.5)
      actionview (= 5.0.5)
      activejob (= 5.0.5)
      activemodel (= 5.0.5)
      activerecord (= 5.0.5)
      activesupport (= 5.0.5)
      bundler (>= 1.3.0)
      railties (= 5.0.5)
      sprockets-rails (>= 2.0.0)

If I want to reproduce exactly the same situation in a new project, I will create a Gemfile with exact versions:

source 'https://rubygems.org'

gem 'pg', '0.21.0'
gem 'puma', '3.10.0'
gem 'rails', '5.0.5'

and so on..

You can refer to bundler documentation

Edit

After you changed the focus of your question, it seems you are trying to create a Gemfile from a Gemfile.lock.

You could have a look at bundle --deployment. I saw several SO questions complaining about the output.

So, if it is not 100% satisfactory, you can use the Bundler::LockfileParser and make your own script such as:

# test.rb
require 'bundler'

lockfile = Bundler::LockfileParser.new(Bundler.read_file('Gemfile.lock'))

specs = lockfile.specs
gems_hash = Hash.new.tap do |h|
  specs.each do |s|
    h[s.name] = {
      spec: s,
      dependencies: s.dependencies.map(&:name)
    }
  end
end

dependencies = gems_hash.keys && gems_hash.values.map { |h| h[:dependencies] }.flatten.uniq.sort

# Remove from the new Gemfile all gems installed as dependencies
dependencies.each { |dep| gems_hash.delete(dep) }

relevant_specs = gems_hash.values.map { |h| h[:spec] }

# I assume that by default you are installing from rubygems
puts "source 'https://rubygems.org'"
puts

relevant_specs.each do |s|
  if s.source.to_s =~ /https:\/\/rubygems.org/
    puts "gem '#{s.name}', '#{s.version}'" # eventually add "plaftform: :#{s.platform}"
  # I consider as only alternative a git source. 
  elsif s.source.is_a?(Bundler::Source::Git)
    uri = s.source.uri
    branch = s.source.branch
    ref = s.source.ref
    puts
    puts "git '#{uri}', branch: '#{branch}', ref: :#{ref} do"
    puts "  gem '#{s.name}'"
    puts "end"
    puts
  end
end
puts

I created this gist for reading easily.

You can create then your Gemfile by running:

$ ruby test.rb > Gemfile

You could always write it yourself :)

result = ""
File.readlines("Gemfile").each do |line|
  if line.strip.start_with?("gem") && !line.include?(",")
    name = line.match(/gem '(.+)'/)[1]
    output = `bundle info #{name}`
    version = output.match(/#{name} \((.+)\)/)[1]
    result << "  " if line.start_with?("  ")
    result << "gem '#{name}', '#{version}'\n"
  else
    result << line
  end
end
puts result

Works only with single quotes, though.

to see gem names and versions in Gemfile like format see bundle show

On linux I use this quick and dirty snippet to convert to a format I can copy over Gemfile.

bundle show | ruby -ne 'puts $_.gsub(/ \* /,"gem \"").gsub(/ \(/,"\", \"~> ").gsub(/\)/,"\"")'

Related