aboutsummaryrefslogtreecommitdiffstats
path: root/lib/bundler
diff options
context:
space:
mode:
authorchrismo <chrismo@clabs.org>2016-06-15 09:34:54 -0500
committerchrismo <chrismo@clabs.org>2016-07-08 19:35:57 -0500
commita404dad1e3804fbde807a40a1c2a370e72a96c23 (patch)
treed13476160607851fffbfa1df10165a51d33461da /lib/bundler
parentc821411d5d3a843ad33aadb31c318d635a33564a (diff)
downloadbundler-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.
Diffstat (limited to 'lib/bundler')
-rw-r--r--lib/bundler/definition.rb3
-rw-r--r--lib/bundler/resolver.rb109
2 files changed, 109 insertions, 3 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 8891f855..f5c31ada 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 2bfbfcc5..79611401 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