%PDF- %PDF-
| Direktori : /opt/plesk/ruby/3.2.2/lib64/ruby/3.2.0/bundler/vendor/pub_grub/lib/pub_grub/ |
| Current File : //opt/plesk/ruby/3.2.2/lib64/ruby/3.2.0/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb |
require_relative 'partial_solution'
require_relative 'term'
require_relative 'incompatibility'
require_relative 'solve_failure'
module Bundler::PubGrub
class VersionSolver
attr_reader :logger
attr_reader :source
attr_reader :solution
def initialize(source:, root: Package.root, logger: Bundler::PubGrub.logger)
@logger = logger
@source = source
# { package => [incompatibility, ...]}
@incompatibilities = Hash.new do |h, k|
h[k] = []
end
@seen_incompatibilities = {}
@solution = PartialSolution.new
add_incompatibility Incompatibility.new([
Term.new(VersionConstraint.any(root), false)
], cause: :root)
propagate(root)
end
def solved?
solution.unsatisfied.empty?
end
# Returns true if there is more work to be done, false otherwise
def work
return false if solved?
next_package = choose_package_version
propagate(next_package)
if solved?
logger.info { "Solution found after #{solution.attempted_solutions} attempts:" }
solution.decisions.each do |package, version|
next if Package.root?(package)
logger.info { "* #{package} #{version}" }
end
false
else
true
end
end
def solve
work until solved?
solution.decisions
end
alias_method :result, :solve
private
def propagate(initial_package)
changed = [initial_package]
while package = changed.shift
@incompatibilities[package].reverse_each do |incompatibility|
result = propagate_incompatibility(incompatibility)
if result == :conflict
root_cause = resolve_conflict(incompatibility)
changed.clear
changed << propagate_incompatibility(root_cause)
elsif result # should be a Package
changed << result
end
end
changed.uniq!
end
end
def propagate_incompatibility(incompatibility)
unsatisfied = nil
incompatibility.terms.each do |term|
relation = solution.relation(term)
if relation == :disjoint
return nil
elsif relation == :overlap
# If more than one term is inconclusive, we can't deduce anything
return nil if unsatisfied
unsatisfied = term
end
end
if !unsatisfied
return :conflict
end
logger.debug { "derived: #{unsatisfied.invert}" }
solution.derive(unsatisfied.invert, incompatibility)
unsatisfied.package
end
def next_package_to_try
solution.unsatisfied.min_by do |term|
package = term.package
range = term.constraint.range
matching_versions = source.versions_for(package, range)
higher_versions = source.versions_for(package, range.upper_invert)
[matching_versions.count <= 1 ? 0 : 1, higher_versions.count]
end.package
end
def choose_package_version
if solution.unsatisfied.empty?
logger.info "No packages unsatisfied. Solving complete!"
return nil
end
package = next_package_to_try
unsatisfied_term = solution.unsatisfied.find { |t| t.package == package }
version = source.versions_for(package, unsatisfied_term.constraint.range).first
logger.debug { "attempting #{package} #{version}" }
if version.nil?
add_incompatibility source.no_versions_incompatibility_for(package, unsatisfied_term)
return package
end
conflict = false
source.incompatibilities_for(package, version).each do |incompatibility|
if @seen_incompatibilities.include?(incompatibility)
logger.debug { "knew: #{incompatibility}" }
next
end
@seen_incompatibilities[incompatibility] = true
add_incompatibility incompatibility
conflict ||= incompatibility.terms.all? do |term|
term.package == package || solution.satisfies?(term)
end
end
unless conflict
logger.info { "selected #{package} #{version}" }
solution.decide(package, version)
else
logger.info { "conflict: #{conflict.inspect}" }
end
package
end
def resolve_conflict(incompatibility)
logger.info { "conflict: #{incompatibility}" }
new_incompatibility = false
while !incompatibility.failure?
most_recent_term = nil
most_recent_satisfier = nil
difference = nil
previous_level = 1
incompatibility.terms.each do |term|
satisfier = solution.satisfier(term)
if most_recent_satisfier.nil?
most_recent_term = term
most_recent_satisfier = satisfier
elsif most_recent_satisfier.index < satisfier.index
previous_level = [previous_level, most_recent_satisfier.decision_level].max
most_recent_term = term
most_recent_satisfier = satisfier
difference = nil
else
previous_level = [previous_level, satisfier.decision_level].max
end
if most_recent_term == term
difference = most_recent_satisfier.term.difference(most_recent_term)
if difference.empty?
difference = nil
else
difference_satisfier = solution.satisfier(difference.inverse)
previous_level = [previous_level, difference_satisfier.decision_level].max
end
end
end
if previous_level < most_recent_satisfier.decision_level ||
most_recent_satisfier.decision?
logger.info { "backtracking to #{previous_level}" }
solution.backtrack(previous_level)
if new_incompatibility
add_incompatibility(incompatibility)
end
return incompatibility
end
new_terms = []
new_terms += incompatibility.terms - [most_recent_term]
new_terms += most_recent_satisfier.cause.terms.reject { |term|
term.package == most_recent_satisfier.term.package
}
if difference
new_terms << difference.invert
end
incompatibility = Incompatibility.new(new_terms, cause: Incompatibility::ConflictCause.new(incompatibility, most_recent_satisfier.cause))
new_incompatibility = true
partially = difference ? " partially" : ""
logger.info { "! #{most_recent_term} is#{partially} satisfied by #{most_recent_satisfier.term}" }
logger.info { "! which is caused by #{most_recent_satisfier.cause}" }
logger.info { "! thus #{incompatibility}" }
end
raise SolveFailure.new(incompatibility)
end
def add_incompatibility(incompatibility)
logger.debug { "fact: #{incompatibility}" }
incompatibility.terms.each do |term|
package = term.package
@incompatibilities[package] << incompatibility
end
end
end
end