1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
# 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<Object>] 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<Object>] 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<Object>] the dependencies responsible for causing the error
attr_reader :dependencies
# Initializes a new error with the given circular vertices.
# @param [Array<DependencyGraph::Vertex>] 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
|