diff options
author | David RodrÃguez <deivid.rodriguez@riseup.net> | 2021-02-01 16:17:16 +0100 |
---|---|---|
committer | Hiroshi SHIBATA <hsbt@ruby-lang.org> | 2021-03-08 13:47:35 +0900 |
commit | 53468cc11147b0d285fc376fc546b677dad600ca (patch) | |
tree | eb9c97f544d089be2d324126b025b11f41a22c90 /lib/bundler/resolver.rb | |
parent | 2ab6b7a7516e1b2c48a66ce513afabb62d101461 (diff) | |
download | ruby-53468cc11147b0d285fc376fc546b677dad600ca.tar.gz |
Sync latest development version of bundler & rubygems
Diffstat (limited to 'lib/bundler/resolver.rb')
-rw-r--r-- | lib/bundler/resolver.rb | 195 |
1 files changed, 116 insertions, 79 deletions
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 636dc8af46..69899dc6c3 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -5,6 +5,8 @@ module Bundler require_relative "vendored_molinillo" require_relative "resolver/spec_group" + include GemHelpers + # 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. @@ -15,31 +17,38 @@ 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 = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) - platforms = Set.new(platforms) if platforms + 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(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) + resolver = new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) result = resolver.start(requirements) SpecSet.new(result) end - def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) - @index = index + def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) @source_requirements = source_requirements + + @index_requirements = source_requirements.each_with_object({}) do |source_requirement, index_requirements| + name, source = source_requirement + index_requirements[name] = name == :global ? source : source.specs + end + @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) + @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 + @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 - @allow_bundler_dependency_conflicts = Bundler.feature_flag.allow_bundler_dependency_conflicts? @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major? - @lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.disable_multisource? + @no_aggregate_global_source = @source_requirements[:global].nil? + + @variant_specific_names = [] + @generic_names = ["Ruby\0", "RubyGems\0"] end def start(requirements) @@ -75,7 +84,7 @@ module Bundler return unless debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) - puts debug_info.split("\n").map {|s| "BUNDLER: " + " " * depth + s } + puts debug_info.split("\n").map {|s| depth == 0 ? "BUNDLER: #{s}" : "BUNDLER(#{depth}): #{s}" } end def debug? @@ -103,16 +112,25 @@ module Bundler include Molinillo::SpecificationProvider def dependencies_for(specification) - specification.dependencies_for_activated_platforms + all_dependencies = specification.dependencies_for_activated_platforms + + if @variant_specific_names.include?(specification.name) + @variant_specific_names |= all_dependencies.map(&:name) - @generic_names + else + generic_names, variant_specific_names = specification.partitioned_dependency_names_for_activated_platforms + @variant_specific_names |= variant_specific_names - @generic_names + @generic_names |= generic_names + end + + all_dependencies end def search_for(dependency_proxy) platform = dependency_proxy.__platform dependency = dependency_proxy.dep - @search_for[dependency_proxy] ||= begin - name = dependency.name - index = index_for(dependency) - results = index.search(dependency, @base[name]) + name = dependency.name + search_result = @search_for[dependency_proxy] ||= begin + results = results_for(dependency, @base[name]) if vertex = @base_dg.vertex_named(name) locked_requirement = vertex.payload.requirement @@ -137,55 +155,67 @@ module Bundler 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 + + 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. - search = if !@use_gvp + if !@use_gvp spec_groups else @gem_version_promoter.sort_versions(dependency, spec_groups) end - selected_sgs = [] - search.each do |sg| - next unless sg.for?(platform) - sg_all_platforms = sg.copy_for(self.class.sort_platforms(@platforms).reverse) - next unless sg_all_platforms - - selected_sgs << sg_all_platforms - - next if sg_all_platforms.activated_platforms == [Gem::Platform::RUBY] - # Add a spec group for "non platform specific spec" as the fallback - # spec group. - sg_ruby = sg.copy_for([Gem::Platform::RUBY]) - selected_sgs.insert(-2, sg_ruby) if sg_ruby + end + + unless search_result.empty? + specific_dependency = @variant_specific_names.include?(name) + return search_result unless specific_dependency + + search_result.each do |sg| + if @generic_names.include?(name) + @variant_specific_names -= [name] + sg.activate_all_platforms! + else + sg.activate_platform!(platform) + end end - selected_sgs end + + search_result end def index_for(dependency) - source = @source_requirements[dependency.name] + source = @index_requirements[dependency.name] if source - source.specs - elsif @lockfile_uses_separate_rubygems_sources + source + elsif @no_aggregate_global_source 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 + dependency.all_sources.each {|s| idx.add_source(s.specs) } end else - @index + @index_requirements[:global] end end + def results_for(dependency, base) + index_for(dependency).search(dependency, base) + end + def name_for(dependency) dependency.name end @@ -206,21 +236,27 @@ module Bundler requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) end + def dependencies_equal?(dependencies, other_dependencies) + dependencies.map(&:dep) == other_dependencies.map(&:dep) + end + def relevant_sources_for_vertex(vertex) if vertex.root? [@source_requirements[vertex.name]] - elsif @lockfile_uses_separate_rubygems_sources + elsif @no_aggregate_global_source vertex.recursive_predecessors.map do |v| @source_requirements[v.name] - end << @source_requirements[:default] + end.compact << @source_requirements[:default] + else + [] end end 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) + dependency.all_sources = relevant_sources_for_vertex(vertex) [ @base_dg.vertex_named(name) ? 0 : 1, vertex.payload ? 0 : 1, @@ -233,13 +269,6 @@ module Bundler end end - # Sort platforms from most general to most specific - def self.sort_platforms(platforms) - platforms.sort_by do |platform| - platform_sort_key(platform) - end - end - def self.platform_sort_key(platform) # Prefer specific platform to not specific platform return ["99-LAST", "", "", ""] if Gem::Platform::RUBY == platform @@ -294,7 +323,7 @@ module Bundler "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] + specs = source.specs.search(name) versions_with_platforms = specs.map {|s| [s.version, s.platform] } message = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{cache_message}.\n") message << if versions_with_platforms.any? @@ -303,7 +332,7 @@ module Bundler "The source does not contain any versions of '#{name}'" end else - message = "Could not find gem '#{requirement}' in any of the gem sources " \ + message = "Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in any of the gem sources " \ "listed in your Gemfile#{cache_message}." end raise GemNotFound, message @@ -324,10 +353,16 @@ module Bundler 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))) + + 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" @@ -355,15 +390,25 @@ module Bundler :additional_message_for_conflict => 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 + 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 = @index_requirements[:default_bundler].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) @@ -372,14 +417,8 @@ module Bundler 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 - [] + conflict.requirement.all_sources end.compact.map(&:to_s).uniq.sort metadata_requirement = name.end_with?("\0") @@ -416,23 +455,21 @@ module Bundler def validate_resolved_specs!(resolved_specs) resolved_specs.each do |v| name = v.name - next unless sources = relevant_sources_for_vertex(v) - sources.compact! + sources = relevant_sources_for_vertex(v) + next unless sources.any? if default_index = sources.index(@source_requirements[:default]) sources.delete_at(default_index) end - sources.reject! {|s| s.specs[name].empty? } + sources.reject! {|s| s.specs.search(name).empty? } sources.uniq! next if sources.size <= 1 - multisource_disabled = Bundler.feature_flag.disable_multisource? - msg = ["The gem '#{name}' was found in multiple relevant sources."] msg.concat sources.map {|s| " * #{s}" }.sort - msg << "You #{multisource_disabled ? :must : :should} add this gem to the source block for the source you wish it to be installed from." + msg << "You #{@no_aggregate_global_source ? :must : :should} add this gem to the source block for the source you wish it to be installed from." msg = msg.join("\n") - raise SecurityError, msg if multisource_disabled + raise SecurityError, msg if @no_aggregate_global_source Bundler.ui.warn "Warning: #{msg}" end end |