aboutsummaryrefslogtreecommitdiffstats
path: root/lib/bundler/graph.rb
blob: 0e8e28a6aadf6f785b5ca2f9ae2da086313ffe54 (plain)
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
module Bundler
  class Graph

    USER_OPTIONS = {:style => 'filled, bold', :fillcolor => '#cccccc'}.freeze

    def initialize(env)
      @env = env
    end

    def nodes
      populate
      @nodes
    end

    def groups
      populate
      @groups
    end

    def viz(output_file, show_gem_versions = false, show_dependency_requirements = false)
      require 'graphviz'
      populate

      graph_viz = GraphViz::new('Gemfile', {:concentrate => true, :dpi => 65 } )
      graph_viz.edge[:fontname] = graph_viz.node[:fontname] = 'Arial, Helvetica, SansSerif'
      graph_viz.edge[:fontsize] = 12

      viz_nodes = {}

      # populate all of the nodes
      nodes.each do |name, node|
        label = name.dup
        label << "\n#{node.version}" if show_gem_versions
        options = { :label => label }
        options.merge!( USER_OPTIONS ) if node.is_user
        viz_nodes[name] = graph_viz.add_node( name, options )
      end

      group_nodes = {}
      @groups.each do |name, dependencies|
        group_nodes[name] = graph_viz.add_node(name.to_s, { :shape => 'folder', :fontsize => 16 }.merge(USER_OPTIONS))
        dependencies.each do |dependency|
          options = { :weight => 2 }
          if show_dependency_requirements && (dependency.requirement.to_s != ">= 0")
            options[:label] = dependency.requirement.to_s
          end
          graph_viz.add_edge( group_nodes[name], viz_nodes[dependency.name], options )
        end
      end

      @groups.keys.select { |group| group != :default }.each do |group|
        graph_viz.add_edge( group_nodes[group], group_nodes[:default], { :weight => 2 } )
      end

      viz_nodes.each do |name, node|
        nodes[name].dependencies.each do |dependency|
          options = { }
          if nodes[dependency.name].is_user
            options[:constraint] = false
          end
          if show_dependency_requirements && (dependency.requirement.to_s != ">= 0")
            options[:label] = dependency.requirement.to_s
          end

          graph_viz.add_edge( node, viz_nodes[dependency.name], options )
        end
      end

      graph_viz.output( :png => output_file )
    end

    private

    def populate
      return if @populated

      # hash of name => GraphNode
      @nodes = {}
      @groups = {}

      # Populate @nodes
      @env.specs.each { |spec| @nodes[spec.name] = GraphNode.new(spec.name, spec.version) }

      # For gems in Gemfile, add details
      @env.dependencies.each do |dependency|
        node = @nodes[dependency.name]
        node.is_user = true

        dependency.groups.each do |group|
          if @groups.has_key? group
            group = @groups[group]
          else
            group = @groups[group] = []
          end
          group << dependency
        end
      end

      # walk though a final time and add edges
      @env.specs.each do |spec|

        from = @nodes[spec.name]
        spec.runtime_dependencies.each do |dependency|
          from.dependencies << dependency
        end

      end

      @nodes.freeze
      @groups.freeze
      @populated = true
    end

  end

  # Add version info
  class GraphNode

    def initialize(name, version)
      @name = name
      @version = version
      @is_user = false
      @dependencies = []
    end

    attr_reader :name, :dependencies, :version
    attr_accessor :is_user

  end
end