提交 9f158709 编写于 作者: M Matt Jones 提交者: Michael Koziarski

Make VendorGemSourceIndex handle broken/missing specs generated by previous versions.

Signed-off-by: NMichael Koziarski <michael@koziarski.com>
上级 0d4dbb3d
......@@ -82,6 +82,10 @@ 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
......@@ -146,17 +150,42 @@ def unpack_to(directory)
Gem::GemRunner.new.run(unpack_command)
end
# Gem.activate changes the spec - get the original
real_spec = Gem::Specification.load(spec.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
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.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 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
end
def ==(other)
self.name == other.name && self.requirement == other.requirement
end
......
......@@ -13,6 +13,14 @@ class VendorGemSourceIndex
attr_reader :installed_source_index
attr_reader :vendor_source_index
def self.silence_spec_warnings
@@silence_spec_warnings
end
def self.silence_spec_warnings=(v)
@@silence_spec_warnings = v
end
def initialize(installed_index, vendor_dir=Rails::GemDependency.unpacked_path)
@installed_source_index = installed_index
@vendor_dir = vendor_dir
......@@ -33,31 +41,68 @@ def refresh!
# load specifications from vendor/gems
Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |d|
dir_name = File.basename(d)
dir_version = version_for_dir(dir_name)
spec = load_specification(d)
next unless spec
# NOTE: this is a bit of a hack - the gem system expects a different structure
# than we have.
# It's looking for:
# repository
# -> specifications
# - gem_name.spec <= loaded_from points to this
# -> gems
# - gem_name <= gem files here
# and therefore goes up one directory from loaded_from, then adds gems/gem_name
# to the path.
# But we have:
# vendor
# -> gems
# -> gem_name <= gem files here
# - .specification
# so we set loaded_from to vendor/gems/.specification (not a real file) to
# get the correct behavior.
spec.loaded_from = File.join(Rails::GemDependency.unpacked_path, '.specification')
if spec
if spec.full_name != dir_name
# mismatched directory name and gem spec - produced by 2.1.0-era unpack code
if dir_version
# fix the spec version - this is not optimal (spec.files may be wrong)
# but it's better than breaking apps. Complain to remind users to get correct specs.
# use ActiveSupport::Deprecation.warn, as the logger is not set yet
$stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has a mismatched specification file."+
" Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings
spec.version = dir_version
else
$stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems is not in a versioned directory"+
"(should be #{spec.full_name}).") unless @@silence_spec_warnings
# continue, assume everything is OK
end
end
else
# no spec - produced by early-2008 unpack code
# emulate old behavior, and complain.
$stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has no specification file."+
" Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings
if dir_version
spec = Gem::Specification.new
spec.version = dir_version
spec.require_paths = ['lib']
ext_path = File.join(d, 'ext')
spec.require_paths << 'ext' if File.exist?(ext_path)
spec.name = /^(.*)-[^-]+$/.match(dir_name)[1]
files = ['lib']
# set files to everything in lib/
files += Dir[File.join(d, 'lib', '*')].map { |v| v.gsub(/^#{d}\//, '') }
files += Dir[File.join(d, 'ext', '*')].map { |v| v.gsub(/^#{d}\//, '') } if ext_path
spec.files = files
else
$stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems not in a versioned directory."+
" Giving up.") unless @silence_spec_warnings
next
end
end
spec.loaded_from = File.join(d, '.specification')
# finally, swap out full_gem_path
# it would be better to use a Gem::Specification subclass, but the YAML loads an explicit class
class << spec
def full_gem_path
path = File.join installation_path, full_name
return path if File.directory? path
File.join installation_path, original_name
end
end
vendor_gems[File.basename(d)] = spec
end
@vendor_source_index = Gem::SourceIndex.new(vendor_gems)
end
def version_for_dir(d)
matches = /-([^-]+)$/.match(d)
Gem::Version.new(matches[1]) if matches
end
def load_specification(gem_dir)
spec_file = File.join(gem_dir, '.specification')
YAML.load_file(spec_file) if File.exist?(spec_file)
......
......@@ -65,4 +65,14 @@ namespace :gems do
end
end
end
desc "Regenerate gem specifications in correct format."
task :refresh_specs => :base do
require 'rubygems'
require 'rubygems/gem_runner'
Rails.configuration.gems.each do |gem|
next unless gem.frozen? && (ENV['GEM'].blank? || ENV['GEM'] == gem.name)
gem.refresh_spec(Rails::GemDependency.unpacked_path) if gem.loaded?
end
end
end
\ No newline at end of file
require 'lib/rails/vendor_gem_source_index'
Rails::VendorGemSourceIndex.silence_spec_warnings = true
require 'plugin_test_helper'
class Rails::GemDependency
......@@ -110,5 +113,22 @@ def test_gem_load_frozen_minimum_version
assert_equal '0.6.0', DUMMY_GEM_C_VERSION
end
def test_gem_load_missing_specification
dummy_gem = Rails::GemDependency.new "dummy-gem-d"
dummy_gem.add_load_paths
dummy_gem.load
assert_not_nil DUMMY_GEM_D_VERSION
assert_equal '1.0.0', DUMMY_GEM_D_VERSION
assert_equal ['lib', 'lib/dummy-gem-d.rb'], dummy_gem.specification.files
end
def test_gem_load_bad_specification
dummy_gem = Rails::GemDependency.new "dummy-gem-e", :version => "= 1.0.0"
dummy_gem.add_load_paths
dummy_gem.load
assert_not_nil DUMMY_GEM_E_VERSION
assert_equal '1.0.0', DUMMY_GEM_E_VERSION
end
end
end
--- !ruby/object:Gem::Specification
name: dummy-gem-e
version: !ruby/object:Gem::Version
version: 1.3.0
platform: ruby
authors:
- "Nobody"
date: 2008-10-03 00:00:00 -04:00
files:
- lib
- lib/dummy-gem-e.rb
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
specification_version: 2
summary: Dummy Gem E
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册