Skip to content

Commit

Permalink
Showing 27 changed files with 901 additions and 243 deletions.
44 changes: 42 additions & 2 deletions lib/ruby/stdlib/rubygems.rb
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
require 'thread'

module Gem
VERSION = '2.6.4'
VERSION = '2.6.6'
end

# Must be first since it unloads the prelude from 1.9.2
@@ -154,6 +154,26 @@ module Gem
specifications/default
]

##
# Exception classes used in a Gem.read_binary +rescue+ statement. Not all of
# these are defined in Ruby 1.8.7, hence the need for this convoluted setup.

READ_BINARY_ERRORS = begin
read_binary_errors = [Errno::EACCES]
read_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP)
read_binary_errors
end.freeze

##
# Exception classes used in Gem.write_binary +rescue+ statement. Not all of
# these are defined in Ruby 1.8.7.

WRITE_BINARY_ERRORS = begin
write_binary_errors = []
write_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP)
write_binary_errors
end.freeze

@@win_platform = nil

@configuration = nil
@@ -829,7 +849,7 @@ def self.read_binary(path)
f.flock(File::LOCK_EX)
f.read
end
rescue Errno::EACCES
rescue *READ_BINARY_ERRORS
open path, 'rb' do |f|
f.read
end
@@ -843,6 +863,26 @@ def self.read_binary(path)
end
end

##
# Safely write a file in binary mode on all platforms.
def self.write_binary(path, data)
open(path, 'wb') do |io|
begin
io.flock(File::LOCK_EX)
rescue *WRITE_BINARY_ERRORS
end
io.write data
end
rescue Errno::ENOLCK # NFS
if Thread.main != Thread.current
raise
else
open(path, 'wb') do |io|
io.write data
end
end
end

##
# The path to the running Ruby interpreter.

2 changes: 1 addition & 1 deletion lib/ruby/stdlib/rubygems/commands/update_command.rb
Original file line number Diff line number Diff line change
@@ -241,7 +241,7 @@ def update_rubygems
update_gem 'rubygems-update', version

installed_gems = Gem::Specification.find_all_by_name 'rubygems-update', requirement
version = installed_gems.last.version
version = installed_gems.first.version

install_rubygems version
end
2 changes: 1 addition & 1 deletion lib/ruby/stdlib/rubygems/config_file.rb
Original file line number Diff line number Diff line change
@@ -54,7 +54,7 @@ class Gem::ConfigFile
# For Ruby implementers to set configuration defaults. Set in
# rubygems/defaults/#{RUBY_ENGINE}.rb

PLATFORM_DEFAULTS = {}
PLATFORM_DEFAULTS = Gem.platform_defaults

# :stopdoc:

18 changes: 18 additions & 0 deletions lib/ruby/stdlib/rubygems/defaults.rb
Original file line number Diff line number Diff line change
@@ -175,4 +175,22 @@ def self.vendor_dir # :nodoc:
RbConfig::CONFIG['ruby_version']
end

##
# Default options for gem commands.
#
# The options here should be structured as an array of string "gem"
# command names as keys and a string of the default options as values.
#
# Example:
#
# def self.platform_defaults
# {
# 'install' => '--no-rdoc --no-ri --env-shebang',
# 'update' => '--no-rdoc --no-ri --env-shebang'
# }
# end

def self.platform_defaults
{}
end
end
1 change: 1 addition & 0 deletions lib/ruby/stdlib/rubygems/installer.rb
Original file line number Diff line number Diff line change
@@ -284,6 +284,7 @@ def install

# Completely remove any previous gem files
FileUtils.rm_rf gem_dir
FileUtils.rm_rf spec.extension_dir

FileUtils.mkdir_p gem_dir

4 changes: 3 additions & 1 deletion lib/ruby/stdlib/rubygems/package.rb
Original file line number Diff line number Diff line change
@@ -211,7 +211,9 @@ def add_files tar # :nodoc:
stat = File.lstat file

if stat.symlink?
tar.add_symlink file, File.readlink(file), stat.mode
relative_dir = File.dirname(file).sub("#{Dir.pwd}/", '')
target_path = File.join(relative_dir, File.readlink(file))
tar.add_symlink file, target_path, stat.mode
end

next unless stat.file?
26 changes: 10 additions & 16 deletions lib/ruby/stdlib/rubygems/package/tar_writer.rb
Original file line number Diff line number Diff line change
@@ -310,27 +310,21 @@ def mkdir(name, mode)
# Splits +name+ into a name and prefix that can fit in the TarHeader

