summaryrefslogtreecommitdiff
path: root/lib/bundler/resolver.rb
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2022-09-05 09:15:30 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2022-09-05 14:37:12 +0900
commit3eca1e438db6fabaa7cd5e5a7120da147ac0ec26 (patch)
tree597be042af7d0052616ba41bb9d0b8d8ceedd094 /lib/bundler/resolver.rb
parent3767c6a90d8970f9b39e9ed116a7b9bbac3f9f26 (diff)
Merge https://github.com/rubygems/rubygems/commit/16c3535413afebcdbab7582c6017c27b5da8a8dc
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/6326
Diffstat (limited to 'lib/bundler/resolver.rb')
-rw-r--r--lib/bundler/resolver.rb149
1 files changed, 67 insertions, 82 deletions
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