问题
I have a tool that analyze some ruby projects having Gemfile and Gemfile.lock files. This tool given in input the path where ruby project is, list all its dependencies.
My problem is that I only need to print production dependencies excluding development and test. Today I find out that my code does not exclude them and I do not know how to modify it for my purpose. How I can remove development and test dependencies from the list?
Here a simplified version of the ruby code I use to list dependencies:
project_path = ARGV.map {|path| File.expand_path(path) }
ENV['BUNDLE_GEMFILE'] = project_path+"/Gemfile"
lockfile_path=project_path+"/Gemfile.lock"
lockfile_contents = File.read(lockfile_path)
parser = Bundler::LockfileParser.new(lockfile_contents)
lockfile_contents = File.read(lockfile_path)
parser = Bundler::LockfileParser.new(lockfile_contents)
to_fetch = []
parser.specs.each do |spec|
gem_basename = "#{spec.name},#{spec.version}"
to_fetch << gem_basename
end
to_fetch.uniq!
to_fetch.sort!
The variable to_fetch contains the dependencies.
Here the Gemfile:
source "https://rubygems.org"
gem "cf-message-bus", git: "https://github.com/cloudfoundry/cf-message-bus.git"
gem "vcap_common", git: "https://github.com/cloudfoundry/vcap-common.git"
gem "aws-sdk", '~> 2', require: false
gem "steno"
gem "httparty"
group :test do
gem 'codeclimate-test-reporter', require: false
gem "rake"
gem "rspec"
gem "ci_reporter"
gem "timecop"
gem "webmock"
end
Let me know if you need also the Gemfile.lock that is 149 lines long.
回答1:
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
回答2:
How about this
require 'bundler'
dependencies = Bundler::Definition.build('Gemfile','Gemfile.lock',nil).
dependencies.each_with_object(Hash.new { |h,k| h[k] = [] }) do |dep,obj|
dep.groups.each do |g|
obj[g] << {name: dep.name,
required_version: dep.requirement.requirements.join,
actual_version: dep.to_spec.version.to_s
}
end
end
This will group all the dependencies by their group and place them in Hash
es containing their name, their required version and their specific version. For example:
{:default=>
[{:name=>"rake", :required_version=>"=11.3.0", :actual_version=>"11.3.0"},
{:name=>"rails", :required_version=>"=4.2.5.2", :actual_version=>"4.2.5.2"},
{:name=>"arel", :required_version=>"=6.0.3", :actual_version=>"6.0.3"},
{:name=>"activerecord",
:required_version=>"~>4.2.0",
:actual_version=>"4.2.8"},
{:name=>"activerecord-sqlserver-adapter",
:required_version=>"~>4.2.0",
:actual_version=>"4.2.18"},
{:name=>"tiny_tds", :required_version=>"~>0.7.0", :actual_version=>"0.7.0"}],
:doc=>
[{:name=>"sdoc", :required_version=>"~>0.4.0", :actual_version=>"0.4.2"}],
:development=>
[{:name=>"byebug", :required_version=>">=0", :actual_version=>"9.0.6"},
{:name=>"web-console",
:required_version=>"~>2.0",
:actual_version=>"2.3.0"},
{:name=>"capistrano",
:required_version=>"=3.7.2",
:actual_version=>"3.7.2"}],
:test=>
[{:name=>"byebug", :required_version=>">=0", :actual_version=>"9.0.6"}]}
BTW :default
is production (might need to expand further to handle dependencies of dependencies I will see what I can do)
Update 1: Similar formatting lists :runtime
dependencies of dependencies (not uniq)
def dep_to_hash(dep)
{name: dep.name,
required_version: dep.requirement.requirements.join,
actual_version: dep.to_spec.version.to_s,
dependencies: dep.to_spec.dependencies.select {|d| d.type == :runtime}.each_with_object(Hash.new { |h,k| h[k] = [] }) do |dep2,obj|
obj[dep2.type] << dep_to_hash(dep2)
end
}
end
deps = Bundler::Definition.build('Gemfile','Gemfile.lock',nil).
dependencies.each_with_object(Hash.new { |h,k| h[k] = [] }) do |dep,obj|
dep.groups.each do |g|
obj[g] << dep_to_hash(dep)
end
end
Output snippet:
{:default=>
[{:name=>"rails",
:required_version=>"=4.2.5.2",
:actual_version=>"4.2.5.2",
:dependencies=>
{:runtime=>
[{:name=>"activesupport",
:required_version=>"=4.2.5.2",
:actual_version=>"4.2.5.2",
:dependencies=>
{:runtime=>
[{:name=>"i18n",
:required_version=>"~>0.7",
:actual_version=>"0.8.1",
:dependencies=>{}},
{:name=>"json",
:required_version=>">=1.7.7~>1.7",
:actual_version=>"1.8.6",
:dependencies=>{}},
Update 2 (More like what you seem to be targeting now and is uniq by "group")
require 'bundler'
def add_to_dep(dep,top_level)
deps = dep.to_spec.dependencies.select {|d| d.type == :runtime}
deps.each do |dep|
add_to_dep(dep,top_level)
end
# handle existing dependencies by using highest version
exists = top_level.grep(/#{dep.name}/)[0]
if exists
version = exists.split(',').last
new_version = dep.to_spec.version.to_s
if new_version > version
top_level.delete_at(top_level.index(exists))
top_level << "#{dep.name}, #{dep.to_spec.version}"
end
else
top_level << "#{dep.name}, #{dep.to_spec.version}"
end
end
deps = Bundler::Definition.build('Gemfile','Gemfile.lock',nil).
dependencies.each_with_object(Hash.new { |h,k| h[k] = [] }) do |dep,obj|
dep.groups.each do |g|
add_to_dep(dep,obj[g])
end
end.each {|_k,v| v.sort!}
Output Snippet:
{:default=>
["actionmailer, 4.2.5.2",
"actionpack, 4.2.8",
"actionview, 4.2.8",
"activejob, 4.2.5.2",
"activemodel, 4.2.8",
"activerecord, 4.2.8",
"activerecord-sqlserver-adapter, 4.2.18",
"activesupport, 4.2.8",
"arel, 6.0.4",
"axlsx, 2.0.1",
"builder, 3.2.3",
"bundler, 1.14.6",
"coffee-rails, 4.1.1",
"coffee-script, 2.4.1",
"coffee-script-source, 1.12.2",
"concurrent-ruby, 1.0.5",
"erubis, 2.7.0",
"execjs, 2.7.0",
"globalid, 0.4.0",
"hash-deep-merge, 0.1.1",
"htmlentities, 4.3.4",
"i18n, 0.8.1",
"jbuilder, 2.6.2",
"jquery-rails, 4.2.2",
"jquery-ui-rails, 6.0.1",
"json, 1.8.6",
"lazy_high_charts, 1.5.6",
"loofah, 2.0.3",
"mail, 2.6.5",
"mime-types, 3.1",
"mime-types-data, 3.2016.0521",
来源:https://stackoverflow.com/questions/44681231/how-to-list-ruby-production-only-dependencies-using-gemfile-lock-and-lockfilepar