1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* lib/rubygems: Update to RubyGems master 4bdc4f2. Important changes

in this commit:

  RubyGems now chooses the test server port reliably.  Patch by akr.

  Partial implementation of bundler's Gemfile format.

  Refactorings to improve the new resolver.

  Fixes bugs in the resolver.

* test/rubygems:  Tests for the above.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43643 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
drbrain 2013-11-10 17:51:40 +00:00
parent 31d355aaa9
commit 4f6779bac7
75 changed files with 3143 additions and 616 deletions

View file

@ -3,16 +3,125 @@
class Gem::RequestSet::GemDependencyAPI
##
# The dependency groups created by #group in the dependency API file.
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],
}
attr_reader :dependency_groups
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 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+.
@ -21,9 +130,13 @@ class Gem::RequestSet::GemDependencyAPI
@set = set
@path = path
@current_groups = nil
@dependency_groups = Hash.new { |h, group| h[group] = [] }
@vendor_set = @set.vendor_set
@current_groups = nil
@current_platform = nil
@default_sources = true
@requires = Hash.new { |h, name| h[name] = [] }
@vendor_set = @set.vendor_set
@gem_sources = {}
@without_groups = []
end
##
@ -47,10 +160,32 @@ class Gem::RequestSet::GemDependencyAPI
options = requirements.pop if requirements.last.kind_of?(Hash)
options ||= {}
if directory = options.delete(:path) then
@vendor_set.add_vendor_gem name, directory
source_set = gem_path 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 :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) : []
@ -59,19 +194,81 @@ class Gem::RequestSet::GemDependencyAPI
all_groups |= @current_groups if @current_groups
unless all_groups.empty? then
all_groups.each do |group|
gem_arguments = [name, *requirements]
gem_arguments << options unless options.empty?
@dependency_groups[group] << gem_arguments
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
return
end
case WINDOWS[platform_name]
when :only then
next false unless Gem.win_platform?
when :never then
next false if Gem.win_platform?
end
@set.gem name, *requirements
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
##
# Returns the basename of the file the dependencies were loaded from
@ -96,9 +293,12 @@ class Gem::RequestSet::GemDependencyAPI
# :category: Gem Dependencies DSL
def platform what
if what == :ruby
yield
end
@current_platform = what
yield
ensure
@current_platform = nil
end
##
@ -112,23 +312,58 @@ class Gem::RequestSet::GemDependencyAPI
# +:engine+ options from Bundler are currently ignored.
def ruby version, options = {}
return true if version == RUBY_VERSION
engine = options[:engine]
engine_version = options[:engine_version]
message = "Your Ruby version is #{RUBY_VERSION}, " +
"but your #{gem_deps_file} specified #{version}"
raise ArgumentError,
'you must specify engine_version along with the ruby engine' if
engine and not engine_version
raise Gem::RubyVersionMismatch, message
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::DepedencyAPI = self # :nodoc:
Gem::RequestSet::GemDepedencyAPI = self # :nodoc:
end

View file