def split_name(name) # :nodoc:
if name.bytesize > 256
if name.bytesize > 256 then
raise Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)")
end

if name.bytesize <= 100 then
prefix = ""
else
parts = name.split(/\//)
newname = parts.pop
nxt = ""

loop do
nxt = parts.pop
break if newname.bytesize + 1 + nxt.bytesize > 100
newname = nxt + "/" + newname
prefix = ''
if name.bytesize > 100 then
parts = name.split('/', -1) # parts are never empty here
name = parts.pop # initially empty for names with a trailing slash ("foo/.../bar/")
prefix = parts.join('/') # if empty, then it's impossible to split (parts is empty too)
while !parts.empty? && (prefix.bytesize > 155 || name.empty?)
name = parts.pop + '/' + name
prefix = parts.join('/')
end

prefix = (parts + [nxt]).join "/"
name = newname

if name.bytesize > 100
if name.bytesize > 100 or prefix.empty? then
raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)")
end

16 changes: 1 addition & 15 deletions lib/ruby/stdlib/rubygems/remote_fetcher.rb
Original file line number Diff line number Diff line change
@@ -328,20 +328,7 @@ def cache_update_path uri, path = nil, update = true
end

if update and path
begin
open(path, 'wb') do |io|
io.flock(File::LOCK_EX)
io.write data
end
rescue Errno::ENOLCK # NFS
if Thread.main != Thread.current
raise
else
open(path, 'wb') do |io|
io.write data
end
end
end
Gem.write_binary(path, data)
end

data
@@ -427,4 +414,3 @@ def pools_for proxy
end
end
end

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true
module Gem::Resolver::Molinillo
# @!visibility private
module Delegates
# Delegates all {Gem::Resolver::Molinillo::ResolutionState} methods to a `#state` property.
module ResolutionState
# (see Gem::Resolver::Molinillo::ResolutionState#name)
def name
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.name
end

# (see Gem::Resolver::Molinillo::ResolutionState#requirements)
def requirements
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.requirements
end

# (see Gem::Resolver::Molinillo::ResolutionState#activated)
def activated
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.activated
end

# (see Gem::Resolver::Molinillo::ResolutionState#requirement)
def requirement
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.requirement
end

# (see Gem::Resolver::Molinillo::ResolutionState#possibilities)
def possibilities
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.possibilities
end

# (see Gem::Resolver::Molinillo::ResolutionState#depth)
def depth
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.depth
end

# (see Gem::Resolver::Molinillo::ResolutionState#conflicts)
def conflicts
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.conflicts
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# frozen_string_literal: true
module Gem::Resolver::Molinillo
module Delegates
# Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a
# `#specification_provider` property.
module SpecificationProvider
# (see Gem::Resolver::Molinillo::SpecificationProvider#search_for)
def search_for(dependency)
with_no_such_dependency_error_handling do
specification_provider.search_for(dependency)
end
end

# (see Gem::Resolver::Molinillo::SpecificationProvider#dependencies_for)
def dependencies_for(specification)
with_no_such_dependency_error_handling do
specification_provider.dependencies_for(specification)
end
end

# (see Gem::Resolver::Molinillo::SpecificationProvider#requirement_satisfied_by?)
def requirement_satisfied_by?(requirement, activated, spec)
with_no_such_dependency_error_handling do
specification_provider.requirement_satisfied_by?(requirement, activated, spec)
end
end

# (see Gem::Resolver::Molinillo::SpecificationProvider#name_for)
def name_for(dependency)
with_no_such_dependency_error_handling do
specification_provider.name_for(dependency)
end
end

# (see Gem::Resolver::Molinillo::SpecificationProvider#name_for_explicit_dependency_source)
def name_for_explicit_dependency_source
with_no_such_dependency_error_handling do
specification_provider.name_for_explicit_dependency_source
end
end

# (see Gem::Resolver::Molinillo::SpecificationProvider#name_for_locking_dependency_source)
def name_for_locking_dependency_source
with_no_such_dependency_error_handling do
specification_provider.name_for_locking_dependency_source
end
end

# (see Gem::Resolver::Molinillo::SpecificationProvider#sort_dependencies)
def sort_dependencies(dependencies, activated, conflicts)
with_no_such_dependency_error_handling do
specification_provider.sort_dependencies(dependencies, activated, conflicts)
end
end

# (see Gem::Resolver::Molinillo::SpecificationProvider#allow_missing?)
def allow_missing?(dependency)
with_no_such_dependency_error_handling do
specification_provider.allow_missing?(dependency)
end
end

private

