How to list ruby production only dependencies using Gemfile.lock and LockfileParser class
I will leave my other answer in case it helps someone else: Here is the revised version based on what I think you want
require 'bundler'
class DependencyTree
attr_reader :definition
def initialize(gemfile,lockfile)
@gemfile = gemfile
@definition = Bundler::Definition.build(gemfile,lockfile,nil)
end
def all_dependencies
return @all_dependencies if @all_dependencies
collect_dependencies
end
def inspect
"#<#{self.class.name}:#{self.object_id} Gemfile: #{Pathname.new(@gemfile).expand_path} >"
end
def lock_file
@definition.locked_gems
end
def to_h
lock_file.specs.each_with_object(Hash.new {|h,k| h[k] = []}) do |lock,obj|
gem_file_dep = all_dependencies.detect {|dep| dep[:name] == lock.name} || {group: :unknown}
name = lock.full_name.dup
name << " (#{gem_file_dep[:error]})" if gem_file_dep[:error]
obj[gem_file_dep[:group]] << name
end
end
private
def groupify(dep)
dep.groups.map do |g|
a = [{group: g, name: dep.name}]
begin
a << runtime_dependencies(g,dep.to_spec)
rescue Gem::LoadError => e
a[-1] = {group: g, name: dep.name,error: 'NOT INSTALLED'}
end
end
end
def collect_dependencies
@all_dependencies = @definition.dependencies.map do |dep|
groupify(dep)
end.flatten
group_missing
@all_dependencies.uniq!
end
def runtime_dependencies(group,spec)
spec.dependencies.select { |dep| dep.type == :runtime}.map do |dep|
a = {group: group, name: dep.name}
dep.to_spec.dependencies.empty? ? a : [a] << runtime_dependencies(group,dep.to_spec)
end
end
def group_missing
all_locks.cycle(2) do |a|
deep_dep = @all_dependencies.find_all {|h| a.include?(h[:name])}.uniq
a.each do |k|
deep_dep.each do |h|
all_dependencies << {group: h[:group], name: k, error: 'NOT INSTALLED'}
end
end
end
end
def all_locks
lock_file.specs.map do |spec|
spec.to_lock.delete(' ').split("\n").map do |s|
s.slice(/^[\w\-]+/)
end
end
end
end
the usage is:
dt = DependencyTree.new('Gemfile','Gemfile.lock')
dt.to_h
output Snippet:
{:default=>
["actionmailer-4.2.5.2 (NOT INSTALLED)",
"actionpack-4.2.5.2",
"actionview-4.2.5.2",
"activejob-4.2.5.2 (NOT INSTALLED)",
"activemodel-4.2.5.2",
"activerecord-4.2.5.2",
"activerecord-sqlserver-adapter-4.2.17",
"activesupport-4.2.5.2",
"arel-6.0.3 (NOT INSTALLED)",
"axlsx-2.0.1 (NOT INSTALLED)",
"binding_of_caller-0.7.2 (NOT INSTALLED)",
"builder-3.2.3",
"coffee-rails-4.1.1 (NOT INSTALLED)",
"coffee-script-2.4.1 (NOT INSTALLED)",
"coffee-script-source-1.12.2 (NOT INSTALLED)",
"concurrent-ruby-1.0.4",
"debug_inspector-0.0.2 (NOT INSTALLED)",
"erubis-2.7.0",
"execjs-2.7.0"],
:development=>
["airbrussh-1.1.2",
"byebug-9.0.6 (NOT INSTALLED)",
"capistrano-3.7.2"],
:doc => ["sdoc-0.4.2 (NOT INSTALLED)"]}
production gems will be in :default
development gems would be :default
+ :development
Parsing direct and indirect Gems of Gemfile.lock file
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.
Figure out which gem is requesting rb-inotify
It turned out that SASS was requesting rb-inotify. It was loading the newest version of SASS, and I had to pin it (and sass-rails) to older versions. What helped me find this was the DependencyTree solution from this SO post.
Where to put test files that get created. Ruby directory structure
Sounds like a perfect candidate for Dir.mktmpdir
.
Upgrading from Rails 4.1 to 4.2 Issue with ffi/rb-inotify
This was because I was pulling in the latest version of SASS. When I changed the Gemfile to pin sass and sass-rails to older versions, this problem went away. It was difficult to find the dependency but i used DependencyTree from this SO post to find it.
Rails uninitialized constant Bundler (NameError)
These commands from the bundler FAQ did the trick:
#remove project-specific settings
rm -rf .bundle/
# remove project-specific cached gems and repos
rm -rf vendor/cache/
# remove the saved resolve of the Gemfile
rm -rf Gemfile.lock
and then rebundling with bundle install
edit: heroku deploy was also failing to start up because it starts the server with bin/rails s and my binstubs were all wonky. to fix this: BUNDLE INSTALL --BINSTUBS
, again from bundler docs
Related Topics
Google Maps, Ruby on Rails, Zoom Level with One Marker
Portable Ruby on Rails Environment
Variable Scope and Order of Parsing VS. Operations: Assignment in an "If"
Rails, Postgresql, and History Triggers
Run Rspec Tasks in a Specific Order
Circular Dependency Detected While Autoloading Constant User
How to Embed Regular Expressions in Other Regular Expressions in Ruby
Rails - Cannot Run App: Unable to Load the Eventmachine C Extension;
Using Memcache Client with Ruby
Block Syntax Difference Causes "Localjumperror: No Block Given (Yield)"
Capturing Groups Don't Work as Expected with Ruby Scan Method
How to List Ruby Production Only Dependencies Using Gemfile.Lock and Lockfileparser Class
Uninstall Old Versions of Ruby Gems
Equivalent of "Continue" in Ruby