@ -0,0 +1,347 @@
require 'pathname'
class Gem::RequestSet::Lockfile
##
# Raised when a lockfile cannot be parsed
class ParseError < Gem::Exception
##
# The column where the error was encountered
attr_reader :column
##
# The line where the error was encountered
attr_reader :line
##
# The location of the lock file
attr_reader :path
##
# Raises a ParseError with the given +message+ which was encountered at a
# +line+ and +column+ while parsing.
def initialize message, line, column, path
@line = line
@column = column
@path = path
super "#{message} (at #{line}:#{column})"
end
end
##
# The platforms for this Lockfile
attr_reader :platforms
##
# Creates a new Lockfile for the given +request_set+ and +gem_deps_file+
# location.
def initialize request_set, gem_deps_file
@set = request_set
@gem_deps_file = Pathname(gem_deps_file).expand_path
@gem_deps_dir = @gem_deps_file.dirname
@current_token = nil
@line = 0
@line_pos = 0
@platforms = []
@tokens = []
end
def add_DEPENDENCIES out # :nodoc:
out << "DEPENDENCIES"
@set.dependencies.sort.map do |dependency|
source = @requests.find do |req|
req.name == dependency.name and
req.spec.class == Gem::DependencyResolver::VendorSpecification
end
source_dep = '!' if source
requirement = dependency.requirement
out << " #{dependency.name}#{source_dep}#{requirement.for_lockfile}"
end
out << nil
end
def add_GEM out # :nodoc:
out << "GEM"
source_groups = @spec_groups.values.flatten.group_by do |request|
request.spec.source.uri
end
source_groups.map do |group, requests|
out << " remote: #{group}"
out << " specs:"
requests.sort_by { |request| request.name }.each do |request|
platform = "-#{request.spec.platform}" unless
Gem::Platform::RUBY == request.spec.platform
out << " #{request.name} (#{request.version}#{platform})"
request.full_spec.dependencies.sort.each do |dependency|
requirement = dependency.requirement
out << " #{dependency.name}#{requirement.for_lockfile}"
end
end
end
out << nil
end
def add_PATH out # :nodoc:
return unless path_requests =
@spec_groups.delete(Gem::DependencyResolver::VendorSpecification)
out << "PATH"
path_requests.each do |request|
directory = Pathname(request.spec.source.uri).expand_path
out << " remote: #{directory.relative_path_from @gem_deps_dir}"
out << " specs:"
out << " #{request.name} (#{request.version})"
end
out << nil
end
def add_PLATFORMS out # :nodoc:
out << "PLATFORMS"
platforms = @requests.map { |request| request.spec.platform }.uniq
platforms.delete Gem::Platform::RUBY if platforms.length > 1
platforms.each do |platform|
out << " #{platform}"
end
out << nil
end
##
# Gets the next token for a Lockfile
def get expected_type = nil, expected_value = nil # :nodoc:
@current_token = @tokens.shift
type, value, line, column = @current_token
if expected_type and expected_type != type then
unget
message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
"expected #{expected_type.inspect}"
raise ParseError.new message, line, column, "#{@gem_deps_file}.lock"
end
if expected_value and expected_value != value then
unget
message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
"expected [#{expected_type.inspect}, #{expected_value.inspect}]"
raise ParseError.new message, line, column, "#{@gem_deps_file}.lock"
end
@current_token
end
def parse # :nodoc:
tokenize
until @tokens.empty? do
type, data, column, line = get
case type
when :section then
skip :newline
case data
when 'DEPENDENCIES' then
parse_DEPENDENCIES
when 'GEM' then
parse_GEM
when 'PLATFORMS' then
parse_PLATFORMS
else
type, = get until @tokens.empty? or peek.first == :section
end
else
raise "BUG: unhandled token #{type} (#{data.inspect}) at #{line}:#{column}"
end
end
end
def parse_DEPENDENCIES # :nodoc:
while not @tokens.empty? and :text == peek.first do
_, name, = get :text
@set.gem name
skip :newline
end
end
def parse_GEM # :nodoc:
get :entry, 'remote'
_, data, = get :text
source = Gem::Source.new data
skip :newline
get :entry, 'specs'
skip :newline
set = Gem::DependencyResolver::LockSet.new source
while not @tokens.empty? and :text == peek.first do
_, name, = get :text
case peek[0]
when :newline then # ignore
when :l_paren then
get :l_paren
_, version, = get :text
get :r_paren
set.add name, version, Gem::Platform::RUBY
else
raise "BUG: unknown token #{peek}"
end
skip :newline
end
@set.sets << set
end
def parse_PLATFORMS # :nodoc:
while not @tokens.empty? and :text == peek.first do
_, name, = get :text
@platforms << name
skip :newline
end
end
##
# Peeks at the next token for Lockfile
def peek # :nodoc:
@tokens.first
end
def skip type # :nodoc:
get while not @tokens.empty? and peek.first == type
end
def to_s
@set.resolve
out = []
@requests = @set.sorted_requests
@spec_groups = @requests.group_by do |request|
request.spec.class
end
add_PATH out
add_GEM out
add_PLATFORMS out
add_DEPENDENCIES out
out.join "\n"
end
##
# Calculates the column (by byte) and the line of the current token based on
# +byte_offset+.
def token_pos byte_offset # :nodoc:
[byte_offset - @line_pos, @line]
end
def tokenize # :nodoc:
@line = 0
@line_pos = 0
@platforms = []
@tokens = []
@current_token = nil
lock_file = "#{@gem_deps_file}.lock"
@input = File.read lock_file
s = StringScanner.new @input
until s.eos? do
pos = s.pos
# leading whitespace is for the user's convenience
next if s.scan(/ +/)
if s.scan(/[<|=>]{7}/) then
message = "your #{lock_file} contains merge conflict markers"
line, column = token_pos pos
raise ParseError.new message, line, column, lock_file
end
@tokens <<
case
when s.scan(/\r?\n/) then
token = [:newline, nil, *token_pos(pos)]
@line_pos = s.pos
@line += 1
token
when s.scan(/[A-Z]+/) then
[:section, s.matched, *token_pos(pos)]
when s.scan(/([a-z]+):\s/) then
s.pos -= 1 # rewind for possible newline
[:entry, s[1], *token_pos(pos)]
when s.scan(/\(/) then
[:l_paren, nil, *token_pos(pos)]
when s.scan(/\)/) then
[:r_paren, nil, *token_pos(pos)]
when s.scan(/[^\s)]*/) then
[:text, s.matched, *token_pos(pos)]
else
raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}"
end
end
@tokens
end
##
# Ungets the last token retrieved by #get
def unget # :nodoc:
@tokens.unshift @current_token
end
end