Froze rails gems
[depot.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
11 attr_accessor :lib, :source
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 framework_gem?
33 @@framework_gems.has_key?(name)
34 end
35
36 def vendor_rails?
37 Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty?
38 end
39
40 def vendor_gem?
41 Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.include?(self.class.unpacked_path)
42 end
43
44 def initialize(name, options = {})
45 require 'rubygems' unless Object.const_defined?(:Gem)
46
47 if options[:requirement]
48 req = options[:requirement]
49 elsif options[:version]
50 req = Gem::Requirement.create(options[:version])
51 else
52 req = Gem::Requirement.default
53 end
54
55 @dep = Gem::Dependency.new(name, req)
56 @lib = options[:lib]
57 @source = options[:source]
58 @loaded = @frozen = @load_paths_added = false
59 end
60
61 def add_load_paths
62 self.class.add_frozen_gem_path
63 return if @loaded || @load_paths_added
64 if framework_gem?
65 @load_paths_added = @loaded = @frozen = true
66 return
67 end
68 gem @dep
69 @spec = Gem.loaded_specs[name]
70 @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec
71 @load_paths_added = true
72 rescue Gem::LoadError
73 end
74
75 def dependencies
76 return [] if framework_gem?
77 all_dependencies = specification.dependencies.map do |dependency|
78 GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
79 end
80 all_dependencies += all_dependencies.map(&:dependencies).flatten
81 all_dependencies.uniq
82 end
83
84 def gem_dir(base_directory)
85 File.join(base_directory, specification.full_name)
86 end
87
88 def spec_filename(base_directory)
89 File.join(gem_dir(base_directory), '.specification')
90 end
91
92 def load
93 return if @loaded || @load_paths_added == false
94 require(@lib || name) unless @lib == false
95 @loaded = true
96 rescue LoadError
97 puts $!.to_s
98 $!.backtrace.each { |b| puts b }
99 end
100
101 def name
102 @dep.name.to_s
103 end
104
105 def requirement
106 r = @dep.version_requirements
107 (r == Gem::Requirement.default) ? nil : r
108 end
109
110 def frozen?
111 @frozen ||= vendor_rails? || vendor_gem?
112 end
113
114 def loaded?
115 @loaded ||= begin
116 if vendor_rails?
117 true
118 elsif specification.nil?
119 false
120 else
121 # check if the gem is loaded by inspecting $"
122 # specification.files lists all the files contained in the gem
123 gem_files = specification.files
124 # select only the files contained in require_paths - typically in bin and lib
125 require_paths_regexp = Regexp.new("^(#{specification.require_paths*'|'})/")
126 gem_lib_files = gem_files.select { |f| require_paths_regexp.match(f) }
127 # chop the leading directory off - a typical file might be in
128 # lib/gem_name/file_name.rb, but it will be 'require'd as gem_name/file_name.rb
129 gem_lib_files.map! { |f| f.split('/', 2)[1] }
130 # if any of the files from the above list appear in $", the gem is assumed to
131 # have been loaded
132 !(gem_lib_files & $").empty?
133 end
134 end
135 end
136
137 def load_paths_added?
138 # always try to add load paths - even if a gem is loaded, it may not
139 # be a compatible version (ie random_gem 0.4 is loaded and a later spec
140 # needs >= 0.5 - gem 'random_gem' will catch this and error out)
141 @load_paths_added
142 end
143
144 def install
145 cmd = "#{gem_command} #{install_command.join(' ')}"
146 puts cmd
147 puts %x(#{cmd})
148 end
149
150 def unpack_to(directory)
151 FileUtils.mkdir_p directory
152 Dir.chdir directory do
153 Gem::GemRunner.new.run(unpack_command)
154 end
155
156 # Gem.activate changes the spec - get the original
157 real_spec = Gem::Specification.load(specification.loaded_from)
158 write_spec(directory, real_spec)
159
160 end
161
162 def write_spec(directory, spec)
163 # copy the gem's specification into GEMDIR/.specification so that
164 # we can access information about the gem on deployment systems
165 # without having the gem installed
166 File.open(spec_filename(directory), 'w') do |file|
167 file.puts spec.to_yaml
168 end
169 end
170
171 def refresh_spec(directory)
172 real_gems = Gem.source_index.installed_source_index
173 exact_dep = Gem::Dependency.new(name, "= #{specification.version}")
174 matches = real_gems.search(exact_dep)
175 installed_spec = matches.first
176 if File.exist?(File.dirname(spec_filename(directory)))
177 if installed_spec
178 # we have a real copy
179 # get a fresh spec - matches should only have one element
180 # note that there is no reliable method to check that the loaded
181 # spec is the same as the copy from real_gems - Gem.activate changes
182 # some of the fields
183 real_spec = Gem::Specification.load(matches.first.loaded_from)
184 write_spec(directory, real_spec)
185 puts "Reloaded specification for #{name} from installed gems."
186 else
187 # the gem isn't installed locally - write out our current specs
188 write_spec(directory, specification)
189 puts "Gem #{name} not loaded locally - writing out current spec."
190 end
191 else
192 if framework_gem?
193 puts "Gem directory for #{name} not found - check if it's loading before rails."
194 else
195 puts "Something bad is going on - gem directory not found for #{name}."
196 end
197 end
198 end
199
200 def ==(other)
201 self.name == other.name && self.requirement == other.requirement
202 end
203 alias_method :"eql?", :"=="
204
205 def hash
206 @dep.hash
207 end
208
209 def specification
210 # code repeated from Gem.activate. Find a matching spec, or the currently loaded version.
211 # error out if loaded version and requested version are incompatible.
212 @spec ||= begin
213 matches = Gem.source_index.search(@dep)
214 matches << @@framework_gems[name] if framework_gem?
215 if Gem.loaded_specs[name] then
216 # This gem is already loaded. If the currently loaded gem is not in the
217 # list of candidate gems, then we have a version conflict.
218 existing_spec = Gem.loaded_specs[name]
219 unless matches.any? { |spec| spec.version == existing_spec.version } then
220 raise Gem::Exception,
221 "can't activate #{@dep}, already activated #{existing_spec.full_name}"
222 end
223 # we're stuck with it, so change to match
224 @dep.version_requirements = Gem::Requirement.create("=#{existing_spec.version}")
225 existing_spec
226 else
227 # new load
228 matches.last
229 end
230 end
231 end
232
233 private
234 def gem_command
235 RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem'
236 end
237
238 def install_command
239 cmd = %w(install) << name
240 cmd << "--version" << %("#{requirement.to_s}") if requirement
241 cmd << "--source" << @source if @source
242 cmd
243 end
244
245 def unpack_command
246 cmd = %w(unpack) << name
247 cmd << "--version" << "= "+specification.version.to_s if requirement
248 cmd
249 end
250 end
251 end