diff options
author | Homu <homu@barosl.com> | 2016-08-25 23:12:53 +0900 |
---|---|---|
committer | Homu <homu@barosl.com> | 2016-08-25 23:12:53 +0900 |
commit | 8513ed1481dde5b47eead96dd0a1cfe3d77c4c84 (patch) | |
tree | d4e961360ad9e4986cdcb6332b69581299ca9dbe /lib | |
parent | 92aa19d8c979db9ccf54edfa172c4a9f7a6382de (diff) | |
parent | 2b3e175c13ddae4e95764b745e1589fbc1131dd1 (diff) | |
download | bundler-8513ed1481dde5b47eead96dd0a1cfe3d77c4c84.tar.gz |
Auto merge of #4654 - bundler:aa-ruby-version-conflict-message, r=indirect
Add resolver-style error messages for required Ruby conflicts
*Proposal:* When a gem in the Gemfile requires a Ruby version different than the Gemfile's `ruby`, print a conflict error message that looks more or less exactly like any other resolution conflict error.
*Current behavior:* On `master` today, Bundler will ignore the gem's required_ruby_version until install-time, and raise an exception like "require_ruby-1.0 requires ruby version > 9000, which is incompatible with the current version`.
After #4650 has been merged, Bundler will correctly reject gems that require a different version of Ruby, and instead print the error "Could not find `required_ruby` in any of the sources in your Gemfile".
*Rationale for change:* I believe that error message is surprising and misleading. The source _does_ contain `required ruby`, and I think we'll get bug reports about how our error message is wrong.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/bundler/definition.rb | 55 | ||||
-rw-r--r-- | lib/bundler/resolver.rb | 60 | ||||
-rw-r--r-- | lib/bundler/ruby_version.rb | 5 | ||||
-rw-r--r-- | lib/bundler/rubygems_ext.rb | 5 |
4 files changed, 95 insertions, 30 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index e1826746ff..dc3b4690d7 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -62,6 +62,7 @@ module Bundler @specs = nil @ruby_version = ruby_version + @lockfile = lockfile @lockfile_contents = String.new @locked_bundler_version = nil @locked_ruby_version = nil @@ -94,11 +95,7 @@ module Bundler @unlock[:gems] ||= [] @unlock[:sources] ||= [] - @unlock[:ruby] ||= if @ruby_version && @locked_ruby_version - unless locked_ruby_version_object = RubyVersion.from_string(@locked_ruby_version) - raise LockfileError, "Failed to create a `RubyVersion` object from " \ - "`#{@locked_ruby_version}` found in #{lockfile} -- try running `bundle update --ruby`." - end + @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object @ruby_version.diff(locked_ruby_version_object) end @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version) @@ -249,7 +246,7 @@ module Bundler else # Run a resolve against the locally available gems Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}") - last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, ruby_version, gem_version_promoter, additional_base_requirements_for_resolve) + last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve) end end end @@ -264,6 +261,8 @@ module Bundler dependency_names -= pinned_spec_names(source.specs) dependency_names.concat(source.unmet_deps).uniq! end + idx << Gem::Specification.new("ruby\0", RubyVersion.system.to_gem_version_with_patchlevel) + idx << Gem::Specification.new("rubygems\0", Gem::VERSION) end end @@ -340,6 +339,18 @@ module Bundler end end + def locked_ruby_version_object + return unless @locked_ruby_version + @locked_ruby_version_object ||= begin + unless version = RubyVersion.from_string(@locked_ruby_version) + raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \ + "#{@lockfile} could not be parsed. " \ + "Try running bundle update --ruby to resolve this." + end + version + end + end + def to_lock out = String.new @@ -728,8 +739,38 @@ module Bundler @locked_specs.any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) } end + # This list of dependencies is only used in #resolve, so it's OK to add + # the metadata dependencies here def expanded_dependencies - @expanded_dependencies ||= expand_dependencies(dependencies, @remote) + @expanded_dependencies ||= begin + ruby_versions = concat_ruby_version_requirements(@ruby_version) + if ruby_versions.empty? || !@ruby_version.exact? + concat_ruby_version_requirements(RubyVersion.system) + concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby] + end + + metadata_dependencies = [ + Dependency.new("ruby\0", ruby_versions), + Dependency.new("rubygems\0", Gem::VERSION), + ] + expand_dependencies(dependencies + metadata_dependencies, @remote) + end + end + + def concat_ruby_version_requirements(ruby_version, ruby_versions = []) + return ruby_versions unless ruby_version + if ruby_version.patchlevel + ruby_versions << ruby_version.to_gem_version_with_patchlevel + else + ruby_versions.concat(ruby_version.versions.map do |version| + requirement = Gem::Requirement.new(version) + if requirement.exact? + "~> #{version}.0" + else + requirement + end + end) + end end def expand_dependencies(dependencies, remote = false) diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index e1d993831f..3ab4ae6f38 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -75,8 +75,8 @@ module Bundler def initialize(a) super - @required_by = [] - @activated = [] + @required_by = [] + @activated_platforms = [] @dependencies = nil @specs = {} @@ -88,13 +88,13 @@ module Bundler def initialize_copy(o) super @required_by = o.required_by.dup - @activated = o.activated.dup + @activated_platforms = o.activated.dup end def to_specs specs = {} - @activated.each do |p| + @activated_platforms.each do |p| next unless s = @specs[p] platform = generic(Gem::Platform.new(s.platform)) next if specs[platform] @@ -107,7 +107,9 @@ module Bundler end def activate_platform!(platform) - @activated << platform if !@activated.include?(platform) && for?(platform, nil) + return unless for?(platform) + return if @activated_platforms.include?(platform) + @activated_platforms << platform end def name @@ -122,17 +124,9 @@ module Bundler @source ||= first.source end - def for?(platform, ruby_version) + def for?(platform) spec = @specs[platform] - return false unless spec - - return true if ruby_version.nil? - # Only allow endpoint specifications since they won't hit the network to - # fetch the full gemspec when calling required_ruby_version - return true if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification) - return true if spec.required_ruby_version.nil? - - spec.required_ruby_version.satisfied_by?(ruby_version.to_gem_version_with_patchlevel) + !spec.nil? end def to_s @@ -140,7 +134,11 @@ module Bundler end def dependencies_for_activated_platforms - @activated.map {|p| __dependencies[p] }.flatten + dependencies = @activated_platforms.map {|p| __dependencies[p] } + metadata_dependencies = @activated_platforms.map do |platform| + metadata_dependencies(@specs[platform], platform) + end + dependencies.concat(metadata_dependencies).flatten end def platforms_for_dependency_named(dependency) @@ -163,6 +161,21 @@ module Bundler dependencies end end + + def metadata_dependencies(spec, platform) + return [] unless spec + # Only allow endpoint specifications since they won't hit the network to + # fetch the full gemspec when calling required_ruby_version + 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) + 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) + end + dependencies + end end # Figures out the best possible configuration of gems that satisfies @@ -175,14 +188,14 @@ module Bundler # ==== Returns # <GemBundle>,nil:: If the list of dependencies can be resolved, a # collection of gemspecs is returned. Otherwise, nil is returned. - def self.resolve(requirements, index, source_requirements = {}, base = [], ruby_version = nil, gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = []) + def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = []) base = SpecSet.new(base) unless base.is_a?(SpecSet) - resolver = new(index, source_requirements, base, ruby_version, gem_version_promoter, additional_base_requirements) + resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements) result = resolver.start(requirements) SpecSet.new(result) end - def initialize(index, source_requirements, base, ruby_version, gem_version_promoter, additional_base_requirements) + def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements) @index = index @source_requirements = source_requirements @base = base @@ -194,14 +207,15 @@ module Bundler @base_dg.add_vertex(ls.name, DepProxy.new(dep, ls.platform), true) end additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) } - @ruby_version = ruby_version @gem_version_promoter = gem_version_promoter end def start(requirements) verify_gemfile_dependencies_are_found!(requirements) dg = @resolver.resolve(requirements, @base_dg) - dg.map(&:payload).map(&:to_specs).flatten + dg.map(&:payload). + reject {|sg| sg.name.end_with?("\0") }. + map(&:to_specs).flatten rescue Molinillo::VersionConflict => e raise VersionConflict.new(e.conflicts.keys.uniq, e.message) rescue Molinillo::CircularDependencyError => e @@ -282,7 +296,7 @@ module Bundler @gem_version_promoter.sort_versions(dependency, spec_groups) end end - search.select {|sg| sg.for?(platform, @ruby_version) }.each {|sg| sg.activate_platform!(platform) } + search.select {|sg| sg.for?(platform) }.each {|sg| sg.activate_platform!(platform) } end def index_for(dependency) @@ -307,7 +321,7 @@ module Bundler def requirement_satisfied_by?(requirement, activated, spec) return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) - spec.activate_platform!(requirement.__platform) || spec.for?(requirement.__platform, nil) + spec.activate_platform!(requirement.__platform) || spec.for?(requirement.__platform) end def sort_dependencies(dependencies, activated, conflicts) diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 9321f94c23..8c050c6d31 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -128,6 +128,11 @@ module Bundler end end + def exact? + return @exact if defined?(@exact) + @exact = versions.all? {|v| Gem::Requirement.create(v).exact? } + end + private def matches?(requirements, version) diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index fc8eadd186..7cd83e631e 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -159,6 +159,11 @@ module Gem def none? @none ||= (to_s == ">= 0") end unless allocate.respond_to?(:none?) + + def exact? + return false unless @requirements.size == 1 + @requirements[0][0] == "=" + end unless allocate.respond_to?(:exact?) end class Platform |