2017-06-21 52 views
0

我有一個工具,分析一些具有Gemfile和Gemfile.lock文件的紅寶石項目。這個工具在輸入ruby項目所在的路徑時列出了它的所有依賴關係。如何使用Gemfile.lock和LockfileParser類僅列出紅寶石生產依賴關係

我的問題是,我只需要打印生產依賴項,不包括開發和測試。今天我發現我的代碼並不排除它們,我不知道如何修改它以達到我的目的。 我如何從列表中刪除開發和測試依賴項?

這裏的簡化版本,我用列表的依賴紅寶石代碼:

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! 

變量to_fetch包含的依賴關係。

這裏的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 

讓我知道,如果你還需要Gemfile.lock的是149線長。

回答

1

我會離開我的其他答案它幫助別人:這是基於我認爲你想要的修改版本

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 

的用法是:

dt = DependencyTree.new('Gemfile','Gemfile.lock') 
dt.to_h 

輸出段:

{: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)"]} 

生產的寶石將在:default發展的寶石將:default + :development

+0

感謝您的答覆。對此,我真的非常感激。 locked_gems方法未定義在 def lock_file @ definition.locked_gems end 我該如何解決這個問題? 此外,我有一個印象,我需要在某處安裝需要.gem文件。 目前我有他們在 /供應商/緩存。 如何讓我的代碼找到它們? –

+0

好的已解決。我只是取代它:lockfile_contents = File.read( 'Gemfile.lock的') 捆紮機:: LockfileParser.new(lockfile_contents) –

+0

非常感謝你。 –

1

這個怎麼樣

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 

這將GROUP BY他們的小組所有依賴關係,並放置在包含他們的名字,他們需要的版本和他們的特定版本Hash ES。例如:

{: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是生產(可能需要進一步的處理依賴的依賴擴大我會看看我能做些什麼)

更新1:類似的格式列出了相關的:runtime依賴(不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 

輸出段:

{: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=>{}}, 

更新2(更多像你看起來的樣子,現在需要定位,是uniq的由 「集團」)

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!} 

輸出段:

{: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", 
+0

哇,你是偉大的。非常感謝你。我需要時間來測試它,但我相信你會測試它,它會起作用。驚人!!! –

+0

我可以問你一個問題嗎?我有已經建立了Gemfile.lock的項目。我的理解是,雖然Gemfile列出了所需的依賴關係(所以即使是版本> x.y)Gemfile.lock也包含使用的真正依賴關係(整個依賴關係樹)。現在假設我只有一個項目的源代碼,我如何確定依賴關係?我應該看看Gemfile還是.gemspec?我知道在這種情況下,我無法知道哪些依賴關係會被使用,因爲它取決於它何時將被構建,但對於沒有指定確切版本的依賴關係,我可以擁有最新版本。 –

+1

@ SalvatoreD'angelo從純粹的來源確定依賴關係要困難得多。如果你有一個Gemfile或一個Gemspec應該是合理的。如果真的只是純粹的「需要」某些依賴關係的源代碼,那麼你將不得不跟蹤諸如「$ LOAD_PATH」之類的事情,並確定添加的項目是否爲「gems」,然後將其用於依賴列表。此外,您必須確保您可以訪問代碼庫中的每個文件,以確保每個依賴項都已加載。我相信這是可能的,但比我打算解決的更乏味。 – engineersmnky