Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / railties / lib / rails / gem_dependency.rb
1 require 'rails/vendor_gem_source_index'
2
3 module Gem
4 def self.source_index=(index)
5 @@source_index = index
6 end
7 end
8
9 module Rails
10 class GemDependency < Gem::Dependency
11 attr_accessor :lib, :source, :dep
12
13 def self.unpacked_path
14 @unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems')
15 end
16
17 @@framework_gems = {}
18
19 def self.add_frozen_gem_path
20 @@paths_loaded ||= begin
21 source_index = Rails::VendorGemSourceIndex.new(Gem.source_index)
22 Gem.clear_paths
23 Gem.source_index = source_index
24 # loaded before us - we can't change them, so mark them
25 Gem.loaded_specs.each do |name, spec|
26 @@framework_gems[name] = spec
27 end
28 true
29 end
30 end
31
32 def initialize(name, options = {})
33 require 'rubygems' unless Object.const_defined?(:Gem)
34
35 if options[:requirement]
36 req = options[:requirement]
37 elsif options[:version]
38 req = Gem::Requirement.create(options[:version])
39 else
40 req = Gem::Requirement.default
41 end
42
43 @lib = options[:lib]
44 @source = options[:source]
45 @loaded = @frozen = @load_paths_added = false
46
47 super(name, req)
48 end
49
50 def add_load_paths
51 self.class.add_frozen_gem_path
52 return if @loaded || @load_paths_added
53 if framework_gem?
54 @load_paths_added = @loaded = @frozen = true
55 return
56 end
57 gem self
58 @spec = Gem.loaded_specs[name]
59 @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec
60 @load_paths_added = true
61 rescue Gem::LoadError
62 end
63
64 def dependencies
65 return [] if framework_gem?
66 return [] unless installed?
67 specification.dependencies.reject do |dependency|
68 dependency.type == :development
69 end.map do |dependency|
70 GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
71 end
72 end
73
74 def specification
75 # code repeated from Gem.activate. Find a matching spec, or the currently loaded version.
76 # error out if loaded version and requested version are incompatible.
77 @spec ||= begin
78 matches = Gem.source_index.search(self)
79 matches << @@framework_gems[name] if framework_gem?
80 if Gem.loaded_specs[name] then
81 # This gem is already loaded. If the currently loaded gem is not in the
82 # list of candidate gems, then we have a version conflict.
83 existing_spec = Gem.loaded_specs[name]
84 unless matches.any? { |spec| spec.version == existing_spec.version } then
85 raise Gem::Exception,
86 "can't activate #{@dep}, already activated #{existing_spec.full_name}"
87 end
88 # we're stuck with it, so change to match
89 version_requirements = Gem::Requirement.create("=#{existing_spec.version}")
90 existing_spec
91 else
92 # new load
93 matches.last
94 end
95 end
96 end
97
98 def requirement
99 r = version_requirements
100 (r == Gem::Requirement.default) ? nil : r
101 end
102
103 def built?
104 # TODO: If Rubygems ever gives us a way to detect this, we should use it
105 false
106 end
107
108 def framework_gem?
109 @@framework_gems.has_key?(name)
110 end
111
112 def frozen?
113 @frozen ||= vendor_rails? || vendor_gem?
114 end
115
116 def installed?
117 Gem.loaded_specs.keys.include?(name)
118 end
119
120 def load_paths_added?
121 # always try to add load paths - even if a gem is loaded, it may not
122 # be a compatible version (ie random_gem 0.4 is loaded and a later spec
123 # needs >= 0.5 - gem 'random_gem' will catch this and error out)
124 @load_paths_added
125 end
126
127 def loaded?
128 @loaded ||= begin
129 if vendor_rails?
130 true
131 elsif specification.nil?
132 false
133 else
134 # check if the gem is loaded by inspecting $"
135 # specification.files lists all the files contained in the gem
136 gem_files = specification.files
137 # select only the files contained in require_paths - typically in bin and lib
138 require_paths_regexp = Regexp.new("^(#{specification.require_paths*'|'})/")
139 gem_lib_files = gem_files.select { |f| require_paths_regexp.match(f) }
140 # chop the leading directory off - a typical file might be in
141 # lib/gem_name/file_name.rb, but it will be 'require'd as gem_name/file_name.rb
142 gem_lib_files.map! { |f| f.split('/', 2)[1] }
143 # if any of the files from the above list appear in $", the gem is assumed to
144 # have been loaded
145 !(gem_lib_files & $").empty?
146 end
147 end
148 end
149
150 def vendor_rails?
151 Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty?
152 end
153
154 def vendor_gem?
155 specification && File.exists?(unpacked_gem_directory)
156 end
157
158 def build
159 require 'rails/gem_builder'
160 unless built?
161 return unless File.exists?(unpacked_specification_filename)
162 spec = YAML::load_file(unpacked_specification_filename)
163 Rails::GemBuilder.new(spec, unpacked_gem_directory).build_extensions
164 puts "Built gem: '#{unpacked_gem_directory}'"
165 end
166 dependencies.each { |dep| dep.build }
167 end
168
169 def install
170 unless installed?
171 cmd = "#{gem_command} #{install_command.join(' ')}"
172 puts cmd
173 puts %x(#{cmd})
174 end
175 end
176
177 def load
178 return if @loaded || @load_paths_added == false
179 require(@lib || name) unless @lib == false
180 @loaded = true
181 rescue LoadError
182 puts $!.to_s
183 $!.backtrace.each { |b| puts b }
184 end
185
186 def refresh
187 Rails::VendorGemSourceIndex.silence_spec_warnings = true
188 real_gems = Gem.source_index.installed_source_index
189 exact_dep = Gem::Dependency.new(name, "= #{specification.version}")
190 matches = real_gems.search(exact_dep)
191 installed_spec = matches.first
192 if frozen?
193 if installed_spec
194 # we have a real copy
195 # get a fresh spec - matches should only have one element
196 # note that there is no reliable method to check that the loaded
197 # spec is the same as the copy from real_gems - Gem.activate changes
198 # some of the fields
199 real_spec = Gem::Specification.load(matches.first.loaded_from)
200 write_specification(real_spec)
201 puts "Reloaded specification for #{name} from installed gems."
202 else
203 # the gem isn't installed locally - write out our current specs
204 write_specification(specification)
205 puts "Gem #{name} not loaded locally - writing out current spec."
206 end
207 else
208 if framework_gem?
209 puts "Gem directory for #{name} not found - check if it's loading before rails."
210 else
211 puts "Something bad is going on - gem directory not found for #{name}."
212 end
213 end
214 end
215
216 def unpack(options={})
217 unless frozen? || framework_gem?
218 FileUtils.mkdir_p unpack_base
219 Dir.chdir unpack_base do
220 Gem::GemRunner.new.run(unpack_command)
221 end
222 # Gem.activate changes the spec - get the original
223 real_spec = Gem::Specification.load(specification.loaded_from)
224 write_specification(real_spec)
225 end
226 dependencies.each { |dep| dep.unpack } if options[:recursive]
227 end
228
229 def write_specification(spec)
230 # copy the gem's specification into GEMDIR/.specification so that
231 # we can access information about the gem on deployment systems
232 # without having the gem installed
233 File.open(unpacked_specification_filename, 'w') do |file|
234 file.puts spec.to_yaml
235 end
236 end
237
238 def ==(other)
239 self.name == other.name && self.requirement == other.requirement
240 end
241 alias_method :"eql?", :"=="
242
243 private
244
245 def gem_command
246 case RUBY_PLATFORM
247 when /win32/
248 'gem.bat'
249 when /java/
250 'jruby -S gem'
251 else
252 'gem'
253 end
254 end
255
256 def install_command
257 cmd = %w(install) << name
258 cmd << "--version" << %("#{requirement.to_s}") if requirement
259 cmd << "--source" << @source if @source
260 cmd
261 end
262
263 def unpack_command
264 cmd = %w(unpack) << name
265 cmd << "--version" << "= "+specification.version.to_s if requirement
266 cmd
267 end
268
269 def unpack_base
270 Rails::GemDependency.unpacked_path
271 end
272
273 def unpacked_gem_directory
274 File.join(unpack_base, specification.full_name)
275 end
276
277 def unpacked_specification_filename
278 File.join(unpacked_gem_directory, '.specification')
279 end
280
281 end
282 end