aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails/gem_dependency.rb
diff options
context:
space:
mode:
authorMatt Jones <al2o3cr@gmail.com>2008-10-04 13:51:23 -0400
committerrick <technoweenie@gmail.com>2008-10-05 10:16:17 -0700
commit2bf58aa782d3b493f2d98f153324b93c5b058ba6 (patch)
tree53278af87e3590bb901754b2e2f8ce1d13d49d73 /railties/lib/rails/gem_dependency.rb
parent4f53db0096be4b167628a136044b0d1262215277 (diff)
downloadrails-2bf58aa782d3b493f2d98f153324b93c5b058ba6.tar.gz
rails-2bf58aa782d3b493f2d98f153324b93c5b058ba6.tar.bz2
rails-2bf58aa782d3b493f2d98f153324b93c5b058ba6.zip
Fix a number of errors in the config.gem mechanism.
* Rails::GemDependency was missing definitions for hash and eql?, causing Array#uniq to not work. * If several versions of a gem are unpacked in vendor, now chooses the highest if no version is specified. * streamlined add_load_path. Now sets up Rubygems correctly to allow 'gem' to find frozen gems, with gems frozen to vendor/gems and specifications in vendor/gems/<gem-name>/.specification * Rails::GemDependency#specification would return a spec for the highest installed version, even for frozen gems and/or previously loaded lower versions. See in part ticket #1123. * removed vendor from default_load_paths - it was causing autoloading to append Gems::Gems::<gem-dir> to constant names * added additional tests for loading frozen gems. * incorporates the fix from #1107 for vendor rails * defers to freeze:gems for handling the Rails framework. gems:unpack WILL NOT place a copy of Rails in vendor/gems. Should close #1123 completely. * incorporates, via using the gem loader for frozen gems, fixes corresponding to #227, #324, #362, #527, and #742. * gem plugins now work the same whether frozen or not. Correctness of the behavior is a matter for another ticket... Signed-off-by: rick <technoweenie@gmail.com>
Diffstat (limited to 'railties/lib/rails/gem_dependency.rb')
-rw-r--r--railties/lib/rails/gem_dependency.rb142
1 files changed, 111 insertions, 31 deletions
diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb
index d58ae450eb..3b2f4846a6 100644
--- a/railties/lib/rails/gem_dependency.rb
+++ b/railties/lib/rails/gem_dependency.rb
@@ -1,50 +1,76 @@
+require 'rails/vendor_gem_source_index'
+
+module Gem
+ def self.source_index=(index)
+ @@source_index = index
+ end
+end
+
module Rails
class GemDependency
- attr_accessor :name, :requirement, :version, :lib, :source
+ 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
+ Gem.source_index = Rails::VendorGemSourceIndex.new(Gem.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
+ 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]
- @requirement = options[:requirement]
+ req = options[:requirement]
elsif options[:version]
- @requirement = Gem::Requirement.create(options[:version])
+ req = Gem::Requirement.create(options[:version])
+ else
+ req = Gem::Requirement.default
end
- @version = @requirement.instance_variable_get("@requirements").first.last if @requirement
- @name = name.to_s
+ @dep = Gem::Dependency.new(name, req)
@lib = options[:lib]
@source = options[:source]
@loaded = @frozen = @load_paths_added = false
- @unpack_directory = nil
- end
-
- def unpacked_paths
- Dir[File.join(self.class.unpacked_path, "#{@name}-#{@version || "*"}")]
end
def add_load_paths
+ self.class.add_frozen_gem_path
return if @loaded || @load_paths_added
- unpacked_paths = self.unpacked_paths
- if unpacked_paths.empty?
- args = [@name]
- args << @requirement.to_s if @requirement
- gem *args
- else
- $LOAD_PATH.unshift File.join(unpacked_paths.first, 'lib')
- ext = File.join(unpacked_paths.first, 'ext')
- $LOAD_PATH.unshift(ext) if File.exist?(ext)
- @frozen = true
+ 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
@@ -58,22 +84,51 @@ module Rails
def load
return if @loaded || @load_paths_added == false
- require(@lib || @name) unless @lib == 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
+ @frozen ||= vendor_rails? || vendor_gem?
end
def loaded?
- @loaded
+ @loaded ||= begin
+ if vendor_rails?
+ true
+ 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
@@ -93,17 +148,44 @@ module Rails
# we can access information about the gem on deployment systems
# without having the gem installed
spec_filename = File.join(gem_dir(directory), '.specification')
+ # Gem.activate changes the spec - get the original
+ spec = Gem::Specification.load(specification.loaded_from)
File.open(spec_filename, 'w') do |file|
- file.puts specification.to_yaml
+ file.puts spec.to_yaml
end
end
def ==(other)
self.name == other.name && self.requirement == other.requirement
end
+ alias_method :"eql?", :"=="
+
+ def hash
+ @dep.hash
+ end
def specification
- @spec ||= Gem.source_index.search(Gem::Dependency.new(@name, @requirement)).sort_by { |s| s.version }.last
+ # 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
@@ -112,17 +194,15 @@ module Rails
end
def install_command
- cmd = %w(install) << @name
- cmd << "--version" << %("#{@requirement.to_s}") if @requirement
+ 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
- # We don't quote this requirement as it's run through GemRunner instead
- # of shelling out to gem
- cmd << "--version" << @requirement.to_s if @requirement
+ cmd = %w(unpack) << name
+ cmd << "--version" << "= "+specification.version.to_s if requirement
cmd
end
end