summaryrefslogtreecommitdiff
path: root/lib/rubygems/request_set/lockfile.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/request_set/lockfile.rb')
-rw-r--r--lib/rubygems/request_set/lockfile.rb233
1 files changed, 233 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..8b9c9690d6
--- /dev/null
+++ b/lib/rubygems/request_set/lockfile.rb
@@ -0,0 +1,233 @@
+# frozen_string_literal: true
+
+##
+# Parses a gem.deps.rb.lock file and constructs a LockSet containing the
+# dependencies found inside. If the lock file is missing no LockSet is
+# constructed.
+
+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, column, line, path)
+ @line = line
+ @column = column
+ @path = path
+ super "#{message} (at line #{line} column #{column})"
+ end
+ end
+
+ ##
+ # Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+
+ # location.
+
+ def self.build(request_set, gem_deps_file, dependencies = nil)
+ request_set.resolve
+ dependencies ||= requests_to_deps request_set.sorted_requests
+ new request_set, gem_deps_file, dependencies
+ end
+
+ def self.requests_to_deps(requests) # :nodoc:
+ deps = {}
+
+ requests.each do |request|
+ spec = request.spec
+ name = request.name
+ requirement = request.request.dependency.requirement
+
+ deps[name] = if [Gem::Resolver::VendorSpecification,
+ Gem::Resolver::GitSpecification].include? spec.class
+ Gem::Requirement.source_set
+ else
+ requirement
+ end
+ end
+
+ deps
+ end
+
+ ##
+ # The platforms for this Lockfile
+
+ attr_reader :platforms
+
+ def initialize(request_set, gem_deps_file, dependencies)
+ @set = request_set
+ @dependencies = dependencies
+ @gem_deps_file = File.expand_path(gem_deps_file)
+ @gem_deps_dir = File.dirname(@gem_deps_file)
+ @platforms = []
+ end
+
+ def add_DEPENDENCIES(out) # :nodoc:
+ out << "DEPENDENCIES"
+
+ out.concat @dependencies.sort.map {|name, requirement|
+ " #{name}#{requirement.for_lockfile}"
+ }
+
+ out << nil
+ end
+
+ def add_GEM(out, spec_groups) # :nodoc:
+ return if spec_groups.empty?
+
+ source_groups = spec_groups.values.flatten.group_by do |request|
+ request.spec.source.uri
+ end
+
+ source_groups.sort_by {|group,| group.to_s }.map do |group, requests|
+ out << "GEM"
+ out << " remote: #{group}"
+ out << " specs:"
+
+ requests.sort_by(&:name).each do |request|
+ next if request.spec.name == "bundler"
+ platform = "-#{request.spec.platform}" unless
+ request.spec.platform == Gem::Platform::RUBY
+
+ out << " #{request.name} (#{request.version}#{platform})"
+
+ request.full_spec.dependencies.sort.each do |dependency|
+ next if dependency.type == :development
+
+ requirement = dependency.requirement
+ out << " #{dependency.name}#{requirement.for_lockfile}"
+ end
+ end
+ out << nil
+ end
+ end
+
+ def add_GIT(out, git_requests)
+ return if git_requests.empty?
+
+ by_repository_revision = git_requests.group_by do |request|
+ source = request.spec.source
+ [source.repository, source.rev_parse]
+ end
+
+ by_repository_revision.each do |(repository, revision), requests|
+ out << "GIT"
+ out << " remote: #{repository}"
+ out << " revision: #{revision}"
+ out << " specs:"
+
+ requests.sort_by(&:name).each do |request|
+ out << " #{request.name} (#{request.version})"
+
+ dependencies = request.spec.dependencies.sort_by(&:name)
+ dependencies.each do |dep|
+ out << " #{dep.name}#{dep.requirement.for_lockfile}"
+ end
+ end
+ out << nil
+ end
+ end
+
+ def relative_path_from(dest, base) # :nodoc:
+ dest = File.expand_path(dest)
+ base = File.expand_path(base)
+
+ if dest.index(base) == 0
+ offset = dest[base.size + 1..-1]
+
+ return "." unless offset
+
+ offset
+ else
+ dest
+ end
+ end
+
+ def add_PATH(out, path_requests) # :nodoc:
+ return if path_requests.empty?
+
+ out << "PATH"
+ path_requests.each do |request|
+ directory = File.expand_path(request.spec.source.uri)
+
+ out << " remote: #{relative_path_from directory, @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 = platforms.sort_by(&:to_s)
+
+ platforms.each do |platform|
+ out << " #{platform}"
+ end
+
+ out << nil
+ end
+
+ def spec_groups
+ requests.group_by {|request| request.spec.class }
+ end
+
+ ##
+ # The contents of the lock file.
+
+ def to_s
+ out = []
+
+ groups = spec_groups
+
+ add_PATH out, groups.delete(Gem::Resolver::VendorSpecification) { [] }
+
+ add_GIT out, groups.delete(Gem::Resolver::GitSpecification) { [] }
+
+ add_GEM out, groups
+
+ add_PLATFORMS out
+
+ add_DEPENDENCIES out
+
+ out.join "\n"
+ end
+
+ ##
+ # Writes the lock file alongside the gem dependencies file
+
+ def write
+ content = to_s
+
+ File.open "#{@gem_deps_file}.lock", "w" do |io|
+ io.write content
+ end
+ end
+
+ private
+
+ def requests
+ @set.sorted_requests
+ end
+end