molinillo-0.6.4/0000755000004100000410000000000013176277534013566 5ustar www-datawww-datamolinillo-0.6.4/lib/0000755000004100000410000000000013176277533014333 5ustar www-datawww-datamolinillo-0.6.4/lib/molinillo/0000755000004100000410000000000013176277533016331 5ustar www-datawww-datamolinillo-0.6.4/lib/molinillo/delegates/0000755000004100000410000000000013176277533020266 5ustar www-datawww-datamolinillo-0.6.4/lib/molinillo/delegates/resolution_state.rb0000644000004100000410000000323513176277533024221 0ustar www-datawww-data# frozen_string_literal: true module Molinillo # @!visibility private module Delegates # Delegates all {Molinillo::ResolutionState} methods to a `#state` property. module ResolutionState # (see Molinillo::ResolutionState#name) def name current_state = state || Molinillo::ResolutionState.empty current_state.name end # (see Molinillo::ResolutionState#requirements) def requirements current_state = state || Molinillo::ResolutionState.empty current_state.requirements end # (see Molinillo::ResolutionState#activated) def activated current_state = state || Molinillo::ResolutionState.empty current_state.activated end # (see Molinillo::ResolutionState#requirement) def requirement current_state = state || Molinillo::ResolutionState.empty current_state.requirement end # (see Molinillo::ResolutionState#possibilities) def possibilities current_state = state || Molinillo::ResolutionState.empty current_state.possibilities end # (see Molinillo::ResolutionState#depth) def depth current_state = state || Molinillo::ResolutionState.empty current_state.depth end # (see Molinillo::ResolutionState#conflicts) def conflicts current_state = state || Molinillo::ResolutionState.empty current_state.conflicts end # (see Molinillo::ResolutionState#unused_unwind_options) def unused_unwind_options current_state = state || Molinillo::ResolutionState.empty current_state.unused_unwind_options end end end end molinillo-0.6.4/lib/molinillo/delegates/specification_provider.rb0000644000004100000410000000541013176277533025345 0ustar www-datawww-data# frozen_string_literal: true module Molinillo module Delegates # Delegates all {Molinillo::SpecificationProvider} methods to a # `#specification_provider` property. module SpecificationProvider # (see Molinillo::SpecificationProvider#search_for) def search_for(dependency) with_no_such_dependency_error_handling do specification_provider.search_for(dependency) end end # (see Molinillo::SpecificationProvider#dependencies_for) def dependencies_for(specification) with_no_such_dependency_error_handling do specification_provider.dependencies_for(specification) end end # (see 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 Molinillo::SpecificationProvider#name_for) def name_for(dependency) with_no_such_dependency_error_handling do specification_provider.name_for(dependency) end end # (see 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 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 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 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 molinillo-0.6.4/lib/molinillo/errors.rb0000644000004100000410000001315213176277533020174 0ustar www-datawww-data# frozen_string_literal: true module Molinillo # An error that occurred during the resolution process class ResolverError < StandardError; end # An error caused by searching for a dependency that is completely unknown, # i.e. has no versions available whatsoever. class NoSuchDependencyError < ResolverError # @return [Object] the dependency that could not be found attr_accessor :dependency # @return [Array] the specifications that depended upon {#dependency} attr_accessor :required_by # Initializes a new error with the given missing dependency. # @param [Object] dependency @see {#dependency} # @param [Array] required_by @see {#required_by} def initialize(dependency, required_by = []) @dependency = dependency @required_by = required_by super() end # The error message for the missing dependency, including the specifications # that had this dependency. 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 end end # An error caused by attempting to fulfil a dependency that was circular # # @note This exception will be thrown iff a {Vertex} is added to a # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an # existing {DependencyGraph::Vertex} class CircularDependencyError < ResolverError # [Set] the dependencies responsible for causing the error attr_reader :dependencies # Initializes a new error with the given circular vertices. # @param [Array] vertices the vertices in the dependency # that caused the error def initialize(vertices) super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set end end # An error caused by conflicts in version class VersionConflict < ResolverError # @return [{String => Resolution::Conflict}] the conflicts that caused # resolution to fail attr_reader :conflicts # @return [SpecificationProvider] the specification provider used during # resolution attr_reader :specification_provider # Initializes a new error with the given version conflicts. # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} # @param [SpecificationProvider] specification_provider see {#specification_provider} def initialize(conflicts, specification_provider) pairs = [] Compatibility.flat_map(conflicts.values.flatten, &:requirements).each do |conflicting| conflicting.each do |source, conflict_requirements| conflict_requirements.each do |c| pairs << [c, source] end end end super "Unable to satisfy the following requirements:\n\n" \ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" @conflicts = conflicts @specification_provider = specification_provider end require 'molinillo/delegates/specification_provider' include Delegates::SpecificationProvider # @return [String] An error message that includes requirement trees, # which is much more detailed & customizable than the default message # @param [Hash] opts the options to create a message with. # @option opts [String] :solver_name The user-facing name of the solver # @option opts [String] :possibility_type The generic name of a possibility # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements # @option opts [Proc] :additional_message_for_conflict A proc that appends additional # messages for each conflict # @option opts [Proc] :version_for_spec A proc that returns the version number for a # possibility def message_with_trees(opts = {}) solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } possibility_type = opts.delete(:possibility_type) { 'possibility named' } reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } conflicts.sort.reduce(''.dup) do |o, (name, conflict)| o << %(\n#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":\n) if conflict.locked_requirement o << %( In snapshot (#{name_for_locking_dependency_source}):\n) o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) o << %(\n) end o << %( In #{name_for_explicit_dependency_source}:\n) trees = reduce_trees.call(conflict.requirement_trees) o << trees.map do |tree| t = ''.dup depth = 2 tree.each do |req| t << ' ' * depth << req.to_s unless tree.last == req if spec = conflict.activated_by_name[name_for(req)] t << %( was resolved to #{version_for_spec.call(spec)}, which) end t << %( depends on) end t << %(\n) depth += 1 end t end.join("\n") additional_message_for_conflict.call(o, name, conflict) o end.strip end end end molinillo-0.6.4/lib/molinillo/resolver.rb0000644000004100000410000000303013176277533020513 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph' module Molinillo # This class encapsulates a dependency resolver. # The resolver is responsible for determining which set of dependencies to # activate, with feedback from the {#specification_provider} # # class Resolver require 'molinillo/resolution' # @return [SpecificationProvider] the specification provider used # in the resolution process attr_reader :specification_provider # @return [UI] the UI module used to communicate back to the user # during the resolution process attr_reader :resolver_ui # Initializes a new resolver. # @param [SpecificationProvider] specification_provider # see {#specification_provider} # @param [UI] resolver_ui # see {#resolver_ui} def initialize(specification_provider, resolver_ui) @specification_provider = specification_provider @resolver_ui = resolver_ui end # Resolves the requested dependencies into a {DependencyGraph}, # locking to the base dependency graph (if specified) # @param [Array] requested an array of 'requested' dependencies that the # {#specification_provider} can understand # @param [DependencyGraph,nil] base the base dependency graph to which # dependencies should be 'locked' def resolve(requested, base = DependencyGraph.new) Resolution.new(specification_provider, resolver_ui, requested, base). resolve end end end molinillo-0.6.4/lib/molinillo/modules/0000755000004100000410000000000013176277533020001 5ustar www-datawww-datamolinillo-0.6.4/lib/molinillo/modules/ui.rb0000644000004100000410000000330213176277533020741 0ustar www-datawww-data# frozen_string_literal: true module Molinillo # Conveys information about the resolution process to a user. module UI # The {IO} object that should be used to print output. `STDOUT`, by default. # # @return [IO] def output STDOUT end # Called roughly every {#progress_rate}, this method should convey progress # to the user. # # @return [void] def indicate_progress output.print '.' unless debug? end # How often progress should be conveyed to the user via # {#indicate_progress}, in seconds. A third of a second, by default. # # @return [Float] def progress_rate 0.33 end # Called before resolution begins. # # @return [void] def before_resolution output.print 'Resolving dependencies...' end # Called after resolution ends (either successfully or with an error). # By default, prints a newline. # # @return [void] def after_resolution output.puts end # Conveys debug information to the user. # # @param [Integer] depth the current depth of the resolution process. # @return [void] def debug(depth = 0) if debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } output.puts debug_info end end # Whether or not debug messages should be printed. # By default, whether or not the `MOLINILLO_DEBUG` environment variable is # set. # # @return [Boolean] def debug? return @debug_mode if defined?(@debug_mode) @debug_mode = ENV['MOLINILLO_DEBUG'] end end end molinillo-0.6.4/lib/molinillo/modules/specification_provider.rb0000644000004100000410000000725613176277533025072 0ustar www-datawww-data# frozen_string_literal: true module Molinillo # Provides information about specifcations and dependencies to the resolver, # allowing the {Resolver} class to remain generic while still providing power # and flexibility. # # This module contains the methods that users of Molinillo must to implement, # using knowledge of their own model classes. module SpecificationProvider # Search for the specifications that match the given dependency. # The specifications in the returned array will be considered in reverse # order, so the latest version ought to be last. # @note This method should be 'pure', i.e. the return value should depend # only on the `dependency` parameter. # # @param [Object] dependency # @return [Array] the specifications that satisfy the given # `dependency`. def search_for(dependency) [] end # Returns the dependencies of `specification`. # @note This method should be 'pure', i.e. the return value should depend # only on the `specification` parameter. # # @param [Object] specification # @return [Array] the dependencies that are required by the given # `specification`. def dependencies_for(specification) [] end # Determines whether the given `requirement` is satisfied by the given # `spec`, in the context of the current `activated` dependency graph. # # @param [Object] requirement # @param [DependencyGraph] activated the current dependency graph in the # resolution process. # @param [Object] spec # @return [Boolean] whether `requirement` is satisfied by `spec` in the # context of the current `activated` dependency graph. def requirement_satisfied_by?(requirement, activated, spec) true end # Returns the name for the given `dependency`. # @note This method should be 'pure', i.e. the return value should depend # only on the `dependency` parameter. # # @param [Object] dependency # @return [String] the name for the given `dependency`. def name_for(dependency) dependency.to_s end # @return [String] the name of the source of explicit dependencies, i.e. # those passed to {Resolver#resolve} directly. def name_for_explicit_dependency_source 'user-specified dependency' end # @return [String] the name of the source of 'locked' dependencies, i.e. # those passed to {Resolver#resolve} directly as the `base` def name_for_locking_dependency_source 'Lockfile' end # Sort dependencies so that the ones that are easiest to resolve are first. # Easiest to resolve is (usually) defined by: # 1) Is this dependency already activated? # 2) How relaxed are the requirements? # 3) Are there any conflicts for this dependency? # 4) How many possibilities are there to satisfy this dependency? # # @param [Array] dependencies # @param [DependencyGraph] activated the current dependency graph in the # resolution process. # @param [{String => Array}] conflicts # @return [Array] a sorted copy of `dependencies`. def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by do |dependency| name = name_for(dependency) [ activated.vertex_named(name).payload ? 0 : 1, conflicts[name] ? 0 : 1, ] end end # Returns whether this dependency, which has no possible matching # specifications, can safely be ignored. # # @param [Object] dependency # @return [Boolean] whether this dependency can safely be skipped. def allow_missing?(dependency) false end end end molinillo-0.6.4/lib/molinillo/resolution.rb0000644000004100000410000010301213176277533021056 0ustar www-datawww-data# frozen_string_literal: true module Molinillo class Resolver # A specific resolution from a given {Resolver} class Resolution # A conflict that the resolution process encountered # @attr [Object] requirement the requirement that immediately led to the conflict # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict # @attr [Object, nil] existing the existing spec that was in conflict with # the {#possibility} # @attr [Object] possibility_set the set of specs that was unable to be # activated due to a conflict. # @attr [Object] locked_requirement the relevant locking requirement. # @attr [Array>] requirement_trees the different requirement # trees that led to every requirement for the conflicting name. # @attr [{String=>Object}] activated_by_name the already-activated specs. # @attr [Object] underlying_error an error that has occurred during resolution, and # will be raised at the end of it if no resolution is found. Conflict = Struct.new( :requirement, :requirements, :existing, :possibility_set, :locked_requirement, :requirement_trees, :activated_by_name, :underlying_error ) class Conflict # @return [Object] a spec that was unable to be activated due to a conflict def possibility possibility_set && possibility_set.latest_version end end # A collection of possibility states that share the same dependencies # @attr [Array] dependencies the dependencies for this set of possibilities # @attr [Array] possibilities the possibilities PossibilitySet = Struct.new(:dependencies, :possibilities) class PossibilitySet # String representation of the possibility set, for debugging def to_s "[#{possibilities.join(', ')}]" end # @return [Object] most up-to-date dependency in the possibility set def latest_version possibilities.last end end # Details of the state to unwind to when a conflict occurs, and the cause of the unwind # @attr [Integer] state_index the index of the state to unwind to # @attr [Object] state_requirement the requirement of the state we're unwinding to # @attr [Array] requirement_tree for the requirement we're relaxing # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict # @attr [Array] requirement_trees for the conflict # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind UnwindDetails = Struct.new( :state_index, :state_requirement, :requirement_tree, :conflicting_requirements, :requirement_trees, :requirements_unwound_to_instead ) class UnwindDetails include Comparable # We compare UnwindDetails when choosing which state to unwind to. If # two options have the same state_index we prefer the one most # removed from a requirement that caused the conflict. Both options # would unwind to the same state, but a `grandparent` option will # filter out fewer of its possibilities after doing so - where a state # is both a `parent` and a `grandparent` to requirements that have # caused a conflict this is the correct behaviour. # @param [UnwindDetail] other UnwindDetail to be compared # @return [Integer] integer specifying ordering def <=>(other) if state_index > other.state_index 1 elsif state_index == other.state_index reversed_requirement_tree_index <=> other.reversed_requirement_tree_index else -1 end end # @return [Integer] index of state requirement in reversed requirement tree # (the conflicting requirement itself will be at position 0) def reversed_requirement_tree_index @reversed_requirement_tree_index ||= if state_requirement requirement_tree.reverse.index(state_requirement) else 999_999 end end # @return [Boolean] where the requirement of the state we're unwinding # to directly caused the conflict. Note: in this case, it is # impossible for the state we're unwinding to to be a parent of # any of the other conflicting requirements (or we would have # circularity) def unwinding_to_primary_requirement? requirement_tree.last == state_requirement end # @return [Array] array of sub-dependencies to avoid when choosing a # new possibility for the state we've unwound to. Only relevant for # non-primary unwinds def sub_dependencies_to_avoid @requirements_to_avoid ||= requirement_trees.map do |tree| index = tree.index(state_requirement) tree[index + 1] if index end.compact end # @return [Array] array of all the requirements that led to the need for # this unwind def all_requirements @all_requirements ||= requirement_trees.flatten(1) end end # @return [SpecificationProvider] the provider that knows about # dependencies, requirements, specifications, versions, etc. attr_reader :specification_provider # @return [UI] the UI that knows how to communicate feedback about the # resolution process back to the user attr_reader :resolver_ui # @return [DependencyGraph] the base dependency graph to which # dependencies should be 'locked' attr_reader :base # @return [Array] the dependencies that were explicitly required attr_reader :original_requested # Initializes a new resolution. # @param [SpecificationProvider] specification_provider # see {#specification_provider} # @param [UI] resolver_ui see {#resolver_ui} # @param [Array] requested see {#original_requested} # @param [DependencyGraph] base see {#base} def initialize(specification_provider, resolver_ui, requested, base) @specification_provider = specification_provider @resolver_ui = resolver_ui @original_requested = requested @base = base @states = [] @iteration_counter = 0 @parents_of = Hash.new { |h, k| h[k] = [] } end # Resolves the {#original_requested} dependencies into a full dependency # graph # @raise [ResolverError] if successful resolution is impossible # @return [DependencyGraph] the dependency graph of successfully resolved # dependencies def resolve start_resolution while state break if !state.requirement && state.requirements.empty? 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 do |s| if s states.push(s) activated.tag(s) end end end process_topmost_state end resolve_activated_specs ensure end_resolution end # @return [Integer] the number of resolver iterations in between calls to # {#resolver_ui}'s {UI#indicate_progress} method attr_accessor :iteration_rate private :iteration_rate # @return [Time] the time at which resolution began attr_accessor :started_at private :started_at # @return [Array] the stack of states for the resolution attr_accessor :states private :states private # Sets up the resolution process # @return [void] def start_resolution @started_at = Time.now handle_missing_or_push_dependency_state(initial_state) debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } resolver_ui.before_resolution end def resolve_activated_specs activated.vertices.each do |_, vertex| next unless vertex.payload latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } end activated.set_payload(vertex.name, latest_version) end activated.freeze end # Ends the resolution process # @return [void] def end_resolution resolver_ui.after_resolution debug do "Finished resolution (#{@iteration_counter} steps) " \ "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" end debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state end require 'molinillo/state' require 'molinillo/modules/specification_provider' require 'molinillo/delegates/resolution_state' require 'molinillo/delegates/specification_provider' include Molinillo::Delegates::ResolutionState include Molinillo::Delegates::SpecificationProvider # Processes the topmost available {RequirementState} on the stack # @return [void] def process_topmost_state if possibility attempt_to_activate else create_conflict unwind_for_conflict end rescue CircularDependencyError => underlying_error create_conflict(underlying_error) unwind_for_conflict end # @return [Object] the current possibility that the resolution is trying # to activate def possibility possibilities.last end # @return [RequirementState] the current state the resolution is # operating upon def state states.last end # Creates the initial state for the resolution, based upon the # {#requested} dependencies # @return [DependencyState] the initial state for the resolution def initial_state graph = DependencyGraph.new.tap do |dg| original_requested.each do |requested| vertex = dg.add_vertex(name_for(requested), nil, true) vertex.explicit_requirements << requested end dg.tag(:initial_state) end requirements = sort_dependencies(original_requested, graph, {}) initial_requirement = requirements.shift DependencyState.new( initial_requirement && name_for(initial_requirement), requirements, graph, initial_requirement, possibilities_for_requirement(initial_requirement, graph), 0, {}, [] ) end # Unwinds the states stack because a conflict has been encountered # @return [void] def unwind_for_conflict details_for_unwind = build_details_for_unwind unwind_options = unused_unwind_options debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } conflicts.tap do |c| sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) raise_error_unless_state(c) activated.rewind_to(sliced_states.first || :initial_state) if sliced_states state.conflicts = c state.unused_unwind_options = unwind_options filter_possibilities_after_unwind(details_for_unwind) index = states.size - 1 @parents_of.each { |_, a| a.reject! { |i| i >= index } } state.unused_unwind_options.reject! { |uw| uw.state_index >= index } end end # Raises a VersionConflict error, or any underlying error, if there is no # current state # @return [void] def raise_error_unless_state(conflicts) return if state error = conflicts.values.map(&:underlying_error).compact.first raise error || VersionConflict.new(conflicts, specification_provider) end # @return [UnwindDetails] Details of the nearest index to which we could unwind def build_details_for_unwind # Get the possible unwinds for the current conflict current_conflict = conflicts[name] binding_requirements = binding_requirements_for_conflict(current_conflict) unwind_details = unwind_options_for_requirements(binding_requirements) last_detail_for_current_unwind = unwind_details.sort.last current_detail = last_detail_for_current_unwind # Look for past conflicts that could be unwound to affect the # requirement tree for the current conflict relevant_unused_unwinds = unused_unwind_options.select do |alternative| intersecting_requirements = last_detail_for_current_unwind.all_requirements & alternative.requirements_unwound_to_instead next if intersecting_requirements.empty? # Find the highest index unwind whilst looping through current_detail = alternative if alternative > current_detail alternative end # Add the current unwind options to the `unused_unwind_options` array. # The "used" option will be filtered out during `unwind_for_conflict`. state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } # Update the requirements_unwound_to_instead on any relevant unused unwinds relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement } unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement } current_detail end # @param [Array] array of requirements that combine to create a conflict # @return [Array] array of UnwindDetails that have a chance # of resolving the passed requirements def unwind_options_for_requirements(binding_requirements) unwind_details = [] trees = [] binding_requirements.reverse_each do |r| partial_tree = [r] trees << partial_tree unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) # If this requirement has alternative possibilities, check if any would # satisfy the other requirements that created this conflict requirement_state = find_state_for(r) if conflict_fixing_possibilities?(requirement_state, binding_requirements) unwind_details << UnwindDetails.new( states.index(requirement_state), r, partial_tree, binding_requirements, trees, [] ) end # Next, look at the parent of this requirement, and check if the requirement # could have been avoided if an alternative PossibilitySet had been chosen parent_r = parent_of(r) next if parent_r.nil? partial_tree.unshift(parent_r) requirement_state = find_state_for(parent_r) if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } unwind_details << UnwindDetails.new( states.index(requirement_state), parent_r, partial_tree, binding_requirements, trees, [] ) end # Finally, look at the grandparent and up of this requirement, looking # for any possibilities that wouldn't create their parent requirement grandparent_r = parent_of(parent_r) until grandparent_r.nil? partial_tree.unshift(grandparent_r) requirement_state = find_state_for(grandparent_r) if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } unwind_details << UnwindDetails.new( states.index(requirement_state), grandparent_r, partial_tree, binding_requirements, trees, [] ) end parent_r = grandparent_r grandparent_r = parent_of(parent_r) end end unwind_details end # @param [DependencyState] state # @param [Array] array of requirements # @return [Boolean] whether or not the given state has any possibilities # that could satisfy the given requirements def conflict_fixing_possibilities?(state, binding_requirements) return false unless state state.possibilities.any? do |possibility_set| possibility_set.possibilities.any? do |poss| possibility_satisfies_requirements?(poss, binding_requirements) end end end # Filter's a state's possibilities to remove any that would not fix the # conflict we've just rewound from # @param [UnwindDetails] details of the conflict just unwound from # @return [void] def filter_possibilities_after_unwind(unwind_details) return unless state && !state.possibilities.empty? if unwind_details.unwinding_to_primary_requirement? filter_possibilities_for_primary_unwind(unwind_details) else filter_possibilities_for_parent_unwind(unwind_details) end end # Filter's a state's possibilities to remove any that would not satisfy # the requirements in the conflict we've just rewound from # @param [UnwindDetails] details of the conflict just unwound from # @return [void] def filter_possibilities_for_primary_unwind(unwind_details) unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } unwinds_to_state << unwind_details unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) state.possibilities.reject! do |possibility_set| possibility_set.possibilities.none? do |poss| unwind_requirement_sets.any? do |requirements| possibility_satisfies_requirements?(poss, requirements) end end end end # @param [Object] possibility a single possibility # @param [Array] requirements an array of requirements # @return [Boolean] whether the possibility satisfies all of the # given requirements def possibility_satisfies_requirements?(possibility, requirements) name = name_for(possibility) activated.tag(:swap) activated.set_payload(name, possibility) if activated.vertex_named(name) satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } activated.rewind_to(:swap) satisfied end # Filter's a state's possibilities to remove any that would (eventually) # create a requirement in the conflict we've just rewound from # @param [UnwindDetails] details of the conflict just unwound from # @return [void] def filter_possibilities_for_parent_unwind(unwind_details) unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } unwinds_to_state << unwind_details primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq parent_unwinds = unwinds_to_state.uniq - primary_unwinds allowed_possibility_sets = Compatibility.flat_map(primary_unwinds) do |unwind| states[unwind.state_index].possibilities.select do |possibility_set| possibility_set.possibilities.any? do |poss| possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) end end end requirements_to_avoid = Compatibility.flat_map(parent_unwinds, &:sub_dependencies_to_avoid) state.possibilities.reject! do |possibility_set| !allowed_possibility_sets.include?(possibility_set) && (requirements_to_avoid - possibility_set.dependencies).empty? end end # @param [Conflict] conflict # @return [Array] minimal array of requirements that would cause the passed # conflict to occur. def binding_requirements_for_conflict(conflict) return [conflict.requirement] if conflict.possibility.nil? possible_binding_requirements = conflict.requirements.values.flatten(1).uniq # When there’s a `CircularDependency` error the conflicting requirement # (the one causing the circular) won’t be `conflict.requirement` # (which won’t be for the right state, because we won’t have created it, # because it’s circular). # We need to make sure we have that requirement in the conflict’s list, # otherwise we won’t be able to unwind properly, so we just return all # the requirements for the conflict. return possible_binding_requirements if conflict.underlying_error possibilities = search_for(conflict.requirement) # If all the requirements together don't filter out all possibilities, # then the only two requirements we need to consider are the initial one # (where the dependency's version was first chosen) and the last if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact end # Loop through the possible binding requirements, removing each one # that doesn't bind. Use a `reverse_each` as we want the earliest set of # binding requirements, and don't use `reject!` as we wish to refine the # array *on each iteration*. binding_requirements = possible_binding_requirements.dup possible_binding_requirements.reverse_each do |req| next if req == conflict.requirement unless binding_requirement_in_set?(req, binding_requirements, possibilities) binding_requirements -= [req] end end binding_requirements end # @param [Object] requirement we wish to check # @param [Array] array of requirements # @param [Array] array of possibilities the requirements will be used to filter # @return [Boolean] whether or not the given requirement is required to filter # out all elements of the array of possibilities. def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) possibilities.any? do |poss| possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) end end # @return [Object] the requirement that led to `requirement` being added # to the list of requirements. def parent_of(requirement) return unless requirement return unless index = @parents_of[requirement].last return unless parent_state = @states[index] parent_state.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 vertex = activated.vertex_named(name) return nil unless vertex.payload states.find { |s| s.name == name }.requirement end # @return [ResolutionState] the state whose `requirement` is the given # `requirement`. def find_state_for(requirement) return nil unless requirement states.find { |i| requirement == i.requirement } end # @return [Conflict] a {Conflict} that reflects the failure to activate # the {#possibility} in conjunction with the current {#state} def create_conflict(underlying_error = nil) vertex = activated.vertex_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 do |edge| (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) end activated_by_name = {} activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } conflicts[name] = Conflict.new( requirement, requirements, vertex.payload && vertex.payload.latest_version, possibility, locked_requirement, requirement_trees, activated_by_name, underlying_error ) end # @return [Array>] The different requirement # trees that led to every requirement for the current spec. def requirement_trees vertex = activated.vertex_named(name) vertex.requirements.map { |r| requirement_tree_for(r) } end # @return [Array] the list of requirements that led to # `requirement` being required. def requirement_tree_for(requirement) tree = [] while requirement tree.unshift(requirement) requirement = parent_of(requirement) end tree end # Indicates progress roughly once every second # @return [void] def indicate_progress @iteration_counter += 1 @progress_rate ||= resolver_ui.progress_rate if iteration_rate.nil? if Time.now - started_at >= @progress_rate self.iteration_rate = @iteration_counter end end if iteration_rate && (@iteration_counter % iteration_rate) == 0 resolver_ui.indicate_progress end end # Calls the {#resolver_ui}'s {UI#debug} method # @param [Integer] depth the depth of the {#states} stack # @param [Proc] block a block that yields a {#to_s} # @return [void] def debug(depth = 0, &block) resolver_ui.debug(depth, &block) end # Attempts to activate the current {#possibility} # @return [void] def attempt_to_activate debug(depth) { 'Attempting to activate ' + possibility.to_s } existing_vertex = activated.vertex_named(name) if existing_vertex.payload debug(depth) { "Found existing spec (#{existing_vertex.payload})" } attempt_to_filter_existing_spec(existing_vertex) else latest = possibility.latest_version # use reject!(!satisfied) for 1.8.7 compatibility possibility.possibilities.reject! do |possibility| !requirement_satisfied_by?(requirement, activated, possibility) end if possibility.latest_version.nil? # ensure there's a possibility for better error messages possibility.possibilities << latest if latest create_conflict unwind_for_conflict else activate_new_spec end end end # Attempts to update the existing vertex's `PossibilitySet` with a filtered version # @return [void] def attempt_to_filter_existing_spec(vertex) filtered_set = filtered_possibility_set(vertex) if !filtered_set.possibilities.empty? activated.set_payload(name, filtered_set) new_requirements = requirements.dup push_state_for_requirements(new_requirements, false) else create_conflict debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } unwind_for_conflict end end # Generates a filtered version of the existing vertex's `PossibilitySet` using the # current state's `requirement` # @param [Object] existing vertex # @return [PossibilitySet] filtered possibility set def filtered_possibility_set(vertex) PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) end # @param [String] requirement_name the spec name to search for # @return [Object] the locked spec named `requirement_name`, if one # is found on {#base} def locked_requirement_named(requirement_name) vertex = base.vertex_named(requirement_name) vertex && vertex.payload end # Add the current {#possibility} to the dependency graph of the current # {#state} # @return [void] def activate_new_spec conflicts.delete(name) debug(depth) { "Activated #{name} at #{possibility}" } activated.set_payload(name, possibility) require_nested_dependencies_for(possibility) end # Requires the dependencies that the recently activated spec has # @param [Object] activated_possibility the PossibilitySet that has just been # activated # @return [void] def require_nested_dependencies_for(possibility_set) nested_dependencies = dependencies_for(possibility_set.latest_version) debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } nested_dependencies.each do |d| activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) parent_index = states.size - 1 parents = @parents_of[d] parents << parent_index if parents.empty? end 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) new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort new_requirement = nil loop do new_requirement = new_requirements.shift break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } end new_name = new_requirement ? name_for(new_requirement) : ''.freeze possibilities = possibilities_for_requirement(new_requirement) handle_missing_or_push_dependency_state DependencyState.new( new_name, new_requirements, new_activated, new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup ) end # Checks a proposed requirement with any existing locked requirement # before generating an array of possibilities for it. # @param [Object] the proposed requirement # @return [Array] possibilities def possibilities_for_requirement(requirement, activated = self.activated) return [] unless requirement if locked_requirement_named(name_for(requirement)) return locked_requirement_possibility_set(requirement, activated) end group_possibilities(search_for(requirement)) end # @param [Object] the proposed requirement # @return [Array] possibility set containing only the locked requirement, if any def locked_requirement_possibility_set(requirement, activated = self.activated) all_possibilities = search_for(requirement) locked_requirement = locked_requirement_named(name_for(requirement)) # Longwinded way to build a possibilities array with either the locked # requirement or nothing in it. Required, since the API for # locked_requirement isn't guaranteed. locked_possibilities = all_possibilities.select do |possibility| requirement_satisfied_by?(locked_requirement, activated, possibility) end group_possibilities(locked_possibilities) end # Build an array of PossibilitySets, with each element representing a group of # dependency versions that all have the same sub-dependency version constraints # and are contiguous. # @param [Array] an array of possibilities # @return [Array] an array of possibility sets def group_possibilities(possibilities) possibility_sets = [] current_possibility_set = nil possibilities.reverse_each do |possibility| dependencies = dependencies_for(possibility) if current_possibility_set && current_possibility_set.dependencies == dependencies current_possibility_set.possibilities.unshift(possibility) else possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) current_possibility_set = possibility_sets.first end end possibility_sets end # Pushes a new {DependencyState}. # If the {#specification_provider} says to # {SpecificationProvider#allow_missing?} that particular requirement, and # there are no possibilities for that requirement, then `state` is not # pushed, and the vertex in {#activated} is removed, and we continue # resolving the remaining requirements. # @param [DependencyState] state # @return [void] def handle_missing_or_push_dependency_state(state) if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) state.activated.detach_vertex_named(state.name) push_state_for_requirements(state.requirements.dup, false, state.activated) else states.push(state).tap { activated.tag(state) } end end end end end molinillo-0.6.4/lib/molinillo/compatibility.rb0000644000004100000410000000117613176277533021534 0ustar www-datawww-data# frozen_string_literal: true module Molinillo # Hacks needed for old Ruby versions. module Compatibility module_function if [].respond_to?(:flat_map) # Flat map # @param [Enumerable] enum an enumerable object # @block the block to flat-map with # @return The enum, flat-mapped def flat_map(enum, &blk) enum.flat_map(&blk) end else # Flat map # @param [Enumerable] enum an enumerable object # @block the block to flat-map with # @return The enum, flat-mapped def flat_map(enum, &blk) enum.map(&blk).flatten(1) end end end end molinillo-0.6.4/lib/molinillo/dependency_graph.rb0000644000004100000410000001627213176277533022165 0ustar www-datawww-data# frozen_string_literal: true require 'set' require 'tsort' require 'molinillo/dependency_graph/log' require 'molinillo/dependency_graph/vertex' module Molinillo # A directed acyclic graph that is tuned to hold named dependencies class DependencyGraph include Enumerable # Enumerates through the vertices of the graph. # @return [Array] 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 tsort_each_node each # @!visibility private def tsort_each_child(vertex, &block) vertex.successors.each(&block) end # Topologically sorts the given vertices. # @param [Enumerable] vertices the vertices to be sorted, which must # all belong to the same graph. # @return [Array] The sorted vertices. def self.tsort(vertices) TSort.tsort( lambda { |b| vertices.each(&b) }, lambda { |v, &b| (v.successors & vertices).each(&b) } ) end # A directed edge of a {DependencyGraph} # @attr [Vertex] origin The origin of the directed edge # @attr [Vertex] destination The destination of the directed edge # @attr [Object] requirement The requirement the directed edge represents Edge = Struct.new(:origin, :destination, :requirement) # @return [{String => Vertex}] the vertices of the dependency graph, keyed # 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} # are properly copied. # @param [DependencyGraph] other the graph to copy. 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| destination = add_vertex(edge.destination.name, edge.destination.payload) add_edge_no_circular(new_v, destination, edge.requirement) traverse.call(destination, edge.destination) end end other.vertices.each do |name, vertex| new_vertex = add_vertex(name, vertex.payload, vertex.root?) new_vertex.explicit_requirements.replace(vertex.explicit_requirements) traverse.call(new_vertex, vertex) end end # @return [String] a string suitable for debugging def inspect "#{self.class}:#{vertices.values.inspect}" end # @param [Hash] options options for dot output. # @return [String] Returns a dot format representation of the graph def to_dot(options = {}) edge_label = options.delete(:edge_label) raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? dot_vertices = [] dot_edges = [] vertices.each do |n, v| dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" v.outgoing_edges.each do |e| label = edge_label ? edge_label.call(e) : e.requirement dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" end end dot_vertices.uniq! dot_vertices.sort! dot_edges.uniq! 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} def ==(other) return false unless other return true if equal?(other) vertices.each do |name, vertex| other_vertex = other.vertex_named(name) return false unless other_vertex return false unless vertex.payload == other_vertex.payload return false unless other_vertex.successors.to_set == vertex.successors.to_set end end # @param [String] name # @param [Object] payload # @param [Array] parent_names # @param [Object] requirement the requirement that is requiring the child # @return [void] def add_child_vertex(name, payload, parent_names, requirement) root = !parent_names.delete(nil) { true } vertex = add_vertex(name, payload, root) vertex.explicit_requirements << requirement if root parent_names.each do |parent_name| parent_vertex = vertex_named(parent_name) add_edge(parent_vertex, vertex, requirement) end vertex end # Adds a vertex with the given name, or updates the existing one. # @param [String] name # @param [Object] payload # @return [Vertex] the vertex that was added to `self` def add_vertex(name, payload, root = false) 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 [Array] the vertices which have been detached def detach_vertex_named(name) log.detach_vertex_named(self, name) end # @param [String] name # @return [Vertex,nil] the vertex with the given name def vertex_named(name) vertices[name] end # @param [String] name # @return [Vertex,nil] the root vertex with the given name def root_vertex_named(name) vertex = vertex_named(name) vertex if vertex && vertex.root? end # Adds a new {Edge} to the dependency graph # @param [Vertex] origin # @param [Vertex] destination # @param [Object] requirement the requirement that this edge represents # @return [Edge] the added edge def add_edge(origin, destination, requirement) if destination.path_to?(origin) raise CircularDependencyError.new([origin, destination]) end add_edge_no_circular(origin, destination, requirement) end # Deletes an {Edge} from the dependency graph # @param [Edge] edge # @return [Void] def delete_edge(edge) log.delete_edge(self, edge.origin.name, edge.destination.name, edge.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) log.add_edge_no_circular(self, origin.name, destination.name, requirement) end end end molinillo-0.6.4/lib/molinillo/dependency_graph/0000755000004100000410000000000013176277533021630 5ustar www-datawww-datamolinillo-0.6.4/lib/molinillo/dependency_graph/add_edge_no_circular.rb0000644000004100000410000000357013176277533026256 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph/action' module Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#add_edge_no_circular) class AddEdgeNoCircular < Action # @!group Action # (see Action.action_name) def self.action_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) delete_first(edge.origin.outgoing_edges, edge) delete_first(edge.destination.incoming_edges, 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 private def delete_first(array, item) return unless index = array.index(item) array.delete_at(index) end end end end molinillo-0.6.4/lib/molinillo/dependency_graph/vertex.rb0000644000004100000410000000755313176277533023504 0ustar www-datawww-data# frozen_string_literal: true module 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 [Array] 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] all of the requirements that required # this vertex def requirements (incoming_edges.map(&:requirement) + explicit_requirements).uniq end # @return [Array] the edges of {#graph} that have `self` as their # {Edge#origin} attr_accessor :outgoing_edges # @return [Array] the edges of {#graph} that have `self` as their # {Edge#destination} attr_accessor :incoming_edges # @return [Array] the vertices of {#graph} that have an edge with # `self` as their {Edge#destination} def predecessors incoming_edges.map(&:origin) end # @return [Array] the vertices of {#graph} where `self` is a # {#descendent?} def recursive_predecessors vertices = predecessors vertices += Compatibility.flat_map(vertices, &:recursive_predecessors) vertices.uniq! vertices end # @return [Array] the vertices of {#graph} that have an edge with # `self` as their {Edge#origin} def successors outgoing_edges.map(&:destination) end # @return [Array] the vertices of {#graph} where `self` is an # {#ancestor?} def recursive_successors vertices = successors vertices += Compatibility.flat_map(vertices, &:recursive_successors) 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) return true if equal?(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) return true if equal?(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 molinillo-0.6.4/lib/molinillo/dependency_graph/delete_edge.rb0000644000004100000410000000345313176277533024410 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph/action' module Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#delete_edge) class DeleteEdge < Action # @!group Action # (see Action.action_name) def self.action_name :delete_edge end # (see Action#up) def up(graph) edge = make_edge(graph) edge.origin.outgoing_edges.delete(edge) edge.destination.incoming_edges.delete(edge) end # (see Action#down) def down(graph) edge = make_edge(graph) edge.origin.outgoing_edges << edge edge.destination.incoming_edges << edge edge end # @!group DeleteEdge # @return [String] the name of the origin of the edge attr_reader :origin_name # @return [String] the name of the destination of the edge attr_reader :destination_name # @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_name), graph.vertex_named(destination_name), requirement ) end # Initialize an action to add an edge to a dependency graph # @param [String] origin_name the name of the origin of the edge # @param [String] destination_name the name of the destination of the edge # @param [Object] requirement the requirement that the edge represents def initialize(origin_name, destination_name, requirement) @origin_name = origin_name @destination_name = destination_name @requirement = requirement end end end end molinillo-0.6.4/lib/molinillo/dependency_graph/set_payload.rb0000644000004100000410000000214013176277533024456 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph/action' module Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#set_payload class SetPayload < Action # :nodoc: # @!group Action # (see Action.action_name) def self.action_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 molinillo-0.6.4/lib/molinillo/dependency_graph/log.rb0000644000004100000410000000720213176277533022737 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph/add_edge_no_circular' require 'molinillo/dependency_graph/add_vertex' require 'molinillo/dependency_graph/delete_edge' require 'molinillo/dependency_graph/detach_vertex_named' require 'molinillo/dependency_graph/set_payload' require 'molinillo/dependency_graph/tag' module 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 # {include:DependencyGraph#delete_edge} # @param [Graph] graph the graph to perform the action on # @param [String] origin_name # @param [String] destination_name # @param [Object] requirement # @return (see DependencyGraph#delete_edge) def delete_edge(graph, origin_name, destination_name, requirement) push_action(graph, DeleteEdge.new(origin_name, destination_name, 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.action_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 molinillo-0.6.4/lib/molinillo/dependency_graph/tag.rb0000644000004100000410000000125613176277533022734 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph/action' module Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#tag class Tag < Action # @!group Action # (see Action.action_name) def self.action_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 molinillo-0.6.4/lib/molinillo/dependency_graph/detach_vertex_named.rb0000644000004100000410000000300713176277533026146 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph/action' module Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#detach_vertex_named class DetachVertexNamed < Action # @!group Action # (see Action#name) def self.action_name :add_vertex end # (see Action#up) def up(graph) return [] unless @vertex = graph.vertices.delete(name) removed_vertices = [@vertex] @vertex.outgoing_edges.each do |e| v = e.destination v.incoming_edges.delete(e) if !v.root? && v.incoming_edges.empty? removed_vertices.concat graph.detach_vertex_named(v.name) end end @vertex.incoming_edges.each do |e| v = e.origin v.outgoing_edges.delete(e) end removed_vertices 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 molinillo-0.6.4/lib/molinillo/dependency_graph/action.rb0000644000004100000410000000162313176277533023434 0ustar www-datawww-data# frozen_string_literal: true module 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.action_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 molinillo-0.6.4/lib/molinillo/dependency_graph/add_vertex.rb0000644000004100000410000000315413176277533024305 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/dependency_graph/action' module Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#add_vertex) class AddVertex < Action # :nodoc: # @!group Action # (see Action.action_name) def self.action_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 molinillo-0.6.4/lib/molinillo/gem_metadata.rb0000644000004100000410000000015513176277533021267 0ustar www-datawww-data# frozen_string_literal: true module Molinillo # The version of Molinillo. VERSION = '0.6.4'.freeze end molinillo-0.6.4/lib/molinillo/state.rb0000644000004100000410000000343713176277533020005 0ustar www-datawww-data# frozen_string_literal: true module Molinillo # A state that a {Resolution} can be in # @attr [String] name the name of the current requirement # @attr [Array] requirements currently unsatisfied requirements # @attr [DependencyGraph] activated the graph of activated dependencies # @attr [Object] requirement the current requirement # @attr [Object] possibilities the possibilities to satisfy the current requirement # @attr [Integer] depth the depth of the resolution # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name # @attr [Array] unused_unwind_options unwinds for previous conflicts that weren't explored ResolutionState = Struct.new( :name, :requirements, :activated, :requirement, :possibilities, :depth, :conflicts, :unused_unwind_options ) class ResolutionState # Returns an empty resolution state # @return [ResolutionState] an empty state def self.empty new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) end end # A state that encapsulates a set of {#requirements} with an {Array} of # possibilities class DependencyState < ResolutionState # Removes a possibility from `self` # @return [PossibilityState] a state with a single possibility, # the possibility that was removed from `self` def pop_possibility_state PossibilityState.new( name, requirements.dup, activated, requirement, [possibilities.pop], depth + 1, conflicts.dup, unused_unwind_options.dup ).tap do |state| state.activated.tag(state) end end end # A state that encapsulates a single possibility to fulfill the given # {#requirement} class PossibilityState < ResolutionState end end molinillo-0.6.4/lib/molinillo.rb0000644000004100000410000000047413176277533016663 0ustar www-datawww-data# frozen_string_literal: true require 'molinillo/compatibility' require 'molinillo/gem_metadata' require 'molinillo/errors' require 'molinillo/resolver' require 'molinillo/modules/ui' require 'molinillo/modules/specification_provider' # Molinillo is a generic dependency resolution algorithm. module Molinillo end molinillo-0.6.4/ARCHITECTURE.md0000644000004100000410000002016113176277533015771 0ustar www-datawww-data# Molinillo Architecture At the highest level, Molinillo is a dependency resolution algorithm. You hand the `Resolver` a list of dependencies and a 'locking' `DependencyGraph`, and you get a resulting dependency graph out of that. In order to guarantee that the list of dependencies is properly resolved, however, an algorithm is required that is smarter than just walking the list of dependencies and activating each, and its own dependencies, in turn. ## Backtracking At the heart of Molinillo is a [backtracking](http://en.wikipedia.org/wiki/Backtracking) algorithm with [forward checking](http://en.wikipedia.org/wiki/Look-ahead_(backtracking)). Essentially, the resolution process keeps track of two types of states (dependency and possibility) in a stack. If that stack is ever exhausted, resolution was impossible. New states are pushed onto the stack for every dependency, and every time a dependency is successfully 'activated' a new state is pushed onto the stack that represents that activation. This stack-based approach is used because backtracking (also known as *unwinding*) becomes as simple as popping a state off that stack. ### Walkthrough 1. The client initializes a `Resolver` with a `SpecificationProvider` and `UI` 2. The client calls `resolve` with an array of user-requested dependencies and an optional 'locking' `DependencyGraph` 3. The `Resolver` creates a new `Resolution` with those four user-specified parameters and calls `resolve` on it 4. The `Resolution` creates an `initial_state`, which takes the user-requested dependencies and puts them into a `DependencyState` - In the process of creating the state, the `SpecificationProvider` is asked to sort the dependencies and return all the `possibilities` for the `initial_requirement` (taking into account whether the dependency is `locked`). These possibilities are then grouped into `PossibilitySet`s, with each set representing a group of versions for the dependency which share the same sub-dependency requirements and are contiguous - A `DependencyGraph` is created that has all of these requirements point to `root_vertices` 5. The resolution process now enters its main loop, which continues as long as there is a current `state` to process, and the current state has requirements left to process 6. `UI#indicate_progress` is called to allow the client to report progress 7. If the current state is a `DependencyState`, we have it pop off a `PossibilityState` that encapsulates a `PossibilitySet` for that dependency 8. Process the topmost state on the stack 9. If there is a non-empty `PossibilitySet` for the state, `attempt_to_activate` it (jump to #11) 10. If there is no non-empty `PossibilitySet` for the state, `create_conflict` if the state is a `PossibilityState`, and then `unwind_for_conflict` - `create_conflict` builds a `Conflict` object, with details of all of the requirements for the given dependency, and adds it to a hash of conflicts stored on the `state`, indexed by the name of the dependency - `unwind_for_conflict` loops through all the conflicts on the `state`, looking for a state it can rewind to that might avoid that conflict. If no such state exists, it raises a VersionConflict error. Otherwise, it takes the most recent state with a chance to avoid the current conflicts and rewinds to it (go to #6) 11. Check if there is an existing vertex in the `activated` dependency graph for the dependency this state's `requirement` relates to 12. If there is no existing vertex in the `activated` dependency graph for the dependency this state's `requirement` relates to, `activate_new_spec`. This creates a new vertex in the `activated` dependency graph, with it's payload set to the possibility's `PossibilitySet`. It also pushes a new `DependencyState`, with the now-activated `PossibilitySet`'s own dependencies. Go to #6 13. If there is an existing, `activated` vertex for the dependency, `attempt_to_filter_existing_spec` - This filters the contents of the existing vertex's `PossibilitySet` by the current state's `requirement` - If any possibilities remain within the `PossibilitySet`, it updates the activated vertex's payload with the new, filtered state and pushes a new `DependencyState` - If no possibilities remain within the `PossibilitySet` after filtering, or if the current state's `PossibilitySet` had a different set of sub-dependecy requirements to the existing vertex's `PossibilitySet`, `create_conflict` and `unwind_for_conflict`, back to the last `DependencyState` that has a chance to not generate a conflict. Go to #6 15. Terminate with the topmost state's dependency graph when there are no more requirements left 16. For each vertex with a payload of allowable versions for this resolution (i.e., a `PossibilitySet`), pick a single specific version. ### Optimal unwinding For our backtracking algorithm to be efficient as well as correct, we need to unwind efficiently after a conflict is encountered. Unwind too far and we'll miss valid resolutions - once we unwind passed a DependencyState we can never get there again. Unwind too little and resolution will be extremely slow - we'll repeatedly hit the same conflict, processing many unnecessary iterations before getting to a branch that avoids it. To unwind the optimal amount, we consider the current conflict, along with all the previous unwinds that have determined our current state. 1. First, consider the current conflict as follows: - Find the earliest (lowest index) set of requirements which combine to cause the conflict. Any non-binding requirements can be ignored, as removing them would not resolve the current onflict - For each binding requirement, find all the alternative possibilities that would relax the requirement: - the requirement's DependencyState might have alternative possibilities that would satisfy all the other requirements - the parent of the requirement might have alternative possibilities that would prevent the requirement existing - the parent of the parent of the requirement might have alternative possibilities that would prevent the parent, and thus the requirement, from existing - etc., etc. - Group all of the above possibilities into an array, and pick the one with the highest index (i.e., the smallest rewind) as our candidate rewind 2. Next, consider any previous unwinds that were not executed (because a different, smaller unwind was chosen instead): - Ignore any previously unused unwinds that would now unwind further than the highest index found in (1), if any - For the remaining unused unwinds, check whether the unwind has a chance of preventing us encountering the current conflict. For this to be the case, the unwind must have been rejected in favour of an unwind to one of the states in the current conflict's requirement tree - If any such unwinds exist, use the one with the highest index (smallest unwind) instead of the one found in (1) 3a. If no possible unwind was found in (1) and (2), raise a VersionConflict error as resolution is not possible. 3b. Filter the state that we're unwinding to, in order to remove any possibilities we know will result in a conflict. Consider all possible unwinds to the chosen state (there may be several, amasssed from previous unused unwinds for different conflicts) when doing this filtering - only possibilities that will certainly result in *all* of those conflicts can be filtered out as having no chance of resolution 4. Update the list of unused unwinds: - Add all possible unwinds for the current conflict - Update the `requirements_unwound_to_instead` attribute on any considered unwind that was only rejected because it had a lower index than the chosen one - Remove all unwinds to a state greater than or equal to the chosen unwind 5. Go to #6 in the main loop ## Specification Provider The `SpecificationProvider` module forms the basis for the key integration point for a client library with Molinillo. Its methods convert the client's domain-specific model objects into concepts the resolver understands: - Nested dependencies - Names - Requirement satisfaction - Finding specifications (known internally as `possibilities`) - Sorting dependencies (for the sake of reasonable resolver performance) molinillo-0.6.4/LICENSE0000644000004100000410000000215513176277533014575 0ustar www-datawww-dataThis project is licensed under the MIT license. Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. molinillo-0.6.4/CHANGELOG.md0000644000004100000410000002675213176277533015412 0ustar www-datawww-data# Molinillo Changelog ## 0.6.4 (2017-10-29) ##### Enhancements * Reduce memory usage during resolution by making the `Vertex#requirements` array unique. [Grey Baker](https://github.com/greysteil) [Jan Krutisch](https://github.com/halfbyte) ##### Bug Fixes * None. ## 0.6.3 (2017-09-06) ##### Enhancements * None. ##### Bug Fixes * Handle the case where an unwind occurs to a requirement that directly caused the current conflict but could also have been unwound to directly from previous conflicts. In this case, filtering must not remove any possibilities that could have avoided the previous conflicts (even if they would not avoid the current one). [Grey Baker](https://github.com/greysteil) ## 0.6.2 (2017-08-25) ##### Enhancements * None. ##### Bug Fixes * Insist each PossibilitySet contains contiguous versions. Fixes a regression where an older dependency version with identical sub-dependencies to the latest version may be preferred over the second-latest version. [Grey Baker](https://github.com/greysteil) ## 0.6.1 (2017-08-01) ##### Enhancements * None. ##### Bug Fixes * Allow the set of dependencies for a given possibility to change over time, fixing a regression in 0.6.0. [Samuel Giddins](https://github.com/segiddins) ## 0.6.0 (2017-07-27) ##### Breaking * Objects returned by `dependencies_for` and passed to `resolve` must properly implement both `==` and `eql?`, such that they return `true` when they exhibit the same behavior in `requirement_satisfied_by?`. ##### Enhancements * Speed up dependency resolution by considering multiple possible versions of a dependency at once, grouped by sub-dependencies. Groups are then filtered as additional requirements are introduced. If a group's sub-dependencies cause conflicts the entire group can be discarded, which reduces the number of possibilities that have to be tested to find a resolution. [Grey Baker](https://github.com/greysteil) [Samuel Giddins](https://github.com/segiddins) [#69](https://github.com/CocoaPods/Molinillo/pull/69) * Check for locked requirements when generating a new state's possibilities array, and reduce possibilities set accordingly. Reduces scope for erroneous VersionConflict errors. [Grey Baker](https://github.com/greysteil) [#67](https://github.com/CocoaPods/Molinillo/pull/67) * Add `VersionConflict#message_with_trees` for consumers who prefer a more verbose conflict message that includes full requirement trees for all conflicts. [Samuel Giddins](https://github.com/segiddins) ##### Bug Fixes * Improve unwinding by considering previous conflicts for the same dependency when deciding which state to unwind to. Previously, prior conflicts were stored in a hash indexed by their name, with only the most recent conflict stored for each dependency. With this fix, Molinillo can resolve anything that's thrown at it. 🎉 [Grey Baker](https://github.com/greysteil) [#73](https://github.com/CocoaPods/Molinillo/pull/73) * Only raise CircularDependency errors if they prevent resolution. [Ian Young](https://github.com/iangreenleaf) [Grey Baker](https://github.com/greysteil) [#78](https://github.com/CocoaPods/Molinillo/pull/78) * Consider additional (binding) requirements that caused a conflict when determining which state to unwind to. Previously, in some cases Molinillo would erroneously throw a VersionConflict error if multiple requirements combined to cause a conflict. [Grey Baker](https://github.com/greysteil) [#72](https://github.com/CocoaPods/Molinillo/pull/72) * Consider previous conflicts when determining the state to unwind to. If a previous conflict, for a different dependency, is the reason we ended up with the current conflict, then unwinding to a state that would not have caused that conflict could prevent the current one, too. [Grey Baker](https://github.com/greysteil) [#72](https://github.com/CocoaPods/Molinillo/pull/72) ## 0.5.7 (2017-03-03) ##### Enhancements * None. ##### Bug Fixes * Keep a stack of parents per requirement, so unwinding past a swap point that updated the parent of the requirement works. [Samuel Giddins](https://github.com/segiddins) [bundler#5425](https://github.com/bundler/bundler/issues/5425) ## 0.5.6 (2017-02-08) ##### Enhancements * None. ##### Bug Fixes * Only reset the parent of a requirement after swapping when its original parent was the same vertex being swapped. [Samuel Giddins](https://github.com/segiddins) [bundler#5359](https://github.com/bundler/bundler/issues/5359) [bundler#5362](https://github.com/bundler/bundler/issues/5362) ## 0.5.5 (2017-01-07) ##### Enhancements * None. ##### Bug Fixes * Only remove requirements from the to-be-resolved list if there are no activated vertices depending upon them after swapping. [Samuel Giddins](https://github.com/segiddins) [bundler#5294](https://github.com/bundler/bundler/issues/5294) ## 0.5.4 (2016-11-14) ##### Enhancements * None. ##### Bug Fixes * Fix unwinding when both sides of a conflict have a common parent requirement. [Samuel Giddins](https://github.com/segiddins) [bundler#5154](https://github.com/bundler/bundler/issues/5154) ## 0.5.3 (2016-10-28) ##### Enhancements * None. ##### Bug Fixes * Fixed a regression in v0.5.2 that could cause resolution to fail after swapping, because stale dependencies would still be in the requirements list. [Samuel Giddins](https://github.com/segiddins) [#48](https://github.com/CocoaPods/Molinillo/issues/48) * Rename `Action.name` to `Action.action_name` to avoid overriding `Module.name`. [Samuel Giddins](https://github.com/segiddins) [#50](https://github.com/CocoaPods/Molinillo/issues/50) ## 0.5.2 (2016-10-24) ##### Enhancements * None. ##### Bug Fixes * Fixed a bug where `Resolution#parent_of` would return the incorrect parent for a dependency after swapping had occurred, resulting in resolution failing. [Samuel Giddins](https://github.com/segiddins) [bundler#5059](https://github.com/bundler/bundler/issues/5059) ## 0.5.1 (2016-09-12) ##### Enhancements * None. ##### Bug Fixes * Fixed a bug where `Resolution#parent_of` would return the incorrect parent for a dependency, resulting in resolution failing. [Samuel Giddins](https://github.com/segiddins) [bundler#4961](https://github.com/bundler/bundler/issues/4961) ## 0.5.0 (2016-06-14) ##### Enhancements * Add an operation log to `DependencyGraph` to eliminate the need for graph copies during dependency resolution, resulting in a 3-100x speedup and reduction in allocations. [Samuel Giddins](https://github.com/segiddins) [bundler#4376](https://github.com/bundler/bundler/issues/4376) * Remove all metaprogramming to reduce array allocation overhead and improve discoverability. [Samuel Giddins](https://github.com/segiddins) ##### Bug Fixes * None. ## 0.4.5 (2016-04-30) ##### Enhancements * For performance, don't needlessly dup objects in `Resolution#push_state_for_requirements`. [Joe Rafaniello](https://github.com/jrafanie) ##### Bug Fixes * Recursively prune requirements when removing an orphan after swapping. [Daniel DeLeo](https://github.com/danielsdeleo) [berkshelf/solve#57](https://github.com/berkshelf/solve/issues/57) ## 0.4.4 (2016-02-28) ##### Bug Fixes * Fix mutating a frozen string in `NoSuchDependencyError#message`. [Samuel Giddins](https://github.com/segiddins) ## 0.4.3 (2016-02-18) ##### Enhancements * Add frozen string literal comments to all ruby files. [Samuel Giddins](https://github.com/segiddins) ##### Bug Fixes * Prune the dependency list when removing an orphan after swapping. [Samuel Giddins](https://github.com/segiddins) [bundler/bundler#4276](https://github.com/bundler/bundler/issues/4276) ## 0.4.2 (2016-01-30) ##### Bug Fixes * Detaching a vertex correctly removes it from the list of successors of its predecessors. [Samuel Giddins](https://github.com/segiddins) * Vertices orphaned after swapping dependencies are properly cleaned up from the graph of activated specs. [Samuel Giddins](https://github.com/segiddins) [bundler/bundler#4198](https://github.com/bundler/bundler/issues/4198) ## 0.4.1 (2015-12-30) ##### Enhancements * Ensure every API is 100% documented. [Samuel Giddins](https://github.com/segiddins) [#22](https://github.com/CocoaPods/Molinillo/issues/22) ## 0.4.0 (2015-07-27) ##### API Breaking Changes * The `DependencyGraph` no longer treats root vertices specially, nor does it maintain a direct reference to `edges`. Additionally, `Vertex` no longer has a reference to its parent graph. ##### Enhancements * Resolution has been sped up by 25x in some pathological cases, and in general recursive operations on a `DependencyGraph` or `Vertex` are now `O(n)`. [Samuel Giddins](https://github.com/segiddins) [Bundler#3803](https://github.com/bundler/bundler/issues/3803) * Re-sorting of dependencies is skipped when the unresolved dependency list has not changed, speeding up resolution of fully locked graphs. [Samuel Giddins](https://github.com/segiddins) ## 0.3.1 (2015-07-24) ##### Enhancements * Add `Conflict#activated_by_name` to allow even richer version conflict messages. [Samuel Giddins](https://github.com/segiddins) ##### Bug Fixes * Ensure `Conflict#requirement_trees` is exhaustive. [Samuel Giddins](https://github.com/segiddins) [Bundler#3860](https://github.com/bundler/bundler/issues/3860) ## 0.3.0 (2015-06-29) ##### Enhancements * Add the ability to optionally skip dependencies that have no possibilities. [Samuel Giddins](https://github.com/segiddins) ## 0.2.3 (2015-03-28) ##### Bug Fixes * Silence a silly MRI warning about declaring private attributes. [Piotr Szotkowski](https://github.com/chastell) [Bundler#3516](https://github.com/bundler/bundler/issues/3516) [Bundler#3525](https://github.com/bundler/bundler/issues/3525) ## 0.2.2 (2015-03-27) ##### Bug Fixes * Use an ivar in `DependencyGraph#initialize_copy` to silence an MRI warning. [Samuel Giddins](https://github.com/segiddins) [Bundler#3516](https://github.com/bundler/bundler/issues/3516) ## 0.2.1 (2015-02-21) * Allow resolving some pathological cases where the backjumping algorithm would skip over a valid possibility. [Samuel Giddins](https://github.com/segiddins) ## 0.2.0 (2014-12-25) * Institute stricter forward checking by backjumping to the source of a conflict, even if that source comes from the existing spec. This further improves performance in highly conflicting situations when sorting heuristics prove misleading. [Samuel Giddins](https://github.com/segiddins) [Smit Shah](https://github.com/Who828) * Add support for topologically sorting a dependency graph's vertices. [Samuel Giddins](https://github.com/segiddins) ## 0.1.2 (2014-11-19) ##### Enhancements * Improve performance in highly conflicting situations by backtracking more than one state at a time. [Samuel Giddins](https://github.com/segiddins) ##### Bug Fixes * Ensure that recursive invocations of `detach_vertex_named` don't lead to messaging `nil`. [Samuel Giddins](https://github.com/segiddins) [CocoaPods#2805](https://github.com/CocoaPods/CocoaPods/issues/2805) ## 0.1.1 (2014-11-06) * Ensure that an unwanted exception is not raised when an error occurs before the initial state has been pushed upon the stack. [Samuel Giddins](https://github.com/segiddins) ## 0.1.0 (2014-10-26) * Initial release. [Samuel Giddins](https://github.com/segiddins) molinillo-0.6.4/README.md0000644000004100000410000000264113176277533015047 0ustar www-datawww-data# Molinillo [![Build Status](https://img.shields.io/travis/CocoaPods/Molinillo/master.svg?style=flat)](https://travis-ci.org/CocoaPods/Molinillo) [![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/Molinillo.svg?style=flat)](https://codeclimate.com/github/CocoaPods/Molinillo) [![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/Molinillo.svg?style=flat)](https://codeclimate.com/github/CocoaPods/Molinillo) A generic dependency-resolution implementation. ## Installation Add this line to your application's Gemfile: ```ruby gem 'molinillo', :git => 'https://github.com/CocoaPods/Molinillo' ``` And then execute: ```bash $ bundle install ``` Or install it yourself as: ```bash $ gem install molinillo ``` ## Usage See the [ARCHITECTURE](ARCHITECTURE.md) file for an overview and look at the test suite for example usage. Better documentation and examples are forthcoming. ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a pull request ## The Name [Molinillo](http://en.wikipedia.org/wiki/Molinillo_(whisk)) is a special whisk used in Mexico in the preparation of beverages such as hot chocolate. Much like a dependency resolver, a molinillo helps take a list of ingredients and turn it into a delicious concoction! molinillo-0.6.4/molinillo.gemspec0000644000004100000410000000404213176277534017131 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "molinillo" s.version = "0.6.4" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Samuel E. Giddins"] s.date = "2017-10-29" s.email = ["segiddins@segiddins.me"] s.files = ["ARCHITECTURE.md", "CHANGELOG.md", "LICENSE", "README.md", "lib/molinillo.rb", "lib/molinillo/compatibility.rb", "lib/molinillo/delegates/resolution_state.rb", "lib/molinillo/delegates/specification_provider.rb", "lib/molinillo/dependency_graph.rb", "lib/molinillo/dependency_graph/action.rb", "lib/molinillo/dependency_graph/add_edge_no_circular.rb", "lib/molinillo/dependency_graph/add_vertex.rb", "lib/molinillo/dependency_graph/delete_edge.rb", "lib/molinillo/dependency_graph/detach_vertex_named.rb", "lib/molinillo/dependency_graph/log.rb", "lib/molinillo/dependency_graph/set_payload.rb", "lib/molinillo/dependency_graph/tag.rb", "lib/molinillo/dependency_graph/vertex.rb", "lib/molinillo/errors.rb", "lib/molinillo/gem_metadata.rb", "lib/molinillo/modules/specification_provider.rb", "lib/molinillo/modules/ui.rb", "lib/molinillo/resolution.rb", "lib/molinillo/resolver.rb", "lib/molinillo/state.rb"] s.homepage = "https://github.com/CocoaPods/Molinillo" s.licenses = ["MIT"] s.require_paths = ["lib"] s.rubygems_version = "1.8.23" s.summary = "Provides support for dependency resolution" if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 1.5"]) s.add_development_dependency(%q, [">= 0"]) else s.add_dependency(%q, ["~> 1.5"]) s.add_dependency(%q, [">= 0"]) end else s.add_dependency(%q, ["~> 1.5"]) s.add_dependency(%q, [">= 0"]) end end