summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHomu <homu@barosl.com>2016-08-25 23:12:53 +0900
committerHomu <homu@barosl.com>2016-08-25 23:12:53 +0900
commit8513ed1481dde5b47eead96dd0a1cfe3d77c4c84 (patch)
treed4e961360ad9e4986cdcb6332b69581299ca9dbe /lib
parent92aa19d8c979db9ccf54edfa172c4a9f7a6382de (diff)
parent2b3e175c13ddae4e95764b745e1589fbc1131dd1 (diff)
downloadbundler-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.rb55
-rw-r--r--lib/bundler/resolver.rb60
-rw-r--r--lib/bundler/ruby_version.rb5
-rw-r--r--lib/bundler/rubygems_ext.rb5
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