aboutsummaryrefslogtreecommitdiffstats
path: root/lib/rubygems/request_set/lockfile.rb
diff options
context:
space:
mode:
authordrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-11-10 17:51:40 +0000
committerdrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-11-10 17:51:40 +0000
commit4f6779bac7b4e294bc473782d60cbd071f0d0f8d (patch)
treed37b54da20f8c0adf2d98e810aacc8259b0602ff /lib/rubygems/request_set/lockfile.rb
parent31d355aaa9436e2b24efd5e6501cabd876267c46 (diff)
downloadruby-4f6779bac7b4e294bc473782d60cbd071f0d0f8d.tar.gz
* 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
Diffstat (limited to 'lib/rubygems/request_set/lockfile.rb')
-rw-r--r--lib/rubygems/request_set/lockfile.rb347
1 files changed, 347 insertions, 0 deletions
diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb
new file mode 100644
index 0000000000..a9c419549d
--- /dev/null
+++ b/lib/rubygems/request_set/lockfile.rb
@@ -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
+