1## 2# The Dependency class holds a Gem name and a Gem::Requirement. 3 4require "rubygems/requirement" 5 6class Gem::Dependency 7 8 ## 9 # Valid dependency types. 10 #-- 11 # When this list is updated, be sure to change 12 # Gem::Specification::CURRENT_SPECIFICATION_VERSION as well. 13 # 14 # REFACTOR: This type of constant, TYPES, indicates we might want 15 # two classes, used via inheritance or duck typing. 16 17 TYPES = [ 18 :development, 19 :runtime, 20 ] 21 22 ## 23 # Dependency name or regular expression. 24 25 attr_accessor :name 26 27 ## 28 # Allows you to force this dependency to be a prerelease. 29 30 attr_writer :prerelease 31 32 ## 33 # Constructs a dependency with +name+ and +requirements+. The last 34 # argument can optionally be the dependency type, which defaults to 35 # <tt>:runtime</tt>. 36 37 def initialize name, *requirements 38 case name 39 when String then # ok 40 when Regexp then 41 msg = ["NOTE: Dependency.new w/ a regexp is deprecated.", 42 "Dependency.new called from #{Gem.location_of_caller.join(":")}"] 43 warn msg.join("\n") unless Gem::Deprecate.skip 44 else 45 raise ArgumentError, 46 "dependency name must be a String, was #{name.inspect}" 47 end 48 49 type = Symbol === requirements.last ? requirements.pop : :runtime 50 requirements = requirements.first if 1 == requirements.length # unpack 51 52 unless TYPES.include? type 53 raise ArgumentError, "Valid types are #{TYPES.inspect}, " + 54 "not #{type.inspect}" 55 end 56 57 @name = name 58 @requirement = Gem::Requirement.create requirements 59 @type = type 60 @prerelease = false 61 62 # This is for Marshal backwards compatibility. See the comments in 63 # +requirement+ for the dirty details. 64 65 @version_requirements = @requirement 66 end 67 68 ## 69 # A dependency's hash is the XOR of the hashes of +name+, +type+, 70 # and +requirement+. 71 72 def hash # :nodoc: 73 name.hash ^ type.hash ^ requirement.hash 74 end 75 76 def inspect # :nodoc: 77 if @prerelease 78 "<%s type=%p name=%p requirements=%p prerelease=ok>" % 79 [self.class, self.type, self.name, requirement.to_s] 80 else 81 "<%s type=%p name=%p requirements=%p>" % 82 [self.class, self.type, self.name, requirement.to_s] 83 end 84 end 85 86 ## 87 # Does this dependency require a prerelease? 88 89 def prerelease? 90 @prerelease || requirement.prerelease? 91 end 92 93 ## 94 # Is this dependency simply asking for the latest version 95 # of a gem? 96 97 def latest_version? 98 @requirement.none? 99 end 100 101 def pretty_print q # :nodoc: 102 q.group 1, 'Gem::Dependency.new(', ')' do 103 q.pp name 104 q.text ',' 105 q.breakable 106 107 q.pp requirement 108 109 q.text ',' 110 q.breakable 111 112 q.pp type 113 end 114 end 115 116 ## 117 # What does this dependency require? 118 119 def requirement 120 return @requirement if defined?(@requirement) and @requirement 121 122 # @version_requirements and @version_requirement are legacy ivar 123 # names, and supported here because older gems need to keep 124 # working and Dependency doesn't implement marshal_dump and 125 # marshal_load. In a happier world, this would be an 126 # attr_accessor. The horrifying instance_variable_get you see 127 # below is also the legacy of some old restructurings. 128 # 129 # Note also that because of backwards compatibility (loading new 130 # gems in an old RubyGems installation), we can't add explicit 131 # marshaling to this class until we want to make a big 132 # break. Maybe 2.0. 133 # 134 # Children, define explicit marshal and unmarshal behavior for 135 # public classes. Marshal formats are part of your public API. 136 137 # REFACTOR: See above 138 139 if defined?(@version_requirement) && @version_requirement 140 version = @version_requirement.instance_variable_get :@version 141 @version_requirement = nil 142 @version_requirements = Gem::Requirement.new version 143 end 144 145 @requirement = @version_requirements if defined?(@version_requirements) 146 end 147 148 # DOC: this method needs documentation or :nodoc''d 149 def requirements_list 150 requirement.as_list 151 end 152 153 def to_s # :nodoc: 154 if type != :runtime then 155 "#{name} (#{requirement}, #{type})" 156 else 157 "#{name} (#{requirement})" 158 end 159 end 160 161 ## 162 # Dependency type. 163 164 def type 165 @type ||= :runtime 166 end 167 168 def == other # :nodoc: 169 Gem::Dependency === other && 170 self.name == other.name && 171 self.type == other.type && 172 self.requirement == other.requirement 173 end 174 175 ## 176 # Dependencies are ordered by name. 177 178 def <=> other 179 self.name <=> other.name 180 end 181 182 ## 183 # Uses this dependency as a pattern to compare to +other+. This 184 # dependency will match if the name matches the other's name, and 185 # other has only an equal version requirement that satisfies this 186 # dependency. 187 188 def =~ other 189 unless Gem::Dependency === other 190 return unless other.respond_to?(:name) && other.respond_to?(:version) 191 other = Gem::Dependency.new other.name, other.version 192 end 193 194 return false unless name === other.name 195 196 reqs = other.requirement.requirements 197 198 return false unless reqs.length == 1 199 return false unless reqs.first.first == '=' 200 201 version = reqs.first.last 202 203 requirement.satisfied_by? version 204 end 205 206 # DOC: this method needs either documented or :nodoc'd 207 208 def match? obj, version=nil 209 if !version 210 name = obj.name 211 version = obj.version 212 else 213 name = obj 214 end 215 216 return false unless self.name === name 217 return true if requirement.none? 218 219 requirement.satisfied_by? Gem::Version.new(version) 220 end 221 222 # DOC: this method needs either documented or :nodoc'd 223 224 def matches_spec? spec 225 return false unless name === spec.name 226 return true if requirement.none? 227 228 requirement.satisfied_by?(spec.version) 229 end 230 231 ## 232 # Merges the requirements of +other+ into this dependency 233 234 def merge other 235 unless name == other.name then 236 raise ArgumentError, 237 "#{self} and #{other} have different names" 238 end 239 240 default = Gem::Requirement.default 241 self_req = self.requirement 242 other_req = other.requirement 243 244 return self.class.new name, self_req if other_req == default 245 return self.class.new name, other_req if self_req == default 246 247 self.class.new name, self_req.as_list.concat(other_req.as_list) 248 end 249 250 # DOC: this method needs either documented or :nodoc'd 251 252 def matching_specs platform_only = false 253 matches = Gem::Specification.find_all { |spec| 254 self.name === spec.name and # TODO: == instead of === 255 requirement.satisfied_by? spec.version 256 } 257 258 if platform_only 259 matches.reject! { |spec| 260 not Gem::Platform.match spec.platform 261 } 262 end 263 264 matches = matches.sort_by { |s| s.sort_obj } # HACK: shouldn't be needed 265 end 266 267 ## 268 # True if the dependency will not always match the latest version. 269 270 def specific? 271 @requirement.specific? 272 end 273 274 # DOC: this method needs either documented or :nodoc'd 275 276 def to_specs 277 matches = matching_specs true 278 279 # TODO: check Gem.activated_spec[self.name] in case matches falls outside 280 281 if matches.empty? then 282 specs = Gem::Specification.find_all { |s| 283 s.name == name 284 }.map { |x| x.full_name } 285 286 if specs.empty? 287 total = Gem::Specification.to_a.size 288 error = Gem::LoadError.new \ 289 "Could not find '#{name}' (#{requirement}) among #{total} total gem(s)" 290 else 291 error = Gem::LoadError.new \ 292 "Could not find '#{name}' (#{requirement}) - did find: [#{specs.join ','}]" 293 end 294 error.name = self.name 295 error.requirement = self.requirement 296 raise error 297 end 298 299 # TODO: any other resolver validations should go here 300 301 matches 302 end 303 304 # DOC: this method needs either documented or :nodoc'd 305 306 def to_spec 307 matches = self.to_specs 308 309 matches.find { |spec| spec.activated? } or matches.last 310 end 311end 312