diff options
author | chrismo <chrismo@clabs.org> | 2016-06-15 09:34:54 -0500 |
---|---|---|
committer | chrismo <chrismo@clabs.org> | 2016-07-08 19:35:57 -0500 |
commit | a404dad1e3804fbde807a40a1c2a370e72a96c23 (patch) | |
tree | d13476160607851fffbfa1df10165a51d33461da | |
parent | c821411d5d3a843ad33aadb31c318d635a33564a (diff) | |
download | bundler-a404dad1e3804fbde807a40a1c2a370e72a96c23.tar.gz |
bundler-patch resolver code ported over.
Putting all this code into Resolver itself, when it's almost completely
isolated, didn't make sense. UpdateOptions was the best place for it,
prompting me to think that class needs renaming and see if the current
search_for method could be moved into it as well. Except for `index_for`
it's a cinch.
`install_spec` and `update_spec` are still passing with this addition,
first new spec for conservative update isn't yet.
-rw-r--r-- | lib/bundler/definition.rb | 3 | ||||
-rw-r--r-- | lib/bundler/resolver.rb | 109 |
2 files changed, 109 insertions, 3 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 8891f855f0..f5c31adaaf 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -57,7 +57,6 @@ module Bundler @lockfile_contents = String.new @locked_bundler_version = nil @locked_ruby_version = nil - @update_opts = Resolver::UpdateOptions.new if lockfile && File.exist?(lockfile) @lockfile_contents = Bundler.read_file(lockfile) @@ -84,6 +83,8 @@ module Bundler @locked_sources = [] end + @update_opts = Resolver::UpdateOptions.new(@locked_specs) + @unlock[:gems] ||= [] @unlock[:sources] ||= [] @unlock[:ruby] ||= if @ruby_version && @locked_ruby_version diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 2bfbfcc5c7..7961140134 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -268,7 +268,9 @@ module Bundler [] end end - search.select {|sg| sg.for?(platform, @ruby_version) }.each {|sg| sg.activate_platform!(platform) } + @update_opts.arrange_dep_specs(dependency, + search.select {|sg| sg.for?(platform, @ruby_version) }.each {|sg| sg.activate_platform!(platform) } + ) end def index_for(dependency) @@ -366,20 +368,123 @@ module Bundler version_platform_strs.join(", ") end + # MODO: Rename DependencySearcher class UpdateOptions attr_accessor :level, :strict, :minimal - def initialize + def initialize(locked_specs) @level_default = :major @level = @level_default @strict = false @minimal = false + @locked_specs = locked_specs end def level=(value) v = value.to_sym rescue nil @level = [:major, :minor, :patch].include?(v) ? v : @level_default end + + def arrange_dep_specs(dep, dep_specs) + return dep_specs if @level == :major + + # MODO: bring search_for in here (copy index_for one liner?) + super_result = "super search_for: #{debug_format_result(dep, dep_specs).inspect}" + + # MODO: figure out caching here plus what search_for provides + res = # @conservative_search_for[dep] ||= + begin + gem_name = dep.name + + # An Array per version returned, different entries for different platforms. + # We just need the version here so it's ok to hard code this to the first instance. + locked_spec = @locked_specs[gem_name].first + + (@strict ? + filter_dep_specs(dep_specs, locked_spec) : + sort_dep_specs(dep_specs, locked_spec)).tap do |res| + if ENV['DEBUG_PATCH_RESOLVER'] # MODO: proper debug flag check and proper debug output + STDERR.puts super_result + STDERR.puts "after search_for: #{debug_format_result(dep, res).inspect}" + end + end + end + end + + def debug_format_result(dep, res) + a = [dep.to_s, + res.map { |sg| [sg.version, sg.dependencies_for_activated_platforms.map { |dp| [dp.name, dp.requirement.to_s] }] }] + [a.first, a.last.map { |sg_data| [sg_data.first.version, sg_data.last.map { |aa| aa.join(' ') }] }] + end + + def filter_dep_specs(specs, locked_spec) + res = specs.select do |sg| + # SpecGroup is grouped by name/version, multiple entries for multiple platforms. + # We only need the name, which will be the same, so hard coding to first is ok. + gem_spec = sg.first + + if locked_spec + gsv = gem_spec.version + lsv = locked_spec.version + + must_match = @level == :minor ? [0] : [0, 1] + + matches = must_match.map { |idx| gsv.segments[idx] == lsv.segments[idx] } + (matches.uniq == [true]) ? gsv.send(:>=, lsv) : false + else + true + end + end + + sort_dep_specs(res, locked_spec) + end + + # reminder: sort still filters anything older than locked version + def sort_dep_specs(specs, locked_spec) + return specs unless locked_spec + gem_name = locked_spec.name + locked_version = locked_spec.version + + filtered = specs.select { |s| s.first.version >= locked_version } + + filtered.sort do |a, b| + a_ver = a.first.version + b_ver = b.first.version + gem_patch = @gems_to_update.gem_patch_for(gem_name) + new_version = gem_patch ? gem_patch.new_version : nil + case + when a_ver.segments[0] != b_ver.segments[0] + b_ver <=> a_ver + when !@level == :minor && (a_ver.segments[1] != b_ver.segments[1]) + b_ver <=> a_ver + when @minimal && !@gems_to_update.unlocking_gem?(gem_name) + b_ver <=> a_ver + when @minimal && @gems_to_update.unlocking_gem?(gem_name) && + (![a_ver, b_ver].include?(locked_version) && + (!new_version || (new_version && a_ver >= new_version && b_ver >= new_version))) + b_ver <=> a_ver + else + a_ver <=> b_ver + end + end.tap do |result| + if @gems_to_update.unlocking_gem?(gem_name) + gem_patch = @gems_to_update.gem_patch_for(gem_name) + if gem_patch && gem_patch.new_version && @minimal + move_version_to_end(specs, gem_patch.new_version, result) + end + else + move_version_to_end(specs, locked_version, result) + end + end + end + + def move_version_to_end(specs, version, result) + spec_group = specs.detect { |s| s.first.version.to_s == version.to_s } + if spec_group + result.reject! { |s| s.first.version.to_s === version.to_s } + result << spec_group + end + end end end end |