Class: Bundler::Resolver
Relationships & Source Files | |
Namespace Children | |
Classes:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Instance Chain:
|
|
Inherits: | Object |
Defined in: | lib/bundler/resolver.rb, lib/bundler/resolver/spec_group.rb |
Class Method Summary
- .new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) ⇒ Resolver constructor
- .platform_sort_key(platform)
-
.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
Figures out the best possible configuration of gems that satisfies the list of passed dependencies and any child dependencies without causing any gem activation errors.
-
.sort_platforms(platforms)
Sort platforms from most general to most specific.
Instance Attribute Summary
- #debug? ⇒ Boolean readonly
Molinillo::UI
- Included
#debug? | Whether or not debug messages should be printed. |
Instance Method Summary
- #after_resolution
- #before_resolution
-
#debug(depth = 0) ⇒ void
readonly
Conveys debug information to the user.
- #dependencies_for(specification)
- #index_for(dependency)
- #indicate_progress
- #name_for(dependency)
- #name_for_explicit_dependency_source
- #name_for_locking_dependency_source
- #relevant_sources_for_vertex(vertex)
- #requirement_satisfied_by?(requirement, activated, spec) ⇒ Boolean
- #search_for(dependency)
- #sort_dependencies(dependencies, activated, conflicts)
- #start(requirements)
-
#amount_constrained(dependency)
private
returns an integer in (-infty, 0] a number closer to 0 means the dependency is less constraining.
- #formatted_versions_with_platforms(versions_with_platforms) private
- #verify_gemfile_dependencies_are_found!(requirements) private
- #version_conflict_message(e) private
Molinillo::SpecificationProvider
- Included
#allow_missing? | Returns whether this dependency, which has no possible matching specifications, can safely be ignored. |
#dependencies_for | Returns the dependencies of |
#name_for | Returns the name for the given |
#name_for_explicit_dependency_source, #name_for_locking_dependency_source, | |
#requirement_satisfied_by? | Determines whether the given |
#search_for | Search for the specifications that match the given dependency. |
#sort_dependencies | Sort dependencies so that the ones that are easiest to resolve are first. |
Molinillo::UI
- Included
#after_resolution | Called after resolution ends (either successfully or with an error). |
#before_resolution | Called before resolution begins. |
#debug | Conveys debug information to the user. |
#indicate_progress | Called roughly every |
#output | The |
#progress_rate | How often progress should be conveyed to the user via #indicate_progress, in seconds. |
Constructor Details
.new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) ⇒ Resolver
# File 'lib/bundler/resolver.rb', line 26
def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) @index = index @source_requirements = source_requirements @base = base @resolver = Molinillo::Resolver.new(self, self) @search_for = {} @base_dg = Molinillo::DependencyGraph.new @base.each do |ls| dep = Dependency.new(ls.name, ls.version) @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) } @platforms = platforms @gem_version_promoter = gem_version_promoter @allow_bundler_dependency_conflicts = Bundler.feature_flag.allow_bundler_dependency_conflicts? @lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.lockfile_uses_separate_rubygems_sources? @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major? end
Class Method Details
.platform_sort_key(platform)
[ GitHub ].resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
Figures out the best possible configuration of gems that satisfies the list of passed dependencies and any child dependencies without causing any gem activation errors.
Parameters
- *dependencies<Gem::Dependency>
-
The list of dependencies to resolve
Returns
- <GemBundle>,nil
-
If the list of dependencies can be resolved, a
collection of gemspecs is returned. Otherwise, nil is returned.
# File 'lib/bundler/resolver.rb', line 18
def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) platforms = Set.new(platforms) if platforms base = SpecSet.new(base) unless base.is_a?(SpecSet) resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) result = resolver.start(requirements) SpecSet.new(result) end
.sort_platforms(platforms)
Sort platforms from most general to most specific
# File 'lib/bundler/resolver.rb', line 216
def self.sort_platforms(platforms) platforms.sort_by do |platform| platform_sort_key(platform) end end
Instance Attribute Details
#debug? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/bundler/resolver.rb', line 78
def debug? return @debug_mode if defined?(@debug_mode) @debug_mode = ENV["DEBUG_RESOLVER"] || ENV["DEBUG_RESOLVER_TREE"] || false end
Instance Method Details
#after_resolution
[ GitHub ]#amount_constrained(dependency) (private)
returns an integer in (-infty, 0] a number closer to 0 means the dependency is less constraining
dependencies w/ 0 or 1 possibilities (ignoring version requirements) are given very negative values, so they always sort first, before dependencies that are unconstrained
# File 'lib/bundler/resolver.rb', line 235
def amount_constrained(dependency) @amount_constrained ||= {} @amount_constrained[dependency.name] ||= begin if (base = @base[dependency.name]) && !base.empty? dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1 else all = index_for(dependency).search(dependency.name).size if all <= 1 all - 1_000_000 else search = search_for(dependency) search = @prerelease_specified[dependency.name] ? search.count : search.count {|s| !s.version.prerelease? } search - all end end end end
#before_resolution
[ GitHub ]
#debug(depth = 0) ⇒ void
(readonly)
This method returns an undefined value.
Conveys debug information to the user.
# File 'lib/bundler/resolver.rb', line 71
def debug(depth = 0) return unless debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) STDERR.puts debug_info.split("\n").map {|s| " " * depth + s } end
#dependencies_for(specification)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 97
def dependencies_for(specification) specification.dependencies_for_activated_platforms end
#formatted_versions_with_platforms(versions_with_platforms) (private)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 291
def formatted_versions_with_platforms(versions_with_platforms) version_platform_strs = versions_with_platforms.map do |vwp| version = vwp.first platform = vwp.last version_platform_str = String.new(version.to_s) version_platform_str << " #{platform}" unless platform.nil? || platform == Gem::Platform::RUBY version_platform_str end version_platform_strs.join(", ") end
#index_for(dependency)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 149
def index_for(dependency) source = @source_requirements[dependency.name] if source source.specs elsif @lockfile_uses_separate_rubygems_sources Index.build do |idx| if dependency.all_sources dependency.all_sources.each {|s| idx.add_source(s.specs) if s } else idx.add_source @source_requirements[:default].specs end end else @index end end
#indicate_progress
[ GitHub ]#name_for(dependency)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 166
def name_for(dependency) dependency.name end
#name_for_explicit_dependency_source
[ GitHub ]# File 'lib/bundler/resolver.rb', line 170
def name_for_explicit_dependency_source Bundler.default_gemfile.basename.to_s rescue "Gemfile" end
#name_for_locking_dependency_source
[ GitHub ]# File 'lib/bundler/resolver.rb', line 176
def name_for_locking_dependency_source Bundler.default_lockfile.basename.to_s rescue "Gemfile.lock" end
#relevant_sources_for_vertex(vertex)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 188
def relevant_sources_for_vertex(vertex) if vertex.root? [@source_requirements[vertex.name]] elsif @lockfile_uses_separate_rubygems_sources vertex.recursive_predecessors.map do |v| @source_requirements[v.name] end << @source_requirements[:default] end end
#requirement_satisfied_by?(requirement, activated, spec) ⇒ Boolean
# File 'lib/bundler/resolver.rb', line 182
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) if !@platforms || @platforms.include?(requirement.__platform) true end
#search_for(dependency)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 101
def search_for(dependency) platform = dependency.__platform dependency = dependency.dep unless dependency.is_a? Gem::Dependency search = @search_for[dependency] ||= begin index = index_for(dependency) results = index.search(dependency, @base[dependency.name]) if vertex = @base_dg.vertex_named(dependency.name) locked_requirement = vertex.payload.requirement end if !@prerelease_specified[dependency.name] && (!@use_gvp || locked_requirement.nil?) # 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) spec_group = SpecGroup.new(specs) spec_group.ignores_bundler_dependencies = @allow_bundler_dependency_conflicts groups << spec_group end 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 search.select {|sg| sg.for?(platform) }.each {|sg| sg.activate_platform!(platform) } end
#sort_dependencies(dependencies, activated, conflicts)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 198
def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by do |dependency| dependency.all_sources = relevant_sources_for_vertex(activated.vertex_named(dependency.name)) name = name_for(dependency) vertex = activated.vertex_named(name) [ @base_dg.vertex_named(name) ? 0 : 1, vertex.payload ? 0 : 1, vertex.root? ? 0 : 1, amount_constrained(dependency), conflicts[name] ? 0 : 1, vertex.payload ? 0 : search_for(dependency).count, self.class.platform_sort_key(dependency.__platform), ] end end
#start(requirements)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 45
def start(requirements) @gem_version_promoter.prerelease_specified = @prerelease_specified = {} requirements.each {|dep| @prerelease_specified[dep.name] ||= dep.prerelease? } verify_gemfile_dependencies_are_found!(requirements) dg = @resolver.resolve(requirements, @base_dg) dg.map(&:payload). reject {|sg| sg.name.end_with?("\0") }. map(&:to_specs).flatten rescue Molinillo::VersionConflict => e = (e) raise VersionConflict.new(e.conflicts.keys.uniq, ) rescue Molinillo::CircularDependencyError => e names = e.dependencies.sort_by(&:name).map {|d| "gem '#{d.name}'" } raise CyclicDependencyError, "Your bundle requires gems that depend" \ " on each other, creating an infinite loop. Please remove" \ " #{names.count > 1 ? "either " : ""}#{names.join(" or ")}" \ " and try again." end
#verify_gemfile_dependencies_are_found!(requirements) (private)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 254
def verify_gemfile_dependencies_are_found!(requirements) requirements.each do |requirement| name = requirement.name next if name == "bundler" next unless search_for(requirement).empty? = begin " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist? rescue GemfileNotFound nil end if (base = @base[name]) && !base.empty? version = base.first.version = "You have requested:\n" \ " #{name} #{requirement.requirement}\n\n" \ "The bundle currently has #{name} locked at #{version}.\n" \ "Try running `bundle update #{name}`\n\n" \ "If you are updating multiple gems in your Gemfile at once,\n" \ "try passing them all to `bundle update`" elsif source = @source_requirements[name] specs = source.specs[name] versions_with_platforms = specs.map {|s| [s.version, s.platform] } = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{}.\n") << if versions_with_platforms.any? "The source contains '#{name}' at: #{formatted_versions_with_platforms(versions_with_platforms)}" else "The source does not contain any versions of '#{name}'" end else = "Could not find gem '#{requirement}' in any of the gem sources " \ "listed in your Gemfile#{}." end raise GemNotFound, end end
#version_conflict_message(e) (private)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 302
def (e) e. ( :solver_name => "Bundler", :possibility_type => "gem", :reduce_trees => lambda do |trees| # called first, because we want to reduce the amount of work required to find maximal empty sets trees = 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| trees.map(&:last).flatten(1).combination(size).to_a 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.sort_by {|t| t.reverse.map(&:name) } end, :printable_requirement => lambda {|req| SharedHelpers.pretty_dependency(req) }, : => lambda do |o, name, conflict| if name == "bundler" o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION})) other_bundler_required = !conflict.requirement.requirement.satisfied_by?(Gem::Version.new Bundler::VERSION) end if name == "bundler" && other_bundler_required o << "\n" o << "This Gemfile requires a different version of Bundler.\n" o << "Perhaps you need to update Bundler by running `gem install bundler`?\n" end if conflict.locked_requirement o << "\n" o << %(Running `bundle update` 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_sources = if conflict.requirement.source [conflict.requirement.source] elsif conflict.requirement.all_sources conflict.requirement.all_sources elsif @lockfile_uses_separate_rubygems_sources # every conflict should have an explicit group of sources when we # enforce strict pinning raise "no source set for #{conflict}" else [] end.compact.map(&:to_s).uniq.sort o << "Could not find gem '#{SharedHelpers.pretty_dependency(conflict.requirement)}'" if conflict.requirement_trees.first.size > 1 o << ", which is required by " o << "gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}'," end o << " " o << if relevant_sources.empty? "in any of the sources.\n" else "in any of the relevant sources:\n #{relevant_sources * "\n "}\n" end end end, :version_for_spec => lambda {|spec| spec.version } ) end