# Ensures any raised {NoSuchDependencyError} has its
# {NoSuchDependencyError#required_by} set.
# @yield
def with_no_such_dependency_error_handling
yield
rescue NoSuchDependencyError => error
if state
vertex = activated.vertex_named(name_for(error.dependency))
error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
end
raise
end
end
end
end
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@
require 'set'
require 'tsort'

require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log'
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex'

module Gem::Resolver::Molinillo
# A directed acyclic graph that is tuned to hold named dependencies
class DependencyGraph
@@ -10,15 +13,16 @@ class DependencyGraph
# Enumerates through the vertices of the graph.
# @return [Array<Vertex>] The graph's vertices.
def each
return vertices.values.each unless block_given?
vertices.values.each { |v| yield v }
end

include TSort

# @visibility private
alias_method :tsort_each_node, :each
# @!visibility private
alias tsort_each_node each

# @visibility private
# @!visibility private
def tsort_each_child(vertex, &block)
vertex.successors.each(&block)
end
@@ -44,9 +48,27 @@ def self.tsort(vertices)
# by {Vertex#name}
attr_reader :vertices

# @return [Log] the op log for this graph
attr_reader :log

# Initializes an empty dependency graph
def initialize
@vertices = {}
@log = Log.new
end

# Tags the current state of the dependency as the given tag
# @param [Object] tag an opaque tag for the current state of the graph
# @return [Void]
def tag(tag)
log.tag(self, tag)
end

# Rewinds the graph to the state tagged as `tag`
# @param [Object] tag the tag to rewind to
# @return [Void]
def rewind_to(tag)
log.rewind_to(self, tag)
end

# Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices}
@@ -55,6 +77,7 @@ def initialize
def initialize_copy(other)
super
@vertices = {}
@log = other.log.dup
traverse = lambda do |new_v, old_v|
return if new_v.outgoing_edges.size == old_v.outgoing_edges.size
old_v.outgoing_edges.each do |edge|
@@ -75,6 +98,22 @@ def inspect
"#{self.class}:#{vertices.values.inspect}"
end

# @return [String] Returns a dot format representation of the graph
def to_dot
dot_vertices = []
dot_edges = []
vertices.each do |n, v|
dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]"
v.outgoing_edges.each do |e|
dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=\"#{e.requirement}\"]"
end
end
dot_vertices.sort!
dot_edges.sort!
dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}')
dot.join("\n")
end

# @return [Boolean] whether the two dependency graphs are equal, determined
# by a recursive traversal of each {#root_vertices} and its
# {Vertex#successors}
@@ -93,12 +132,9 @@ def ==(other)
# @param [Object] requirement the requirement that is requiring the child
# @return [void]
def add_child_vertex(name, payload, parent_names, requirement)
vertex = add_vertex(name, payload)
root = !parent_names.delete(nil) { true }
vertex = add_vertex(name, payload, root)
parent_names.each do |parent_name|
unless parent_name
vertex.root = true
next
end
parent_node = vertex_named(parent_name)
add_edge(parent_node, vertex, requirement)
end
@@ -110,27 +146,15 @@ def add_child_vertex(name, payload, parent_names, requirement)
# @param [Object] payload
# @return [Vertex] the vertex that was added to `self`
def add_vertex(name, payload, root = false)
vertex = vertices[name] ||= Vertex.new(name, payload)
vertex.payload ||= payload
vertex.root ||= root
vertex
log.add_vertex(self, name, payload, root)
end

# Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively
# removing any non-root vertices that were orphaned in the process
# @param [String] name
# @return [void]
def detach_vertex_named(name)
return unless vertex = vertices.delete(name)
vertex.outgoing_edges.each do |e|
v = e.destination
v.incoming_edges.delete(e)
detach_vertex_named(v.name) unless v.root? || v.predecessors.any?
end
vertex.incoming_edges.each do |e|
v = e.origin
v.outgoing_edges.delete(e)
end
log.detach_vertex_named(self, name)
end

# @param [String] name
@@ -158,134 +182,22 @@ def add_edge(origin, destination, requirement)
add_edge_no_circular(origin, destination, requirement)
end

# Sets the payload of the vertex with the given name
# @param [String] name the name of the vertex
# @param [Object] payload the payload
# @return [Void]
def set_payload(name, payload)
log.set_payload(self, name, payload)
end

private

# Adds a new {Edge} to the dependency graph without checking for
# circularity.
# @param (see #add_edge)
# @return (see #add_edge)
def add_edge_no_circular(origin, destination, requirement)
edge = Edge.new(origin, destination, requirement)
origin.outgoing_edges << edge
destination.incoming_edges << edge
edge
end

# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
# {#payload}
class Vertex
# @return [String] the name of the vertex
attr_accessor :name

# @return [Object] the payload the vertex holds
attr_accessor :payload

# @return [Arrary<Object>] the explicit requirements that required
# this vertex
attr_reader :explicit_requirements

# @return [Boolean] whether the vertex is considered a root vertex
attr_accessor :root
alias_method :root?, :root

# Initializes a vertex with the given name and payload.
# @param [String] name see {#name}
# @param [Object] payload see {#payload}
def initialize(name, payload)
@name = name
@payload = payload
@explicit_requirements = []
@outgoing_edges = []
@incoming_edges = []
end

# @return [Array<Object>] all of the requirements that required
# this vertex
def requirements
incoming_edges.map(&:requirement) + explicit_requirements
end

# @return [Array<Edge>] the edges of {#graph} that have `self` as their
# {Edge#origin}
attr_accessor :outgoing_edges

# @return [Array<Edge>] the edges of {#graph} that have `self` as their
# {Edge#destination}
attr_accessor :incoming_edges

# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
# `self` as their {Edge#destination}
def predecessors
incoming_edges.map(&:origin)
end

# @return [Array<Vertex>] the vertices of {#graph} where `self` is a
# {#descendent?}
def recursive_predecessors
vertices = predecessors
vertices += vertices.map(&:recursive_predecessors).flatten(1)
vertices.uniq!
vertices
end

# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
# `self` as their {Edge#origin}
def successors
outgoing_edges.map(&:destination)
end

# @return [Array<Vertex>] the vertices of {#graph} where `self` is an
# {#ancestor?}
def recursive_successors
vertices = successors
vertices += vertices.map(&:recursive_successors).flatten(1)
vertices.uniq!
vertices
end

# @return [String] a string suitable for debugging
def inspect
"#{self.class}:#{name}(#{payload.inspect})"
end

# @return [Boolean] whether the two vertices are equal, determined
# by a recursive traversal of each {Vertex#successors}
def ==(other)
shallow_eql?(other) &&
successors.to_set == other.successors.to_set
end

# @param [Vertex] other the other vertex to compare to
# @return [Boolean] whether the two vertices are equal, determined
# solely by {#name} and {#payload} equality
def shallow_eql?(other)
other &&
name == other.name &&
payload == other.payload
end

alias_method :eql?, :==

# @return [Fixnum] a hash for the vertex based upon its {#name}
def hash
name.hash
end

# Is there a path from `self` to `other` following edges in the
# dependency graph?
# @return true iff there is a path following edges within this {#graph}
def path_to?(other)
equal?(other) || successors.any? { |v| v.path_to?(other) }
end

alias_method :descendent?, :path_to?

# Is there a path from `other` to `self` following edges in the
# dependency graph?
# @return true iff there is a path following edges within this {#graph}
def ancestor?(other)
other.path_to?(self)
end

alias_method :is_reachable_from?, :ancestor?
log.add_edge_no_circular(self, origin.name, destination.name, requirement)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true
module Gem::Resolver::Molinillo
class DependencyGraph
# An action that modifies a {DependencyGraph} that is reversible.
# @abstract
class Action
# rubocop:disable Lint/UnusedMethodArgument

# @return [Symbol] The name of the action.
def self.name
raise 'Abstract'
end

# Performs the action on the given graph.
# @param [DependencyGraph] graph the graph to perform the action on.
# @return [Void]
def up(graph)
raise 'Abstract'
end

# Reverses the action on the given graph.
# @param [DependencyGraph] graph the graph to reverse the action on.
# @return [Void]
def down(graph)
raise 'Abstract'
end

# @return [Action,Nil] The previous action
attr_accessor :previous

# @return [Action,Nil] The next action
attr_accessor :next
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action'
module Gem::Resolver::Molinillo
class DependencyGraph
# @!visibility private
# (see DependencyGraph#add_edge_no_circular)
class AddEdgeNoCircular < Action
# @!group Action

# (see Action.name)
def self.name
:add_vertex
end

# (see Action#up)
def up(graph)
edge = make_edge(graph)
edge.origin.outgoing_edges << edge
edge.destination.incoming_edges << edge
edge
end

# (see Action#down)
def down(graph)
edge = make_edge(graph)
edge.origin.outgoing_edges.delete(edge)
edge.destination.incoming_edges.delete(edge)
end

# @!group AddEdgeNoCircular

# @return [String] the name of the origin of the edge
attr_reader :origin

# @return [String] the name of the destination of the edge
attr_reader :destination

