diff options
author | The Bundler Bot <bot@bundler.io> | 2018-08-14 23:26:45 +0000 |
---|---|---|
committer | The Bundler Bot <bot@bundler.io> | 2018-08-14 23:26:45 +0000 |
commit | 33166ba1c8cc95f7585bc6e8d594014dc6dc8d29 (patch) | |
tree | 0a7e61b9b26121d31cda5de91b6da25cb1141eb9 | |
parent | a43c65fd228c1a70093e7a191de263bd2cd1d779 (diff) | |
parent | 48f6a18c2645159e22b48390d02a84a513011e37 (diff) | |
download | bundler-33166ba1c8cc95f7585bc6e8d594014dc6dc8d29.tar.gz |
Auto merge of #6647 - bundler:segiddins/conflict-message-improvements, r=indirect
Make version conflict messages better
~~This is a WIP. Need to wait until https://github.com/CocoaPods/Molinillo/pull/99 is merged & released~~
### What was the end-user problem that led to this PR?
The problem was Bundler version messages could be confusing.
Closes https://github.com/bundler/bundler/issues/6642
Closes https://github.com/bundler/bundler/issues/6620
### What was your diagnosis of the problem?
My diagnosis was we barf out a bunch of "conflicts", not all of which are actually conflicts. We could also print a whole bunch of requirements that didn't _actually_ contribute to the conflict at hand.
Messaging was also super weird when there were `required_ruby_version` conflicts, since we called "ruby" a gem.
### What is your fix for the problem, implemented in this PR?
Improves Bundler version conflict messages in multiple cases. (conflict on `required_ruby_version`, conflict on 2.0 containing redundant platforms, conflict on 2.0 not being reduced to minimal form, showing irrelevant conflicts that dont actually conflict, actually reducing all type of conflicts to the minimal set).
### Why did you choose this fix out of the possible options?
I chose this fix because...
-rw-r--r-- | lib/bundler/definition.rb | 4 | ||||
-rw-r--r-- | lib/bundler/resolver.rb | 38 | ||||
-rw-r--r-- | lib/bundler/resolver/spec_group.rb | 4 | ||||
-rw-r--r-- | lib/bundler/source/metadata.rb | 6 | ||||
-rw-r--r-- | lib/bundler/version_ranges.rb | 56 | ||||
-rw-r--r-- | spec/bundler/version_ranges_spec.rb | 3 | ||||
-rw-r--r-- | spec/install/gems/resolving_spec.rb | 9 | ||||
-rw-r--r-- | spec/resolver/basic_spec.rb | 4 | ||||
-rw-r--r-- | spec/support/indexes.rb | 4 |
9 files changed, 101 insertions, 27 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index df8ee1f54b..3964c07fb5 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -855,8 +855,8 @@ module Bundler concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby] end [ - Dependency.new("ruby\0", ruby_versions), - Dependency.new("rubygems\0", Gem::VERSION), + Dependency.new("Ruby\0", ruby_versions), + Dependency.new("RubyGems\0", Gem::VERSION), ] end end diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 8872b861be..3fd1706371 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -302,10 +302,23 @@ module Bundler end def version_conflict_message(e) + # only show essential conflicts, if possible + conflicts = e.conflicts.dup + conflicts.delete_if do |_name, conflict| + deps = conflict.requirement_trees.map(&:last).flatten(1) + !Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) + end + e = Molinillo::VersionConflict.new(conflicts, e.specification_provider) unless conflicts.empty? + + solver_name = "Bundler" + possibility_type = "gem" e.message_with_trees( - :solver_name => "Bundler", - :possibility_type => "gem", + :solver_name => solver_name, + :possibility_type => possibility_type, :reduce_trees => lambda do |trees| + # called first, because we want to reduce the amount of work required to find maximal empty sets + trees.uniq! {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } } + # bail out if tree size is too big for Array#combination to make any sense return trees if trees.size > 15 maximal = 1.upto(trees.size).map do |size| @@ -313,10 +326,8 @@ module Bundler end.flatten(1).select do |deps| Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) end.min_by(&:size) - trees.reject! {|t| !maximal.include?(t.last) } if maximal - trees = trees.sort_by {|t| t.flatten.map(&:to_s) } - trees.uniq! {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } } + trees.reject! {|t| !maximal.include?(t.last) } if maximal trees.sort_by {|t| t.reverse.map(&:name) } end, @@ -351,7 +362,11 @@ module Bundler [] end.compact.map(&:to_s).uniq.sort - o << "Could not find gem '#{SharedHelpers.pretty_dependency(conflict.requirement)}'" + metadata_requirement = name.end_with?("\0") + + o << "Could not find gem '" unless metadata_requirement + o << SharedHelpers.pretty_dependency(conflict.requirement) + o << "'" unless metadata_requirement if conflict.requirement_trees.first.size > 1 o << ", which is required by " o << "gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}'," @@ -360,12 +375,21 @@ module Bundler o << if relevant_sources.empty? "in any of the sources.\n" + elsif metadata_requirement + "is not available in #{relevant_sources.join(" or ")}" else "in any of the relevant sources:\n #{relevant_sources * "\n "}\n" end end end, - :version_for_spec => lambda {|spec| spec.version } + :version_for_spec => lambda {|spec| spec.version }, + :incompatible_version_message_for_conflict => lambda do |name, _conflict| + if name.end_with?("\0") + %(#{solver_name} found conflicting requirements for the #{name} version:) + else + %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) + end + end ) end diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb index 34d043aed7..119f63b5c8 100644 --- a/lib/bundler/resolver/spec_group.rb +++ b/lib/bundler/resolver/spec_group.rb @@ -94,10 +94,10 @@ module Bundler return [] if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification) dependencies = [] if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none? - dependencies << DepProxy.new(Gem::Dependency.new("ruby\0", spec.required_ruby_version), platform) + dependencies << DepProxy.new(Gem::Dependency.new("Ruby\0", spec.required_ruby_version), platform) end if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none? - dependencies << DepProxy.new(Gem::Dependency.new("rubygems\0", spec.required_rubygems_version), platform) + dependencies << DepProxy.new(Gem::Dependency.new("RubyGems\0", spec.required_rubygems_version), platform) end dependencies end diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb index 93909002c7..ff8861c710 100644 --- a/lib/bundler/source/metadata.rb +++ b/lib/bundler/source/metadata.rb @@ -5,8 +5,10 @@ module Bundler class Metadata < Source def specs @specs ||= Index.build do |idx| - idx << Gem::Specification.new("ruby\0", RubyVersion.system.to_gem_version_with_patchlevel) - idx << Gem::Specification.new("rubygems\0", Gem::VERSION) + idx << Gem::Specification.new("Ruby\0", RubyVersion.system.to_gem_version_with_patchlevel) + idx << Gem::Specification.new("RubyGems\0", Gem::VERSION) do |s| + s.required_rubygems_version = Gem::Requirement.default + end idx << Gem::Specification.new do |s| s.name = "bundler" diff --git a/lib/bundler/version_ranges.rb b/lib/bundler/version_ranges.rb index ec25716cde..12a956d6a0 100644 --- a/lib/bundler/version_ranges.rb +++ b/lib/bundler/version_ranges.rb @@ -5,11 +5,42 @@ module Bundler NEq = Struct.new(:version) ReqR = Struct.new(:left, :right) class ReqR - Endpoint = Struct.new(:version, :inclusive) + Endpoint = Struct.new(:version, :inclusive) do + def <=>(other) + if version.equal?(INFINITY) + return 0 if other.version.equal?(INFINITY) + return 1 + elsif other.version.equal?(INFINITY) + return -1 + end + + comp = version <=> other.version + return comp unless comp.zero? + + if inclusive && !other.inclusive + 1 + elsif !inclusive && other.inclusive + -1 + else + 0 + end + end + end + def to_s "#{left.inclusive ? "[" : "("}#{left.version}, #{right.version}#{right.inclusive ? "]" : ")"}" end - INFINITY = Object.new.freeze + INFINITY = begin + inf = Object.new + def inf.to_s + "∞" + end + def inf.<=>(other) + return 0 if other.equal?(self) + 1 + end + inf.freeze + end ZERO = Gem::Version.new("0.a") def cover?(v) @@ -32,6 +63,15 @@ module Bundler left.version == right.version end + def <=>(other) + return -1 if other.equal?(INFINITY) + + comp = left <=> other.left + return comp unless comp.zero? + + right <=> other.right + end + UNIVERSAL = ReqR.new(ReqR::Endpoint.new(Gem::Version.new("0.a"), true), ReqR::Endpoint.new(ReqR::INFINITY, false)).freeze end @@ -57,7 +97,7 @@ module Bundler end.uniq ranges, neqs = ranges.partition {|r| !r.is_a?(NEq) } - [ranges.sort_by {|range| [range.left.version, range.left.inclusive ? 0 : 1] }, neqs.map(&:version)] + [ranges.sort, neqs.map(&:version)] end def self.empty?(ranges, neqs) @@ -66,8 +106,14 @@ module Bundler next false if curr_range.single? && neqs.include?(curr_range.left.version) next curr_range if last_range.right.version == ReqR::INFINITY case last_range.right.version <=> curr_range.left.version - when 1 then next curr_range - when 0 then next(last_range.right.inclusive && curr_range.left.inclusive && !neqs.include?(curr_range.left.version) && curr_range) + # higher + when 1 then next ReqR.new(curr_range.left, last_range.right) + # equal + when 0 + if last_range.right.inclusive && curr_range.left.inclusive && !neqs.include?(curr_range.left.version) + ReqR.new(curr_range.left, [curr_range.right, last_range.right].max) + end + # lower when -1 then next false end end diff --git a/spec/bundler/version_ranges_spec.rb b/spec/bundler/version_ranges_spec.rb index ccbb9285d5..bca044b0c0 100644 --- a/spec/bundler/version_ranges_spec.rb +++ b/spec/bundler/version_ranges_spec.rb @@ -25,9 +25,12 @@ RSpec.describe Bundler::VersionRanges do include_examples "empty?", false, ">= 1.0.0", "< 2.0.0" include_examples "empty?", false, "~> 1" include_examples "empty?", false, "~> 2.0", "~> 2.1" + include_examples "empty?", true, ">= 4.1.0", "< 5.0", "= 5.2.1" + include_examples "empty?", true, "< 5.0", "< 5.3", "< 6.0", "< 6", "= 5.2.0", "> 2", ">= 3.0", ">= 3.1", ">= 3.2", ">= 4.0.0", ">= 4.1.0", ">= 4.2.0", ">= 4.2", ">= 4" include_examples "empty?", true, "!= 1", "< 2", "> 2" include_examples "empty?", true, "!= 1", "<= 1", ">= 1" include_examples "empty?", true, "< 2", "> 2" + include_examples "empty?", true, "< 2", "> 2", "= 2" include_examples "empty?", true, "= 1", "!= 1" include_examples "empty?", true, "= 1", "= 2" include_examples "empty?", true, "= 1", "~> 2" diff --git a/spec/install/gems/resolving_spec.rb b/spec/install/gems/resolving_spec.rb index e58f32836c..f581522c71 100644 --- a/spec/install/gems/resolving_spec.rb +++ b/spec/install/gems/resolving_spec.rb @@ -142,15 +142,14 @@ RSpec.describe "bundle install with install-time dependencies" do expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") nice_error = strip_whitespace(<<-E).strip - Bundler could not find compatible versions for gem "ruby\0": + Bundler found conflicting requirements for the Ruby\0 version: In Gemfile: - ruby\0 (#{error_message_requirement}) + Ruby\0 (#{error_message_requirement}) require_ruby was resolved to 1.0, which depends on - ruby\0 (> 9000) + Ruby\0 (> 9000) - Could not find gem 'ruby\0 (> 9000)', which is required by gem 'require_ruby', in any of the relevant sources: - the local ruby installation + Ruby\0 (> 9000), which is required by gem 'require_ruby', is not available in the local ruby installation E expect(last_command.bundler_err).to end_with(nice_error) end diff --git a/spec/resolver/basic_spec.rb b/spec/resolver/basic_spec.rb index 6d2dea2fb3..a2d5e13377 100644 --- a/spec/resolver/basic_spec.rb +++ b/spec/resolver/basic_spec.rb @@ -157,10 +157,10 @@ Bundler could not find compatible versions for gem "a": s.required_ruby_version = "~> 2.0.0" end - gem "ruby\0", "1.8.7" + gem "Ruby\0", "1.8.7" end dep "foo" - dep "ruby\0", "1.8.7" + dep "Ruby\0", "1.8.7" deps = [] @deps.each do |d| diff --git a/spec/support/indexes.rb b/spec/support/indexes.rb index c56d6145a7..5f6c515735 100644 --- a/spec/support/indexes.rb +++ b/spec/support/indexes.rb @@ -76,7 +76,7 @@ module Spec gem "rack-mount", %w[0.4 0.5 0.5.1 0.5.2 0.6] # --- Pre-release support - gem "rubygems\0", ["1.3.2"] + gem "RubyGems\0", ["1.3.2"] # --- Rails versions "1.2.3 2.2.3 2.3.5 3.0.0.beta 3.0.0.beta1" do |version| @@ -413,7 +413,7 @@ module Spec gem("b", %w[0.9.0 1.5.0 2.0.0.pre]) # --- Pre-release support - gem "rubygems\0", ["1.3.2"] + gem "RubyGems\0", ["1.3.2"] end end end |