mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
9eab435631
Signed-off-by: Yehuda Katz + Carl Lerche <ykatz+clerche@engineyard.com>
311 lines
9.6 KiB
Ruby
311 lines
9.6 KiB
Ruby
require 'rails/vendor_gem_source_index'
|
|
|
|
module Gem
|
|
def self.source_index=(index)
|
|
@@source_index = index
|
|
end
|
|
end
|
|
|
|
module Rails
|
|
class GemDependency < Gem::Dependency
|
|
attr_accessor :lib, :source, :dep
|
|
|
|
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 self.from_directory_name(directory_name, load_spec=true)
|
|
directory_name_parts = File.basename(directory_name).split('-')
|
|
name = directory_name_parts[0..-2].join('-')
|
|
version = directory_name_parts.last
|
|
result = self.new(name, :version => version)
|
|
spec_filename = File.join(directory_name, '.specification')
|
|
if load_spec
|
|
raise "Missing specification file in #{File.dirname(spec_filename)}. Perhaps you need to do a 'rake gems:refresh_specs'?" unless File.exists?(spec_filename)
|
|
spec = YAML::load_file(spec_filename)
|
|
result.specification = spec
|
|
end
|
|
result
|
|
rescue ArgumentError => e
|
|
raise "Unable to determine gem name and version from '#{directory_name}'"
|
|
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
|
|
|
|
@lib = options[:lib]
|
|
@source = options[:source]
|
|
@loaded = @frozen = @load_paths_added = false
|
|
|
|
super(name, req)
|
|
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 self
|
|
@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?
|
|
return [] unless installed?
|
|
specification.dependencies.reject do |dependency|
|
|
dependency.type == :development
|
|
end.map do |dependency|
|
|
GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
|
|
end
|
|
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(self)
|
|
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
|
|
version_requirements = Gem::Requirement.create("=#{existing_spec.version}")
|
|
existing_spec
|
|
else
|
|
# new load
|
|
matches.last
|
|
end
|
|
end
|
|
end
|
|
|
|
def specification=(s)
|
|
@spec = s
|
|
end
|
|
|
|
def requirement
|
|
r = version_requirements
|
|
(r == Gem::Requirement.default) ? nil : r
|
|
end
|
|
|
|
def built?
|
|
return false unless frozen?
|
|
|
|
if vendor_gem?
|
|
specification.extensions.each do |ext|
|
|
makefile = File.join(unpacked_gem_directory, File.dirname(ext), 'Makefile')
|
|
return false unless File.exists?(makefile)
|
|
end
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def framework_gem?
|
|
@@framework_gems.has_key?(name)
|
|
end
|
|
|
|
def frozen?
|
|
@frozen ||= vendor_rails? || vendor_gem?
|
|
end
|
|
|
|
def installed?
|
|
Gem.loaded_specs.keys.include?(name)
|
|
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 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 vendor_rails?
|
|
Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty?
|
|
end
|
|
|
|
def vendor_gem?
|
|
specification && File.exists?(unpacked_gem_directory)
|
|
end
|
|
|
|
def build(options={})
|
|
require 'rails/gem_builder'
|
|
return if specification.nil?
|
|
if options[:force] || !built?
|
|
return unless File.exists?(unpacked_specification_filename)
|
|
spec = YAML::load_file(unpacked_specification_filename)
|
|
Rails::GemBuilder.new(spec, unpacked_gem_directory).build_extensions
|
|
puts "Built gem: '#{unpacked_gem_directory}'"
|
|
end
|
|
dependencies.each { |dep| dep.build(options) }
|
|
end
|
|
|
|
def install
|
|
unless installed?
|
|
cmd = "#{gem_command} #{install_command.join(' ')}"
|
|
puts cmd
|
|
puts %x(#{cmd})
|
|
end
|
|
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 refresh
|
|
Rails::VendorGemSourceIndex.silence_spec_warnings = true
|
|
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 frozen?
|
|
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_specification(real_spec)
|
|
puts "Reloaded specification for #{name} from installed gems."
|
|
else
|
|
# the gem isn't installed locally - write out our current specs
|
|
write_specification(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 unpack(options={})
|
|
unless frozen? || framework_gem?
|
|
FileUtils.mkdir_p unpack_base
|
|
Dir.chdir unpack_base 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_specification(real_spec)
|
|
end
|
|
dependencies.each { |dep| dep.unpack(options) } if options[:recursive]
|
|
end
|
|
|
|
def write_specification(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(unpacked_specification_filename, 'w') do |file|
|
|
file.puts spec.to_yaml
|
|
end
|
|
end
|
|
|
|
def ==(other)
|
|
self.name == other.name && self.requirement == other.requirement
|
|
end
|
|
alias_method :"eql?", :"=="
|
|
|
|
private
|
|
|
|
def gem_command
|
|
case RUBY_PLATFORM
|
|
when /win32/
|
|
'gem.bat'
|
|
when /java/
|
|
'jruby -S gem'
|
|
else
|
|
'gem'
|
|
end
|
|
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
|
|
|
|
def unpack_base
|
|
Rails::GemDependency.unpacked_path
|
|
end
|
|
|
|
def unpacked_gem_directory
|
|
File.join(unpack_base, specification.full_name)
|
|
end
|
|
|
|
def unpacked_specification_filename
|
|
File.join(unpacked_gem_directory, '.specification')
|
|
end
|
|
|
|
end
|
|
end
|