Ruby Tricks 3 - Finding all Dependencies of a Ruby Gem

Recently, for some work, I had a question about all the dependencies that a gem had. I went to the gem specification file and took a look – that obviously had a list of gems which had their own dependencies. I wanted to have a way to get all the dependencies so that I would know what I was dragging in by using a seemingly simple gem [like many people, I was wondering if using a Rails module by itself would drag too many things in].

One way to get an idea is to use Bundler and include the gem you want to check in a Gemfile and do a bundle install – then, check the Gemfile.lock but if you want to do a bit more, you might need to use a small script. As always, Google and StackOverflow are your friends. This led me to this page on StackOverflow that has a solution which is very close to it.

I used the script on that page as the starting point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class GemRequirements
  def initialize(name, version = nil)
    @gem = Gem::Dependency.new(name, version)
  end

  def dependency_tree
    @dependency_tree ||= {}.merge(get_dependency(@gem))
  end

  private

  def get_dependency(gem_dependency)
    spec = gem_dependency.matching_specs.first
    dep_key = "#{gem_dependency.name} #{spec.version}"
    hash = { dep_key => {} }
    spec.runtime_dependencies.each do |spec_dependency|
      spec_dependency_spec = spec_dependency.matching_specs.first
      spec_dep_key = "#{spec_dependency.name} #{spec_dependency_spec.version}"
      hash[dep_key][spec_dep_key] = get_dependency(spec_dependency)
    end
    hash
  end
end

This class is then run using this:

1
2
r = GemRequirements.new 'rails'
r.dependency_tree 

This returns a hash that has the dependency tree. You can either just do a puts to output the whole hash or use the built-in pp to pretty print it:

1
2
3
4
require 'pp'

r = GemRequirements.new 'rails'
pp r.dependency_tree 

The only problem that I had with this was that it does not output the unique list of gems and you still need to scan through the whole list. So, I added a small step to collect up the unique gems for outputting. I made some changes to calculate the list and the tree with some minor changes and this is as shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class GemRequirements
  def initialize(name, version = nil)
    @gem = Gem::Dependency.new(name, version)
    @list = []
    get_dependency(@gem)
  end

  def dependency_tree
    @dependency_tree
  end

  def dependency_list
    @dependency_list
  end

  private

  def get_dependency(gem_dependency)
    spec = gem_dependency.matching_specs.first
    dep_key = "#{gem_dependency.name} #{spec.version}"
    hash = { dep_key => {} }
    spec.runtime_dependencies.each do |spec_dependency|
      spec_dependency_spec = spec_dependency.matching_specs.first
      spec_dep_key = "#{spec_dependency.name} #{spec_dependency_spec.version}"
      @list << spec_dep_key
      hash[dep_key][spec_dep_key] = get_dependency(spec_dependency)
    end
    @dependency_list = @list.uniq.sort
    @dependency_tree = hash
  end
end

Then, something like this will call the above class.

1
2
3
4
5
6
require 'pp'
r = GemRequirements.new 'rails'
pp r.dependency_tree

puts '--'
puts r.dependency_list

As always, this is for me to be able to remember how to do it but if it helps someone, that’s great! Also, if you have some comments, please add below so that I can reflect changes to the code.

comments powered by Disqus