# @return [Object] the requirement that the edge represents
attr_reader :requirement

# @param [DependencyGraph] graph the graph to find vertices from
# @return [Edge] The edge this action adds
def make_edge(graph)
Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement)
end

# Initialize an action to add an edge to a dependency graph
# @param [String] origin the name of the origin of the edge
# @param [String] destination the name of the destination of the edge
# @param [Object] requirement the requirement that the edge represents
def initialize(origin, destination, requirement)
@origin = origin
@destination = destination
@requirement = requirement
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action'
module Gem::Resolver::Molinillo
class DependencyGraph
# @!visibility private
# (see DependencyGraph#add_vertex)
class AddVertex < Action # :nodoc:
# @!group Action

# (see Action.name)
def self.name
:add_vertex
end

# (see Action#up)
def up(graph)
if existing = graph.vertices[name]
@existing_payload = existing.payload
@existing_root = existing.root
end
vertex = existing || Vertex.new(name, payload)
graph.vertices[vertex.name] = vertex
vertex.payload ||= payload
vertex.root ||= root
vertex
end

# (see Action#down)
def down(graph)
if defined?(@existing_payload)
vertex = graph.vertices[name]
vertex.payload = @existing_payload
vertex.root = @existing_root
else
graph.vertices.delete(name)
end
end

# @!group AddVertex

# @return [String] the name of the vertex
attr_reader :name

# @return [Object] the payload for the vertex
attr_reader :payload

# @return [Boolean] whether the vertex is root or not
attr_reader :root

# Initialize an action to add a vertex to a dependency graph
# @param [String] name the name of the vertex
# @param [Object] payload the payload for the vertex
# @param [Boolean] root whether the vertex is root or not
def initialize(name, payload, root)
@name = name
@payload = payload
@root = root
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action'
module Gem::Resolver::Molinillo
class DependencyGraph
# @!visibility private
# @see DependencyGraph#detach_vertex_named
class DetachVertexNamed < Action
# @!group Action

# (see Action#name)
def self.name
:add_vertex
end

# (see Action#up)
def up(graph)
return unless @vertex = graph.vertices.delete(name)
@vertex.outgoing_edges.each do |e|
v = e.destination
v.incoming_edges.delete(e)
graph.detach_vertex_named(v.name) unless v.root? || v.predecessors.any?
end
@vertex.incoming_edges.each do |e|
v = e.origin
v.outgoing_edges.delete(e)
end
end

# (see Action#down)
def down(graph)
return unless @vertex
graph.vertices[@vertex.name] = @vertex
@vertex.outgoing_edges.each do |e|
e.destination.incoming_edges << e
end
@vertex.incoming_edges.each do |e|
e.origin.outgoing_edges << e
end
end

# @!group DetachVertexNamed

# @return [String] the name of the vertex to detach
attr_reader :name

# Initialize an action to detach a vertex from a dependency graph
# @param [String] name the name of the vertex to detach
def initialize(name)
@name = name
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular'
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex'
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named'
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload'
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag'

module Gem::Resolver::Molinillo
class DependencyGraph
# A log for dependency graph actions
class Log
# Initializes an empty log
def initialize
@current_action = @first_action = nil
end

# @!macro [new] action
# {include:DependencyGraph#$0}
# @param [Graph] graph the graph to perform the action on
# @param (see DependencyGraph#$0)
# @return (see DependencyGraph#$0)

# @macro action
def tag(graph, tag)
push_action(graph, Tag.new(tag))
end

# @macro action
def add_vertex(graph, name, payload, root)
push_action(graph, AddVertex.new(name, payload, root))
end

# @macro action
def detach_vertex_named(graph, name)
push_action(graph, DetachVertexNamed.new(name))
end

# @macro action
def add_edge_no_circular(graph, origin, destination, requirement)
push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement))
end

# @macro action
def set_payload(graph, name, payload)
push_action(graph, SetPayload.new(name, payload))
end

# Pops the most recent action from the log and undoes the action
# @param [DependencyGraph] graph
# @return [Action] the action that was popped off the log
def pop!(graph)
return unless action = @current_action
unless @current_action = action.previous
@first_action = nil
end
action.down(graph)
action
end

extend Enumerable

# @!visibility private
# Enumerates each action in the log
# @yield [Action]
def each
return enum_for unless block_given?
action = @first_action
loop do
break unless action
yield action
action = action.next
end
self
end

# @!visibility private
# Enumerates each action in the log in reverse order
# @yield [Action]
def reverse_each
return enum_for(:reverse_each) unless block_given?
action = @current_action
loop do
break unless action
yield action
action = action.previous
end
self
end

