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 |
Constant Summary
GemHelpers
- Included
Class Method Summary
- .new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) ⇒ Resolver constructor
- .platform_sort_key(platform)
-
.resolve(requirements, 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.
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_equal?(dependencies, other_dependencies) ⇒ Boolean
- #dependencies_for(specification)
- #index_for(dependency)
- #indicate_progress
- #name_for(dependency)
- #name_for_explicit_dependency_source
- #name_for_locking_dependency_source
- #requirement_satisfied_by?(requirement, activated, spec) ⇒ Boolean
- #results_for(dependency, base)
- #search_for(dependency_proxy)
- #sort_dependencies(dependencies, activated, conflicts)
- #source_for(name)
- #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_equal? | Determines whether two arrays of dependencies are equal, and thus can be grouped. |
#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. |
GemHelpers
- Included
Constructor Details
.new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) ⇒ Resolver
# File 'lib/bundler/resolver.rb', line 27
def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) @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.get_proxy(dep, ls.platform), true) end additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) } @platforms = platforms.reject {|p| p != Gem::Platform::RUBY && (platforms - [p]).any? {|pl| generic(pl) == p } } @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
Class Method Details
.platform_sort_key(platform)
[ GitHub ].resolve(requirements, 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 20
def self.resolve(requirements, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) base = SpecSet.new(base) unless base.is_a?(SpecSet) resolver = new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) result = resolver.start(requirements) SpecSet.new(SpecSet.new(result).for(requirements.reject{|dep| dep.name.end_with?("\0") })) end
Instance Attribute Details
#debug? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/bundler/resolver.rb', line 79
def debug? return @debug_mode if defined?(@debug_mode) @debug_mode = ENV["BUNDLER_DEBUG_RESOLVER"] || ENV["BUNDLER_DEBUG_RESOLVER_TREE"] || 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 231
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 72
def debug(depth = 0) return unless debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) puts debug_info.split("\n").map {|s| depth == 0 ? "BUNDLER: #{s}" : "BUNDLER(#{depth}): #{s}" } end
#dependencies_equal?(dependencies, other_dependencies) ⇒ Boolean
# File 'lib/bundler/resolver.rb', line 197
def dependencies_equal?(dependencies, other_dependencies) dependencies.map(&:dep) == other_dependencies.map(&:dep) end
#dependencies_for(specification)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 103
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 280
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 165
def index_for(dependency) source_for(dependency.name).specs end
#indicate_progress
[ GitHub ]#name_for(dependency)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 177
def name_for(dependency) dependency.name end
#name_for_explicit_dependency_source
[ GitHub ]# File 'lib/bundler/resolver.rb', line 181
def name_for_explicit_dependency_source Bundler.default_gemfile.basename.to_s rescue StandardError "Gemfile" end
#name_for_locking_dependency_source
[ GitHub ]# File 'lib/bundler/resolver.rb', line 187
def name_for_locking_dependency_source Bundler.default_lockfile.basename.to_s rescue StandardError "Gemfile.lock" end
#requirement_satisfied_by?(requirement, activated, spec) ⇒ Boolean
#results_for(dependency, base)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 173
def results_for(dependency, base) index_for(dependency).search(dependency, base) end
#search_for(dependency_proxy)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 107
def search_for(dependency_proxy) platform = dependency_proxy.__platform dependency = dependency_proxy.dep name = dependency.name @search_for[dependency_proxy] ||= begin results = results_for(dependency, @base[name]) if vertex = @base_dg.vertex_named(name) locked_requirement = vertex.payload.requirement end if !@prerelease_specified[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) specs_by_platform = Hash.new do |current_specs, current_platform| current_specs[current_platform] = select_best_platform_match(specs, current_platform) end spec_group_ruby = SpecGroup.create_for(specs_by_platform, [Gem::Platform::RUBY], Gem::Platform::RUBY) groups << spec_group_ruby if spec_group_ruby next groups if @resolving_only_for_ruby spec_group = SpecGroup.create_for(specs_by_platform, @platforms, platform) groups << spec_group if spec_group groups 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 end
#sort_dependencies(dependencies, activated, conflicts)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 201
def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by do |dependency| 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
#source_for(name)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 169
def source_for(name) @source_requirements[name] || @source_requirements[:default] end
#start(requirements)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 44
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 250
def verify_gemfile_dependencies_are_found!(requirements) requirements.each do |requirement| name = requirement.name next if name == "bundler" next unless search_for(requirement).empty? 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`" else source = source_for(name) specs = source.specs.search(name) versions_with_platforms = specs.map {|s| [s.version, s.platform] } = begin " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist? rescue GemfileNotFound nil end = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{}.\n") << "The source contains the following versions of '#{name}': #{formatted_versions_with_platforms(versions_with_platforms)}" if versions_with_platforms.any? end raise GemNotFound, end end
#version_conflict_message(e) (private)
[ GitHub ]# File 'lib/bundler/resolver.rb', line 291
def (e) # only show essential conflicts, if possible conflicts = e.conflicts.dup if conflicts["bundler"] conflicts.replace("bundler" => conflicts["bundler"]) else 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 end e = Molinillo::VersionConflict.new(conflicts, e.specification_provider) unless conflicts.empty? solver_name = "Bundler" possibility_type = "gem" e. ( :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 = 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.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})) conflict_dependency = conflict.requirement conflict_requirement = conflict_dependency.requirement other_bundler_required = !conflict_requirement.satisfied_by?(Gem::Version.new(Bundler::VERSION)) if other_bundler_required o << "\n\n" candidate_specs = source_for(:default_bundler).specs.search(conflict_dependency) if candidate_specs.any? target_version = candidate_specs.last.version new_command = [File.basename($PROGRAM_NAME), "_#{target_version}_", *ARGV].join(" ") o << "Your bundle requires a different version of Bundler than the one you're running.\n" o << "Install the necessary version with `gem install bundler:#{target_version}` and rerun bundler using `#{new_command}`\n" else o << "Your bundle requires a different version of Bundler than the one you're running, and that version could not be found.\n" end end elsif 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_source = conflict.requirement.source || source_for(name) = name.end_with?("\0") o << "Could not find gem '" unless o << SharedHelpers.pretty_dependency(conflict.requirement) o << "'" unless 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 "is not available in #{relevant_source}" else "in #{relevant_source}.\n" end end end, :version_for_spec => lambda {|spec| spec.version }, : => 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