Search code examples
rubyrubygemsbundlergemfilegemfile.lock

Parsing direct and indirect Gems of Gemfile.lock file


I'm trying to parse the following Gemfile.lock to include ALL Gems (direct and indirect dependencies) out of GEM specs:

GEM
  remote: http://rubygems.org/
  specs:
    coderay (1.1.3)
    domain_name (0.5.20190701)
      unf (>= 0.0.5, < 1.0.0)
    http-accept (1.7.0)
    http-cookie (1.0.4)
      domain_name (~> 0.5)
    json (2.5.1)
    method_source (1.0.0)
    mime-types (3.3.1)
      mime-types-data (~> 3.2015)
    mime-types-data (3.2021.0704)
    netrc (0.11.0)
    rest-client (2.1.0)
      http-accept (>= 1.7.0, < 2.0)
      http-cookie (>= 1.0.2, < 2.0)
      mime-types (>= 1.16, < 4.0)
      netrc (~> 0.8)
    unf (0.1.4)
      unf_ext
    unf_ext (0.0.7.7)
    yaml (0.1.1)

PLATFORMS
  ruby
  x86_64-darwin-20

DEPENDENCIES
  json
  rest-client
  yaml

RUBY VERSION
   ruby 2.7.3p183

BUNDLED WITH
   2.2.23

But using my function I can only get the direct dependencies without their indirect ones. e.g: direct dependency: http-cookie (1.0.4)... indirect dependency: domain_name (~> 0.5)

My code:

require 'bundler'

def gemlock(file_path)
  file = file_path
  gemlock_array = []

  context = Bundler::LockfileParser.new(Bundler.read_file(file))

  # Gems
  context.specs.each do |spec|
    name = spec.name
    version = spec.version.to_s

    gemlock_array << {'name' => name, 'version' => version}
  end
  puts gemlock_array
end

gemlock('Gemfile.lock')

I'm getting the following hash back:

{"name"=>"coderay", "version"=>"1.1.3"}
{"name"=>"domain_name", "version"=>"0.5.20190701"}
{"name"=>"http-accept", "version"=>"1.7.0"}
...

As you can see indirect dependencies were automatically ignored! But I still need to get it.

I don't have any experience with bundler and don't know how to solve this problem. Any help in this matter would be much appreciated!

Thanks in advance.


Solution

  • indirect dependencies were automatically ignored

    It would have helped if you'd included the full output. I just ran it myself, and you get:

    [{"name"=>"coderay", "version"=>"1.1.3"},
     {"name"=>"domain_name", "version"=>"0.5.20190701"},
     {"name"=>"http-accept", "version"=>"1.7.0"},
     {"name"=>"http-cookie", "version"=>"1.0.4"},
     {"name"=>"json", "version"=>"2.5.1"},
     {"name"=>"method_source", "version"=>"1.0.0"},
     {"name"=>"mime-types", "version"=>"3.3.1"},
     {"name"=>"mime-types-data", "version"=>"3.2021.0704"},
     {"name"=>"netrc", "version"=>"0.11.0"},
     {"name"=>"rest-client", "version"=>"2.1.0"},
     {"name"=>"unf", "version"=>"0.1.4"}, # <------ !!!!!!!!!!!!
     {"name"=>"unf_ext", "version"=>"0.0.7.7"},
     {"name"=>"yaml", "version"=>"0.1.1"}]
    

    So in summary, the indirect dependency of domain_name, i.e. unf, which is presumably what you were referring to, is included further down the list. Your code already works exactly as you intended.

    One minor point, though: You can simplify the implementation a little by doing this:

    gemlock_array = context.specs.map { |s| {'name' => s.name, 'version' => s.version.to_s} }
    

    In ruby, assigning a temporary array and appending to it within a .each loop is usually sub-optimal. Use map instead, to just construct the array directly.