# @macro action
def rewind_to(graph, tag)
loop do
action = pop!(graph)
raise "No tag #{tag.inspect} found" unless action
break if action.class.name == :tag && action.tag == tag
end
end

private

# Adds the given action to the log, running the action
# @param [DependencyGraph] graph
# @param [Action] action
# @return The value returned by `action.up`
def push_action(graph, action)
action.previous = @current_action
@current_action.next = action if @current_action
@current_action = action
@first_action ||= action
action.up(graph)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action'
module Gem::Resolver::Molinillo
class DependencyGraph
# @!visibility private
# @see DependencyGraph#set_payload
class SetPayload < Action # :nodoc:
# @!group Action

# (see Action.name)
def self.name
:set_payload
end

# (see Action#up)
def up(graph)
vertex = graph.vertex_named(name)
@old_payload = vertex.payload
vertex.payload = payload
end

# (see Action#down)
def down(graph)
graph.vertex_named(name).payload = @old_payload
end

# @!group SetPayload

# @return [String] the name of the vertex
attr_reader :name

# @return [Object] the payload for the vertex
attr_reader :payload

# Initialize an action to add set the payload for a vertex in a dependency
# graph
# @param [String] name the name of the vertex
# @param [Object] payload the payload for the vertex
def initialize(name, payload)
@name = name
@payload = payload
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action'
module Gem::Resolver::Molinillo
class DependencyGraph
# @!visibility private
# @see DependencyGraph#tag
class Tag < Action
# @!group Action

# (see Action.name)
def self.name
:tag
end

# (see Action#up)
def up(_graph)
end

# (see Action#down)
def down(_graph)
end

# @!group Tag

# @return [Object] An opaque tag
attr_reader :tag

# Initialize an action to tag a state of a dependency graph
# @param [Object] tag an opaque tag
def initialize(tag)
@tag = tag
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# frozen_string_literal: true
module Gem::Resolver::Molinillo
class DependencyGraph
# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
# {#payload}
class Vertex
# @return [String] the name of the vertex
attr_accessor :name

# @return [Object] the payload the vertex holds
attr_accessor :payload

# @return [Arrary<Object>] the explicit requirements that required
# this vertex
attr_reader :explicit_requirements

# @return [Boolean] whether the vertex is considered a root vertex
attr_accessor :root
alias root? root

# Initializes a vertex with the given name and payload.
# @param [String] name see {#name}
# @param [Object] payload see {#payload}
def initialize(name, payload)
@name = name.frozen? ? name : name.dup.freeze
@payload = payload
@explicit_requirements = []
@outgoing_edges = []
@incoming_edges = []
end

# @return [Array<Object>] all of the requirements that required
# this vertex
def requirements
incoming_edges.map(&:requirement) + explicit_requirements
end

# @return [Array<Edge>] the edges of {#graph} that have `self` as their
# {Edge#origin}
attr_accessor :outgoing_edges

# @return [Array<Edge>] the edges of {#graph} that have `self` as their
# {Edge#destination}
attr_accessor :incoming_edges

# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
# `self` as their {Edge#destination}
def predecessors
incoming_edges.map(&:origin)
end

# @return [Array<Vertex>] the vertices of {#graph} where `self` is a
# {#descendent?}
def recursive_predecessors
vertices = predecessors
vertices += vertices.map(&:recursive_predecessors).flatten(1)
vertices.uniq!
vertices
end

# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
# `self` as their {Edge#origin}
def successors
outgoing_edges.map(&:destination)
end

# @return [Array<Vertex>] the vertices of {#graph} where `self` is an
# {#ancestor?}
def recursive_successors
vertices = successors
vertices += vertices.map(&:recursive_successors).flatten(1)
vertices.uniq!
vertices
end

# @return [String] a string suitable for debugging
def inspect
"#{self.class}:#{name}(#{payload.inspect})"
end

# @return [Boolean] whether the two vertices are equal, determined
# by a recursive traversal of each {Vertex#successors}
def ==(other)
shallow_eql?(other) &&
successors.to_set == other.successors.to_set
end

# @param [Vertex] other the other vertex to compare to
# @return [Boolean] whether the two vertices are equal, determined
# solely by {#name} and {#payload} equality
def shallow_eql?(other)
other &&
name == other.name &&
payload == other.payload
end

alias eql? ==

# @return [Fixnum] a hash for the vertex based upon its {#name}
def hash
name.hash
end

