From 3eca1e438db6fabaa7cd5e5a7120da147ac0ec26 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Sep 2022 09:15:30 +0900 Subject: Merge https://github.com/rubygems/rubygems/commit/16c3535413afebcdbab7582c6017c27b5da8a8dc --- lib/bundler/resolver.rb | 149 ++++++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 82 deletions(-) (limited to 'lib/bundler/resolver.rb') diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index a74af45027..fcb3812c5a 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -3,6 +3,7 @@ module Bundler class Resolver require_relative "vendored_molinillo" + require_relative "resolver/base" require_relative "resolver/spec_group" include GemHelpers @@ -25,15 +26,13 @@ module Bundler def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) @source_requirements = source_requirements - @base = base + @base = Resolver::Base.new(base, additional_base_requirements) @resolver = Molinillo::Resolver.new(self, self) @results_for = {} @search_for = {} - @additional_base_requirements = additional_base_requirements @platforms = platforms @resolving_only_for_ruby = platforms == [Gem::Platform::RUBY] @gem_version_promoter = gem_version_promoter - @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major? end def start(requirements, exclude_specs: []) @@ -43,18 +42,11 @@ module Bundler remove_from_candidates(spec) end - @base_dg = Molinillo::DependencyGraph.new - @base.each do |ls| - dep = Dependency.new(ls.name, ls.version) - @base_dg.add_vertex(ls.name, DepProxy.get_proxy(dep, ls.platform), true) - end - @additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) } - @gem_version_promoter.prerelease_specified = @prerelease_specified = {} requirements.each {|dep| @prerelease_specified[dep.name] ||= dep.prerelease? } verify_gemfile_dependencies_are_found!(requirements) - result = @resolver.resolve(requirements, @base_dg). + result = @resolver.resolve(requirements). map(&:payload). reject {|sg| sg.name.end_with?("\0") }. map(&:to_specs). @@ -62,8 +54,20 @@ module Bundler SpecSet.new(SpecSet.new(result).for(regular_requirements, false, @platforms)) rescue Molinillo::VersionConflict => e + conflicts = e.conflicts + + deps_to_unlock = conflicts.values.inject([]) do |deps, conflict| + deps |= conflict.requirement_trees.flatten.map {|req| base_requirements[req.name] }.compact + end + + if deps_to_unlock.any? + @base.unlock_deps(deps_to_unlock) + reset_spec_cache + retry + end + message = version_conflict_message(e) - raise VersionConflict.new(e.conflicts.keys.uniq, message) + raise VersionConflict.new(conflicts.keys.uniq, message) rescue Molinillo::CircularDependencyError => e names = e.dependencies.sort_by(&:name).map {|d| "gem '#{d.name}'" } raise CyclicDependencyError, "Your bundle requires gems that depend" \ @@ -118,31 +122,22 @@ module Bundler dependency = dependency_proxy.dep name = dependency.name @search_for[dependency_proxy] ||= begin - results = results_for(dependency) + @base[name].select {|spec| requirement_satisfied_by?(dependency, nil, spec) } + locked_results = @base[name].select {|spec| requirement_satisfied_by?(dependency, nil, spec) } + locked_requirement = base_requirements[name] + results = results_for(dependency) + locked_results + results = results.select {|spec| requirement_satisfied_by?(locked_requirement, nil, spec) } if locked_requirement - if vertex = @base_dg.vertex_named(name) - locked_requirement = vertex.payload.requirement - end - - if !@prerelease_specified[name] && (!@use_gvp || locked_requirement.nil?) + if !@prerelease_specified[name] && locked_results.empty? # Move prereleases to the beginning of the list, so they're considered # last during resolution. pre, results = results.partition {|spec| spec.version.prerelease? } results = pre + results end - spec_groups = if results.any? - nested = [] - results.each do |spec| - version, specs = nested.last - if version == spec.version - specs << spec - else - nested << [spec.version, [spec]] - end - end - nested.reduce([]) do |groups, (version, specs)| - next groups if locked_requirement && !locked_requirement.satisfied_by?(version) + if results.any? + results = @gem_version_promoter.sort_versions(dependency, results) + + results.group_by(&:version).reduce([]) do |groups, (_, specs)| next groups unless specs.any? {|spec| spec.match_platform(platform) } specs_by_platform = Hash.new do |current_specs, current_platform| @@ -165,13 +160,6 @@ module Bundler else [] end - # GVP handles major itself, but it's still a bit risky to trust it with it - # until we get it settled with new behavior. For 2.x it can take over all cases. - if !@use_gvp - spec_groups - else - @gem_version_promoter.sort_versions(dependency, spec_groups) - end end end @@ -197,12 +185,6 @@ module Bundler "Gemfile" end - def name_for_locking_dependency_source - Bundler.default_lockfile.basename.to_s - rescue StandardError - "Gemfile.lock" - end - def requirement_satisfied_by?(requirement, activated, spec) requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) end @@ -216,7 +198,7 @@ module Bundler name = name_for(dependency) vertex = activated.vertex_named(name) [ - @base_dg.vertex_named(name) ? 0 : 1, + @base[name].any? ? 0 : 1, vertex.payload ? 0 : 1, vertex.root? ? 0 : 1, amount_constrained(dependency), @@ -235,9 +217,12 @@ module Bundler private + def base_requirements + @base.base_requirements + end + def remove_from_candidates(spec) @base.delete(spec) - @gem_version_promoter.reset @results_for.keys.each do |dep| next unless dep.name == spec.name @@ -245,7 +230,12 @@ module Bundler @results_for[dep].reject {|s| s.name == spec.name && s.version == spec.version } end + reset_spec_cache + end + + def reset_spec_cache @search_for = {} + @gem_version_promoter.reset end # returns an integer \in (-\infty, 0] @@ -337,18 +327,6 @@ module Bundler e.message_with_trees( :full_message_for_conflict => lambda do |name, conflict| - o = if name.end_with?("\0") - String.new("Bundler found conflicting requirements for the #{name} version:") - else - String.new("Bundler could not find compatible versions for gem \"#{name}\":") - end - o << %(\n) - if conflict.locked_requirement - o << %( In snapshot (#{name_for_locking_dependency_source}):\n) - o << %( #{SharedHelpers.pretty_dependency(conflict.locked_requirement)}\n) - o << %(\n) - end - o << %( In #{name_for_explicit_dependency_source}:\n) trees = conflict.requirement_trees # called first, because we want to reduce the amount of work required to find maximal empty sets @@ -367,30 +345,41 @@ module Bundler trees.sort_by! {|t| t.reverse.map(&:name) } end - o << trees.map do |tree| - t = "".dup - depth = 2 + if trees.size > 1 || name == "bundler" + o = if name.end_with?("\0") + String.new("Bundler found conflicting requirements for the #{name} version:") + else + String.new("Bundler could not find compatible versions for gem \"#{name}\":") + end + o << %(\n) + o << %( In #{name_for_explicit_dependency_source}:\n) + o << trees.map do |tree| + t = "".dup + depth = 2 - base_tree = tree.first - base_tree_name = base_tree.name + base_tree = tree.first + base_tree_name = base_tree.name - if base_tree_name.end_with?("\0") - t = nil - else - tree.each do |req| - t << " " * depth << SharedHelpers.pretty_dependency(req) - unless tree.last == req - if spec = conflict.activated_by_name[req.name] - t << %( was resolved to #{spec.version}, which) + if base_tree_name.end_with?("\0") + t = nil + else + tree.each do |req| + t << " " * depth << SharedHelpers.pretty_dependency(req) + unless tree.last == req + if spec = conflict.activated_by_name[req.name] + t << %( was resolved to #{spec.version}, which) + end + t << %( depends on) end - t << %( depends on) + t << %(\n) + depth += 1 end - t << %(\n) - depth += 1 end - end - t - end.compact.join("\n") + t + end.compact.join("\n") + else + o = String.new + end if name == "bundler" o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION})) @@ -414,17 +403,13 @@ module Bundler end elsif name.end_with?("\0") o << %(\n Current #{name} version:\n #{SharedHelpers.pretty_dependency(@metadata_requirements.find {|req| req.name == name })}\n\n) - elsif conflict.locked_requirement - o << "\n" - o << %(Deleting your #{name_for_locking_dependency_source} file and running `bundle install` will rebuild your snapshot from scratch, using only\n) - o << %(the gems in your Gemfile, which may resolve the conflict.\n) elsif !conflict.existing o << "\n" relevant_source = conflict.requirement.source || source_for(name) - extra_message = if conflict.requirement_trees.first.size > 1 - ", which is required by gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}'," + extra_message = if trees.first.size > 1 + ", which is required by gem '#{SharedHelpers.pretty_dependency(trees.first[-2])}'," else "" end -- cgit v1.2.3