ruby/lib/rubygems/request_set/gem_dependency_api.rb
drbrain a7fa4d5d9a * lib/rubygems: Update to RubyGems master 6a3d9f9. Changes include:
Compatibly renamed Gem::DependencyResolver to Gem::Resolver.

  Added support for git gems in gem.deps.rb and Gemfile.

  Fixed resolver bugs.

* test/rubygems: ditto.

* lib/rubygems/LICENSE.txt:  Updated to license from RubyGems trunk.
  [ruby-trunk - Bug #9086]

* lib/rubygems/commands/which_command.rb:  RubyGems now indicates
  failure when any file is missing.  [ruby-trunk - Bug #9004]

* lib/rubygems/ext/builder:  Extensions are now installed into the
  extension install directory and the first directory in the require
  path from the gem.  This allows backwards compatibility with msgpack
  and other gems that calculate full require paths.
  [ruby-trunk - Bug #9106]



git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43714 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2013-11-19 00:34:13 +00:00

502 lines
12 KiB
Ruby

##
# A semi-compatible DSL for the Bundler Gemfile and Isolate formats.
class Gem::RequestSet::GemDependencyAPI
ENGINE_MAP = { # :nodoc:
:jruby => %w[jruby],
:jruby_18 => %w[jruby],
:jruby_19 => %w[jruby],
:maglev => %w[maglev],
:mri => %w[ruby],
:mri_18 => %w[ruby],
:mri_19 => %w[ruby],
:mri_20 => %w[ruby],
:mri_21 => %w[ruby],
:rbx => %w[rbx],
:ruby => %w[ruby rbx maglev],
:ruby_18 => %w[ruby rbx maglev],
:ruby_19 => %w[ruby rbx maglev],
:ruby_20 => %w[ruby rbx maglev],
:ruby_21 => %w[ruby rbx maglev],
}
x86_mingw = Gem::Platform.new 'x86-mingw32'
x64_mingw = Gem::Platform.new 'x64-mingw32'
PLATFORM_MAP = { # :nodoc:
:jruby => Gem::Platform::RUBY,
:jruby_18 => Gem::Platform::RUBY,
:jruby_19 => Gem::Platform::RUBY,
:maglev => Gem::Platform::RUBY,
:mingw => x86_mingw,
:mingw_18 => x86_mingw,
:mingw_19 => x86_mingw,
:mingw_20 => x86_mingw,
:mingw_21 => x86_mingw,
:mri => Gem::Platform::RUBY,
:mri_18 => Gem::Platform::RUBY,
:mri_19 => Gem::Platform::RUBY,
:mri_20 => Gem::Platform::RUBY,
:mri_21 => Gem::Platform::RUBY,
:mswin => Gem::Platform::RUBY,
:rbx => Gem::Platform::RUBY,
:ruby => Gem::Platform::RUBY,
:ruby_18 => Gem::Platform::RUBY,
:ruby_19 => Gem::Platform::RUBY,
:ruby_20 => Gem::Platform::RUBY,
:ruby_21 => Gem::Platform::RUBY,
:x64_mingw => x64_mingw,
:x64_mingw_20 => x64_mingw,
:x64_mingw_21 => x64_mingw
}
gt_eq_0 = Gem::Requirement.new '>= 0'
tilde_gt_1_8_0 = Gem::Requirement.new '~> 1.8.0'
tilde_gt_1_9_0 = Gem::Requirement.new '~> 1.9.0'
tilde_gt_2_0_0 = Gem::Requirement.new '~> 2.0.0'
tilde_gt_2_1_0 = Gem::Requirement.new '~> 2.1.0'
VERSION_MAP = { # :nodoc:
:jruby => gt_eq_0,
:jruby_18 => tilde_gt_1_8_0,
:jruby_19 => tilde_gt_1_9_0,
:maglev => gt_eq_0,
:mingw => gt_eq_0,
:mingw_18 => tilde_gt_1_8_0,
:mingw_19 => tilde_gt_1_9_0,
:mingw_20 => tilde_gt_2_0_0,
:mingw_21 => tilde_gt_2_1_0,
:mri => gt_eq_0,
:mri_18 => tilde_gt_1_8_0,
:mri_19 => tilde_gt_1_9_0,
:mri_20 => tilde_gt_2_0_0,
:mri_21 => tilde_gt_2_1_0,
:mswin => gt_eq_0,
:rbx => gt_eq_0,
:ruby => gt_eq_0,
:ruby_18 => tilde_gt_1_8_0,
:ruby_19 => tilde_gt_1_9_0,
:ruby_20 => tilde_gt_2_0_0,
:ruby_21 => tilde_gt_2_1_0,
:x64_mingw => gt_eq_0,
:x64_mingw_20 => tilde_gt_2_0_0,
:x64_mingw_21 => tilde_gt_2_1_0,
}
WINDOWS = { # :nodoc:
:mingw => :only,
:mingw_18 => :only,
:mingw_19 => :only,
:mingw_20 => :only,
:mingw_21 => :only,
:mri => :never,
:mri_18 => :never,
:mri_19 => :never,
:mri_20 => :never,
:mri_21 => :never,
:mswin => :only,
:rbx => :never,
:ruby => :never,
:ruby_18 => :never,
:ruby_19 => :never,
:ruby_20 => :never,
:ruby_21 => :never,
:x64_mingw => :only,
:x64_mingw_20 => :only,
:x64_mingw_21 => :only,
}
##
# A set of gems that are loaded via the +:git+ option to #gem
attr_reader :git_set # :nodoc:
##
# A Hash containing gem names and files to require from those gems.
attr_reader :requires
##
# A set of gems that are loaded via the +:path+ option to #gem
attr_reader :vendor_set # :nodoc:
##
# The groups of gems to exclude from installation
attr_accessor :without_groups
##
# Creates a new GemDependencyAPI that will add dependencies to the
# Gem::RequestSet +set+ based on the dependency API description in +path+.
def initialize set, path
@set = set
@path = path
@current_groups = nil
@current_platform = nil
@current_repository = nil
@default_sources = true
@git_set = @set.git_set
@requires = Hash.new { |h, name| h[name] = [] }
@vendor_set = @set.vendor_set
@gem_sources = {}
@without_groups = []
end
##
# Adds +dependencies+ to the request set if any of the +groups+ are allowed.
# This is used for gemspec dependencies.
def add_dependencies groups, dependencies # :nodoc:
return unless (groups & @without_groups).empty?
dependencies.each do |dep|
@set.gem dep.name, *dep.requirement
end
end
private :add_dependencies
##
# Finds a gemspec with the given +name+ that lives at +path+.
def find_gemspec name, path # :nodoc:
glob = File.join path, "#{name}.gemspec"
spec_files = Dir[glob]
case spec_files.length
when 1 then
spec_file = spec_files.first
spec = Gem::Specification.load spec_file
return spec if spec
raise ArgumentError, "invalid gemspec #{spec_file}"
when 0 then
raise ArgumentError, "no gemspecs found at #{Dir.pwd}"
else
raise ArgumentError,
"found multiple gemspecs at #{Dir.pwd}, " +
"use the name: option to specify the one you want"
end
end
##
# Loads the gem dependency file
def load
instance_eval File.read(@path).untaint, @path, 1
end
##
# :category: Gem Dependencies DSL
# :call-seq:
# gem(name)
# gem(name, *requirements)
# gem(name, *requirements, options)
#
# Specifies a gem dependency with the given +name+ and +requirements+. You
# may also supply +options+ following the +requirements+
def gem name, *requirements
options = requirements.pop if requirements.last.kind_of?(Hash)
options ||= {}
options[:git] = @current_repository if @current_repository
source_set = false
source_set ||= gem_path name, options
source_set ||= gem_git name, options
source_set ||= gem_github name, options
return unless gem_platforms options
groups = gem_group name, options
return unless (groups & @without_groups).empty?
unless source_set then
raise ArgumentError,
"duplicate source (default) for gem #{name}" if
@gem_sources.include? name
@gem_sources[name] = :default
end
gem_requires name, options
@set.gem name, *requirements
end
##
# Handles the git: option from +options+ for gem +name+.
#
# Returns +true+ if the path option was handled.
def gem_git name, options # :nodoc:
if gist = options.delete(:gist) then
options[:git] = "https://gist.github.com/#{gist}.git"
end
return unless repository = options.delete(:git)
raise ArgumentError,
"duplicate source git: #{repository} for gem #{name}" if
@gem_sources.include? name
reference = nil
reference ||= options.delete :ref
reference ||= options.delete :branch
reference ||= options.delete :tag
reference ||= 'master'
submodules = options.delete :submodules
@git_set.add_git_gem name, repository, reference, submodules
@gem_sources[name] = repository
true
end
private :gem_git
##
# Handles the github: option from +options+ for gem +name+.
#
# Returns +true+ if the path option was handled.
def gem_github name, options # :nodoc:
return unless path = options.delete(:github)
options[:git] = "git://github.com/#{path}.git"
gem_git name, options
true
end
##
# Handles the :group and :groups +options+ for the gem with the given
# +name+.
def gem_group name, options # :nodoc:
g = options.delete :group
all_groups = g ? Array(g) : []
groups = options.delete :groups
all_groups |= groups if groups
all_groups |= @current_groups if @current_groups
all_groups
end
private :gem_group
##
# Handles the path: option from +options+ for gem +name+.
#
# Returns +true+ if the path option was handled.
def gem_path name, options # :nodoc:
return unless directory = options.delete(:path)
raise ArgumentError,
"duplicate source path: #{directory} for gem #{name}" if
@gem_sources.include? name
@vendor_set.add_vendor_gem name, directory
@gem_sources[name] = directory
true
end
private :gem_path
##
# Handles the platforms: option from +options+. Returns true if the
# platform matches the current platform.
def gem_platforms options # :nodoc:
platform_names = Array(options.delete :platforms)
platform_names << @current_platform if @current_platform
return true if platform_names.empty?
platform_names.any? do |platform_name|
raise ArgumentError, "unknown platform #{platform_name.inspect}" unless
platform = PLATFORM_MAP[platform_name]
next false unless Gem::Platform.match platform
if engines = ENGINE_MAP[platform_name] then
next false unless engines.include? Gem.ruby_engine
end
case WINDOWS[platform_name]
when :only then
next false unless Gem.win_platform?
when :never then
next false if Gem.win_platform?
end
VERSION_MAP[platform_name].satisfied_by? Gem.ruby_version
end
end
private :gem_platforms
##
# Handles the require: option from +options+ and adds those files, or the
# default file to the require list for +name+.
def gem_requires name, options # :nodoc:
if options.include? :require then
if requires = options.delete(:require) then
@requires[name].concat requires
end
else
@requires[name] << name
end
end
private :gem_requires
def git repository
@current_repository = repository
yield
ensure
@current_repository = nil
end
##
# Returns the basename of the file the dependencies were loaded from
def gem_deps_file # :nodoc:
File.basename @path
end
##
# :category: Gem Dependencies DSL
#
# Loads dependencies from a gemspec file.
def gemspec options = {}
name = options.delete(:name) || '{,*}'
path = options.delete(:path) || '.'
development_group = options.delete(:development_group) || :development
spec = find_gemspec name, path
groups = gem_group spec.name, {}
add_dependencies groups, spec.runtime_dependencies
groups << development_group
add_dependencies groups, spec.development_dependencies
gem_requires spec.name, options
end
##
# :category: Gem Dependencies DSL
# Block form for placing a dependency in the given +groups+.
def group *groups
@current_groups = groups
yield
ensure
@current_groups = nil
end
##
# :category: Gem Dependencies DSL
def platform what
@current_platform = what
yield
ensure
@current_platform = nil
end
##
# :category: Gem Dependencies DSL
alias :platforms :platform
##
# :category: Gem Dependencies DSL
# Restricts this gem dependencies file to the given ruby +version+. The
# +:engine+ options from Bundler are currently ignored.
def ruby version, options = {}
engine = options[:engine]
engine_version = options[:engine_version]
raise ArgumentError,
'you must specify engine_version along with the ruby engine' if
engine and not engine_version
unless RUBY_VERSION == version then
message = "Your Ruby version is #{RUBY_VERSION}, " +
"but your #{gem_deps_file} requires #{version}"
raise Gem::RubyVersionMismatch, message
end
if engine and engine != Gem.ruby_engine then
message = "Your ruby engine is #{Gem.ruby_engine}, " +
"but your #{gem_deps_file} requires #{engine}"
raise Gem::RubyVersionMismatch, message
end
if engine_version then
my_engine_version = Object.const_get "#{Gem.ruby_engine.upcase}_VERSION"
if engine_version != my_engine_version then
message =
"Your ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " +
"but your #{gem_deps_file} requires #{engine} #{engine_version}"
raise Gem::RubyVersionMismatch, message
end
end
return true
end
##
# :category: Gem Dependencies DSL
#
# Sets +url+ as a source for gems for this dependency API.
def source url
Gem.sources.clear if @default_sources
@default_sources = false
Gem.sources << url
end
# TODO: remove this typo name at RubyGems 3.0
Gem::RequestSet::GemDepedencyAPI = self # :nodoc:
end