# Is there a path from `self` to `other` following edges in the
# dependency graph?
# @return true iff there is a path following edges within this {#graph}
def path_to?(other)
equal?(other) || successors.any? { |v| v.path_to?(other) }
end

alias descendent? path_to?

# Is there a path from `other` to `self` following edges in the
# dependency graph?
# @return true iff there is a path following edges within this {#graph}
def ancestor?(other)
other.path_to?(self)
end

alias is_reachable_from? ancestor?
end
end
end
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ def initialize(dependency, required_by = [])
def message
sources = required_by.map { |r| "`#{r}`" }.join(' and ')
message = "Unable to find a specification for `#{dependency}`"
message << " depended upon by #{sources}" unless sources.empty?
message += " depended upon by #{sources}" unless sources.empty?
message
end
end
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Gem::Resolver::Molinillo
# The version of Gem::Resolver::Molinillo.
VERSION = '0.4.3'.freeze
VERSION = '0.5.0'.freeze
end
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ def initialize(specification_provider, resolver_ui, requested, base)
@base = base
@states = []
@iteration_counter = 0
@parent_of = {}
end

# Resolves the {#original_requested} dependencies into a full dependency
@@ -67,7 +68,12 @@ def resolve
indicate_progress
if state.respond_to?(:pop_possibility_state) # DependencyState
debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
state.pop_possibility_state.tap { |s| states.push(s) if s }
state.pop_possibility_state.tap do |s|
if s
states.push(s)
activated.tag(s)
end
end
end
process_topmost_state
end
@@ -118,27 +124,11 @@ def end_resolution
require 'rubygems/resolver/molinillo/lib/molinillo/state'
require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider'

ResolutionState.new.members.each do |member|
define_method member do |*args, &block|
current_state = state || ResolutionState.empty
current_state.send(member, *args, &block)
end
end
require 'rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state'
require 'rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider'

SpecificationProvider.instance_methods(false).each do |instance_method|
define_method instance_method do |*args, &block|
begin
specification_provider.send(instance_method, *args, &block)
rescue NoSuchDependencyError => error
if state
vertex = activated.vertex_named(name_for error.dependency)
error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
end
raise
end
end
end
include Gem::Resolver::Molinillo::Delegates::ResolutionState
include Gem::Resolver::Molinillo::Delegates::SpecificationProvider

# Processes the topmost available {RequirementState} on the stack
# @return [void]
@@ -169,6 +159,7 @@ def state
def initial_state
graph = DependencyGraph.new.tap do |dg|
original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } }
dg.tag(:initial_state)
end

requirements = sort_dependencies(original_requested, graph, {})
@@ -189,8 +180,9 @@ def initial_state
def unwind_for_conflict
debug(depth) { "Unwinding for conflict: #{requirement}" }
conflicts.tap do |c|
states.slice!((state_index_for_unwind + 1)..-1)
sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
raise VersionConflict.new(c) unless state
activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
state.conflicts = c
end
end
@@ -217,20 +209,14 @@ def state_index_for_unwind
# @return [Object] the requirement that led to `requirement` being added
# to the list of requirements.
def parent_of(requirement)
return nil unless requirement
seen = false
state = states.reverse_each.find do |s|
seen ||= s.requirement == requirement || s.requirements.include?(requirement)
seen && s.requirement != requirement && !s.requirements.include?(requirement)
end
state && state.requirement
@parent_of[requirement]
end

# @return [Object] the requirement that led to a version of a possibility
# with the given name being activated.
def requirement_for_existing_name(name)
return nil unless activated.vertex_named(name).payload
states.reverse_each.find { |s| !s.activated.vertex_named(name).payload }.requirement
states.find { |s| s.name == name }.requirement
end

# @return [ResolutionState] the state whose `requirement` is the given
@@ -250,19 +236,25 @@ def state_any?(state)
# the {#possibility} in conjunction with the current {#state}
def create_conflict
vertex = activated.vertex_named(name)
requirements = {
name_for_explicit_dependency_source => vertex.explicit_requirements,
name_for_locking_dependency_source => Array(locked_requirement_named(name)),
}
locked_requirement = locked_requirement_named(name)

requirements = {}
unless vertex.explicit_requirements.empty?
requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
end
requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }

activated_by_name = {}
activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
conflicts[name] = Conflict.new(
requirement,
Hash[requirements.select { |_, r| !r.empty? }],
requirements,
vertex.payload,
possibility,
locked_requirement_named(name),
locked_requirement,
requirement_trees,
Hash[activated.map { |v| [v.name, v.payload] }.select(&:last)]
activated_by_name
)
end

