Froze rails gems
[depot.git] / vendor / rails / railties / lib / rails / gem_dependency.rb
diff --git a/vendor/rails/railties/lib/rails/gem_dependency.rb b/vendor/rails/railties/lib/rails/gem_dependency.rb
new file mode 100644 (file)
index 0000000..cd280ac
--- /dev/null
@@ -0,0 +1,251 @@
+require 'rails/vendor_gem_source_index'
+
+module Gem
+  def self.source_index=(index)
+    @@source_index = index
+  end
+end
+
+module Rails
+  class GemDependency
+    attr_accessor :lib, :source
+
+    def self.unpacked_path
+      @unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems')
+    end
+
+    @@framework_gems = {}
+
+    def self.add_frozen_gem_path
+      @@paths_loaded ||= begin
+        source_index = Rails::VendorGemSourceIndex.new(Gem.source_index)
+        Gem.clear_paths
+        Gem.source_index = source_index
+        # loaded before us - we can't change them, so mark them
+        Gem.loaded_specs.each do |name, spec|
+          @@framework_gems[name] = spec
+        end
+        true
+      end
+    end
+
+    def framework_gem?
+      @@framework_gems.has_key?(name)
+    end
+
+    def vendor_rails?
+      Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty?
+    end
+
+    def vendor_gem?
+      Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.include?(self.class.unpacked_path)
+    end
+
+    def initialize(name, options = {})
+      require 'rubygems' unless Object.const_defined?(:Gem)
+
+      if options[:requirement]
+        req = options[:requirement]
+      elsif options[:version]
+        req = Gem::Requirement.create(options[:version])
+      else
+        req = Gem::Requirement.default
+      end
+
+      @dep = Gem::Dependency.new(name, req)
+      @lib      = options[:lib]
+      @source   = options[:source]
+      @loaded   = @frozen = @load_paths_added = false
+    end
+
+    def add_load_paths
+      self.class.add_frozen_gem_path
+      return if @loaded || @load_paths_added
+      if framework_gem?
+        @load_paths_added = @loaded = @frozen = true
+        return
+      end
+      gem @dep
+      @spec = Gem.loaded_specs[name]
+      @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec
+      @load_paths_added = true
+    rescue Gem::LoadError
+    end
+
+    def dependencies
+      return [] if framework_gem?
+      all_dependencies = specification.dependencies.map do |dependency|
+        GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
+      end
+      all_dependencies += all_dependencies.map(&:dependencies).flatten
+      all_dependencies.uniq
+    end
+
+    def gem_dir(base_directory)
+      File.join(base_directory, specification.full_name)
+    end
+
+    def spec_filename(base_directory)
+      File.join(gem_dir(base_directory), '.specification')
+    end
+
+    def load
+      return if @loaded || @load_paths_added == false
+      require(@lib || name) unless @lib == false
+      @loaded = true
+    rescue LoadError
+      puts $!.to_s
+      $!.backtrace.each { |b| puts b }
+    end
+
+    def name
+      @dep.name.to_s
+    end
+
+    def requirement
+      r = @dep.version_requirements
+      (r == Gem::Requirement.default) ? nil : r
+    end
+
+    def frozen?
+      @frozen ||= vendor_rails? || vendor_gem?
+    end
+
+    def loaded?
+      @loaded ||= begin
+        if vendor_rails?
+          true
+        elsif specification.nil?
+          false
+        else
+          # check if the gem is loaded by inspecting $"
+          # specification.files lists all the files contained in the gem
+          gem_files = specification.files
+          # select only the files contained in require_paths - typically in bin and lib
+          require_paths_regexp = Regexp.new("^(#{specification.require_paths*'|'})/")
+          gem_lib_files = gem_files.select { |f| require_paths_regexp.match(f) }
+          # chop the leading directory off - a typical file might be in
+          # lib/gem_name/file_name.rb, but it will be 'require'd as gem_name/file_name.rb
+          gem_lib_files.map! { |f| f.split('/', 2)[1] }
+          # if any of the files from the above list appear in $", the gem is assumed to
+          # have been loaded
+          !(gem_lib_files & $").empty?
+        end
+      end
+    end
+
+    def load_paths_added?
+      # always try to add load paths - even if a gem is loaded, it may not
+      # be a compatible version (ie random_gem 0.4 is loaded and a later spec
+      # needs >= 0.5 - gem 'random_gem' will catch this and error out)
+      @load_paths_added
+    end
+
+    def install
+      cmd = "#{gem_command} #{install_command.join(' ')}"
+      puts cmd
+      puts %x(#{cmd})
+    end
+
+    def unpack_to(directory)
+      FileUtils.mkdir_p directory
+      Dir.chdir directory do
+        Gem::GemRunner.new.run(unpack_command)
+      end
+
+      # Gem.activate changes the spec - get the original
+      real_spec = Gem::Specification.load(specification.loaded_from)
+      write_spec(directory, real_spec)
+
+    end
+
+    def write_spec(directory, spec)
+      # copy the gem's specification into GEMDIR/.specification so that
+      # we can access information about the gem on deployment systems
+      # without having the gem installed
+      File.open(spec_filename(directory), 'w') do |file|
+        file.puts spec.to_yaml
+      end
+    end
+
+    def refresh_spec(directory)
+      real_gems = Gem.source_index.installed_source_index
+      exact_dep = Gem::Dependency.new(name, "= #{specification.version}")
+      matches = real_gems.search(exact_dep)
+      installed_spec = matches.first
+      if File.exist?(File.dirname(spec_filename(directory)))
+        if installed_spec
+          # we have a real copy
+          # get a fresh spec - matches should only have one element
+          # note that there is no reliable method to check that the loaded
+          # spec is the same as the copy from real_gems - Gem.activate changes
+          # some of the fields
+          real_spec = Gem::Specification.load(matches.first.loaded_from)
+          write_spec(directory, real_spec)
+          puts "Reloaded specification for #{name} from installed gems."
+        else
+          # the gem isn't installed locally - write out our current specs
+          write_spec(directory, specification)
+          puts "Gem #{name} not loaded locally - writing out current spec."
+        end
+      else
+        if framework_gem?
+          puts "Gem directory for #{name} not found - check if it's loading before rails."
+        else
+          puts "Something bad is going on - gem directory not found for #{name}."
+        end
+      end
+    end
+
+    def ==(other)
+      self.name == other.name && self.requirement == other.requirement
+    end
+    alias_method :"eql?", :"=="
+
+    def hash
+      @dep.hash
+    end
+
+    def specification
+      # code repeated from Gem.activate. Find a matching spec, or the currently loaded version.
+      # error out if loaded version and requested version are incompatible.
+      @spec ||= begin
+        matches = Gem.source_index.search(@dep)
+        matches << @@framework_gems[name] if framework_gem?
+        if Gem.loaded_specs[name] then
+          # This gem is already loaded.  If the currently loaded gem is not in the
+          # list of candidate gems, then we have a version conflict.
+          existing_spec = Gem.loaded_specs[name]
+          unless matches.any? { |spec| spec.version == existing_spec.version } then
+            raise Gem::Exception,
+                  "can't activate #{@dep}, already activated #{existing_spec.full_name}"
+          end
+          # we're stuck with it, so change to match
+          @dep.version_requirements = Gem::Requirement.create("=#{existing_spec.version}")
+          existing_spec
+        else
+          # new load
+          matches.last
+        end
+      end
+    end
+
+    private
+      def gem_command
+        RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem'
+      end
+
+      def install_command
+        cmd = %w(install) << name
+        cmd << "--version" << %("#{requirement.to_s}") if requirement
+        cmd << "--source"  << @source  if @source
+        cmd
+      end
+
+      def unpack_command
+        cmd = %w(unpack) << name
+        cmd << "--version" << "= "+specification.version.to_s if requirement
+        cmd
+      end
+  end
+end