1#-- 2# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. 3# All rights reserved. 4# See LICENSE.txt for permissions. 5#++ 6 7require 'tsort' 8require 'rubygems/deprecate' 9 10## 11# Gem::DependencyList is used for installing and uninstalling gems in the 12# correct order to avoid conflicts. 13#-- 14# TODO: It appears that all but topo-sort functionality is being duplicated 15# (or is planned to be duplicated) elsewhere in rubygems. Is the majority of 16# this class necessary anymore? Especially #ok?, #why_not_ok? 17 18class Gem::DependencyList 19 attr_reader :specs 20 21 include Enumerable 22 include TSort 23 24 ## 25 # Allows enabling/disabling use of development dependencies 26 27 attr_accessor :development 28 29 ## 30 # Creates a DependencyList from the current specs. 31 32 def self.from_specs 33 list = new 34 list.add(*Gem::Specification.to_a) 35 list 36 end 37 38 ## 39 # Creates a new DependencyList. If +development+ is true, development 40 # dependencies will be included. 41 42 def initialize development = false 43 @specs = [] 44 45 @development = development 46 end 47 48 ## 49 # Adds +gemspecs+ to the dependency list. 50 51 def add(*gemspecs) 52 @specs.concat gemspecs 53 end 54 55 def clear 56 @specs.clear 57 end 58 59 ## 60 # Return a list of the gem specifications in the dependency list, sorted in 61 # order so that no gemspec in the list depends on a gemspec earlier in the 62 # list. 63 # 64 # This is useful when removing gems from a set of installed gems. By 65 # removing them in the returned order, you don't get into as many dependency 66 # issues. 67 # 68 # If there are circular dependencies (yuck!), then gems will be returned in 69 # order until only the circular dependents and anything they reference are 70 # left. Then arbitrary gemspecs will be returned until the circular 71 # dependency is broken, after which gems will be returned in dependency 72 # order again. 73 74 def dependency_order 75 sorted = strongly_connected_components.flatten 76 77 result = [] 78 seen = {} 79 80 sorted.each do |spec| 81 if index = seen[spec.name] then 82 if result[index].version < spec.version then 83 result[index] = spec 84 end 85 else 86 seen[spec.name] = result.length 87 result << spec 88 end 89 end 90 91 result.reverse 92 end 93 94 ## 95 # Iterator over dependency_order 96 97 def each(&block) 98 dependency_order.each(&block) 99 end 100 101 def find_name(full_name) 102 @specs.find { |spec| spec.full_name == full_name } 103 end 104 105 def inspect # :nodoc: 106 "#<%s:0x%x %p>" % [self.class, object_id, map { |s| s.full_name }] 107 end 108 109 ## 110 # Are all the dependencies in the list satisfied? 111 112 def ok? 113 why_not_ok?(:quick).empty? 114 end 115 116 def why_not_ok? quick = false 117 unsatisfied = Hash.new { |h,k| h[k] = [] } 118 each do |spec| 119 spec.runtime_dependencies.each do |dep| 120 inst = Gem::Specification.any? { |installed_spec| 121 dep.name == installed_spec.name and 122 dep.requirement.satisfied_by? installed_spec.version 123 } 124 125 unless inst or @specs.find { |s| s.satisfies_requirement? dep } then 126 unsatisfied[spec.name] << dep 127 return unsatisfied if quick 128 end 129 end 130 end 131 132 unsatisfied 133 end 134 135 ## 136 # Is is ok to remove a gemspec from the dependency list? 137 # 138 # If removing the gemspec creates breaks a currently ok dependency, then it 139 # is NOT ok to remove the gemspec. 140 141 def ok_to_remove?(full_name, check_dev=true) 142 gem_to_remove = find_name full_name 143 144 siblings = @specs.find_all { |s| 145 s.name == gem_to_remove.name && 146 s.full_name != gem_to_remove.full_name 147 } 148 149 deps = [] 150 151 @specs.each do |spec| 152 check = check_dev ? spec.dependencies : spec.runtime_dependencies 153 154 check.each do |dep| 155 deps << dep if gem_to_remove.satisfies_requirement?(dep) 156 end 157 end 158 159 deps.all? { |dep| 160 siblings.any? { |s| 161 s.satisfies_requirement? dep 162 } 163 } 164 end 165 166 ## 167 # Remove everything in the DependencyList that matches but doesn't 168 # satisfy items in +dependencies+ (a hash of gem names to arrays of 169 # dependencies). 170 171 def remove_specs_unsatisfied_by dependencies 172 specs.reject! { |spec| 173 dep = dependencies[spec.name] 174 dep and not dep.requirement.satisfied_by? spec.version 175 } 176 end 177 178 ## 179 # Removes the gemspec matching +full_name+ from the dependency list 180 181 def remove_by_name(full_name) 182 @specs.delete_if { |spec| spec.full_name == full_name } 183 end 184 185 ## 186 # Return a hash of predecessors. <tt>result[spec]</tt> is an Array of 187 # gemspecs that have a dependency satisfied by the named gemspec. 188 189 def spec_predecessors 190 result = Hash.new { |h,k| h[k] = [] } 191 192 specs = @specs.sort.reverse 193 194 specs.each do |spec| 195 specs.each do |other| 196 next if spec == other 197 198 other.dependencies.each do |dep| 199 if spec.satisfies_requirement? dep then 200 result[spec] << other 201 end 202 end 203 end 204 end 205 206 result 207 end 208 209 def tsort_each_node(&block) 210 @specs.each(&block) 211 end 212 213 def tsort_each_child(node) 214 specs = @specs.sort.reverse 215 216 dependencies = node.runtime_dependencies 217 dependencies.push(*node.development_dependencies) if @development 218 219 dependencies.each do |dep| 220 specs.each do |spec| 221 if spec.satisfies_requirement? dep then 222 begin 223 yield spec 224 rescue TSort::Cyclic 225 # do nothing 226 end 227 break 228 end 229 end 230 end 231 end 232 233 private 234 235 ## 236 # Count the number of gemspecs in the list +specs+ that are not in 237 # +ignored+. 238 239 def active_count(specs, ignored) 240 specs.count { |spec| ignored[spec.full_name].nil? } 241 end 242 243end 244 245