@@ -341,15 +333,16 @@ def attempt_to_activate_existing_spec(existing_node)
# spec with the given name
# @return [Boolean] Whether the possibility was swapped into {#activated}
def attempt_to_swap_possibility
swapped = activated.dup
vertex = swapped.vertex_named(name)
vertex.payload = possibility
return unless vertex.requirements.
all? { |r| requirement_satisfied_by?(r, swapped, possibility) }
return unless new_spec_satisfied?
actual_vertex = activated.vertex_named(name)
actual_vertex.payload = possibility
fixup_swapped_children(actual_vertex)
activated.tag(:swap)
vertex = activated.vertex_named(name)
activated.set_payload(name, possibility)
if !vertex.requirements.
all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
!new_spec_satisfied?
activated.rewind_to(:swap)
return
end
fixup_swapped_children(vertex)
activate_spec
end

@@ -363,7 +356,13 @@ def fixup_swapped_children(vertex)
if !dep_names.include?(succ.name) && !succ.root? && succ.predecessors.to_a == [vertex]
debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
activated.detach_vertex_named(succ.name)
requirements.delete_if { |r| name_for(r) == succ.name }

all_successor_names = succ.recursive_successors.map(&:name)

requirements.delete_if do |requirement|
requirement_name = name_for(requirement)
(requirement_name == succ.name) || all_successor_names.include?(requirement_name)
end
end
end
end
@@ -406,8 +405,7 @@ def locked_requirement_named(requirement_name)
def activate_spec
conflicts.delete(name)
debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
vertex = activated.vertex_named(name)
vertex.payload = possibility
activated.set_payload(name, possibility)
require_nested_dependencies_for(possibility)
end

@@ -418,19 +416,22 @@ def activate_spec
def require_nested_dependencies_for(activated_spec)
nested_dependencies = dependencies_for(activated_spec)
debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
nested_dependencies.each { |d| activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d) }
nested_dependencies.each do |d|
activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
@parent_of[d] = requirement
end

push_state_for_requirements(requirements + nested_dependencies, nested_dependencies.size > 0)
push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?)
end

# Pushes a new {DependencyState} that encapsulates both existing and new
# requirements
# @param [Array] new_requirements
# @return [void]
def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated.dup)
def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
new_requirement = new_requirements.shift
new_name = new_requirement ? name_for(new_requirement) : ''
new_name = new_requirement ? name_for(new_requirement) : ''.freeze
possibilities = new_requirement ? search_for(new_requirement) : []
handle_missing_or_push_dependency_state DependencyState.new(
new_name, new_requirements, new_activated,
@@ -451,7 +452,7 @@ def handle_missing_or_push_dependency_state(state)
state.activated.detach_vertex_named(state.name)
push_state_for_requirements(state.requirements.dup, false, state.activated)
else
states.push state
states.push(state).tap { activated.tag(state) }
end
end
end
Original file line number Diff line number Diff line change
@@ -36,12 +36,14 @@ def pop_possibility_state
PossibilityState.new(
name,
requirements.dup,
activated.dup,
activated,
requirement,
[possibilities.pop],
depth + 1,
conflicts.dup
)
).tap do |state|
state.activated.tag(state)
end
end
end

2 changes: 2 additions & 0 deletions lib/ruby/stdlib/rubygems/security/signer.rb
Original file line number Diff line number Diff line change
@@ -102,6 +102,8 @@ def load_cert_chain # :nodoc:
def sign data
return unless @key

raise Gem::Security::Exception, 'no certs provided' if @cert_chain.empty?

if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now then
re_sign_key
end
8 changes: 4 additions & 4 deletions lib/ruby/stdlib/rubygems/specification.rb
Original file line number Diff line number Diff line change
@@ -209,9 +209,9 @@ class Gem::Specification < Gem::BasicSpecification
##
# Paths in the gem to add to <code>$LOAD_PATH</code> when this gem is
# activated.
#
#--
# See also #require_paths
#
#++
# If you have an extension you do not need to add <code>"ext"</code> to the
# require path, the extension build process will copy the extension files
# into "lib" for you.
@@ -2696,9 +2696,9 @@ def validate packaging = true
"#{full_name} contains itself (#{file_name}), check your files list"
end

unless specification_version.is_a?(Fixnum)
unless specification_version.is_a?(Integer)
raise Gem::InvalidSpecificationException,
'specification_version must be a Fixnum (did you mean version?)'
'specification_version must be a Integer (did you mean version?)'
end

case platform
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----

0 comments on commit 879f5ba

Please sign in to comment.