Search code examples
rubybundler

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


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' 

Solution

  • 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