/usr/lib/ruby/vendor_ruby/bundler/gem_version_promoter.rb is in ruby-bundler 1.13.6-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | # frozen_string_literal: true
module Bundler
# This class contains all of the logic for determining the next version of a
# Gem to update to based on the requested level (patch, minor, major).
# Primarily designed to work with Resolver which will provide it the list of
# available dependency versions as found in its index, before returning it to
# to the resolution engine to select the best version.
class GemVersionPromoter
attr_reader :level, :locked_specs, :unlock_gems
# By default, strict is false, meaning every available version of a gem
# is returned from sort_versions. The order gives preference to the
# requested level (:patch, :minor, :major) but in complicated requirement
# cases some gems will by necessity by promoted past the requested level,
# or even reverted to older versions.
#
# If strict is set to true, the results from sort_versions will be
# truncated, eliminating any version outside the current level scope.
# This can lead to unexpected outcomes or even VersionConflict exceptions
# that report a version of a gem not existing for versions that indeed do
# existing in the referenced source.
attr_accessor :strict
# Given a list of locked_specs and a list of gems to unlock creates a
# GemVersionPromoter instance.
#
# @param locked_specs [SpecSet] All current locked specs. Unlike Definition
# where this list is empty if all gems are being updated, this should
# always be populated for all gems so this class can properly function.
# @param unlock_gems [String] List of gem names being unlocked. If empty,
# all gems will be considered unlocked.
# @return [GemVersionPromoter]
def initialize(locked_specs = SpecSet.new([]), unlock_gems = [])
@level = :major
@strict = false
@locked_specs = locked_specs
@unlock_gems = unlock_gems
@sort_versions = {}
end
# @param value [Symbol] One of three Symbols: :major, :minor or :patch.
def level=(value)
v = case value
when String, Symbol
value.to_sym
end
raise ArgumentError, "Unexpected level #{v}. Must be :major, :minor or :patch" unless [:major, :minor, :patch].include?(v)
@level = v
end
# Given a Dependency and an Array of SpecGroups of available versions for a
# gem, this method will return the Array of SpecGroups sorted (and possibly
# truncated if strict is true) in an order to give preference to the current
# level (:major, :minor or :patch) when resolution is deciding what versions
# best resolve all dependencies in the bundle.
# @param dep [Dependency] The Dependency of the gem.
# @param spec_groups [SpecGroup] An array of SpecGroups for the same gem
# named in the @dep param.
# @return [SpecGroup] A new instance of the SpecGroup Array sorted and
# possibly filtered.
def sort_versions(dep, spec_groups)
before_result = "before sort_versions: #{debug_format_result(dep, spec_groups).inspect}" if ENV["DEBUG_RESOLVER"]
@sort_versions[dep] ||= begin
gem_name = dep.name
# An Array per version returned, different entries for different platforms.
# We only need the version here so it's ok to hard code this to the first instance.
locked_spec = locked_specs[gem_name].first
if strict
filter_dep_specs(spec_groups, locked_spec)
else
sort_dep_specs(spec_groups, locked_spec)
end.tap do |specs|
if ENV["DEBUG_RESOLVER"]
STDERR.puts before_result
STDERR.puts " after sort_versions: #{debug_format_result(dep, specs).inspect}"
end
end
end
end
# @return [bool] Convenience method for testing value of level variable.
def major?
level == :major
end
# @return [bool] Convenience method for testing value of level variable.
def minor?
level == :minor
end
private
def filter_dep_specs(spec_groups, locked_spec)
res = spec_groups.select do |spec_group|
if locked_spec && !major?
gsv = spec_group.version
lsv = locked_spec.version
must_match = minor? ? [0] : [0, 1]
matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] }
(matches.uniq == [true]) ? (gsv >= lsv) : false
else
true
end
end
sort_dep_specs(res, locked_spec)
end
def sort_dep_specs(spec_groups, locked_spec)
return spec_groups unless locked_spec
@gem_name = locked_spec.name
@locked_version = locked_spec.version
result = spec_groups.sort do |a, b|
@a_ver = a.version
@b_ver = b.version
if major?
@a_ver <=> @b_ver
elsif either_version_older_than_locked
@a_ver <=> @b_ver
elsif segments_do_not_match(:major)
@b_ver <=> @a_ver
elsif !minor? && segments_do_not_match(:minor)
@b_ver <=> @a_ver
else
@a_ver <=> @b_ver
end
end
post_sort(result)
end
def either_version_older_than_locked
@a_ver < @locked_version || @b_ver < @locked_version
end
def segments_do_not_match(level)
index = [:major, :minor].index(level)
@a_ver.segments[index] != @b_ver.segments[index]
end
def unlocking_gem?
unlock_gems.empty? || unlock_gems.include?(@gem_name)
end
# Specific version moves can't always reliably be done during sorting
# as not all elements are compared against each other.
def post_sort(result)
# default :major behavior in Bundler does not do this
return result if major?
if unlocking_gem?
result
else
move_version_to_end(result, @locked_version)
end
end
def move_version_to_end(result, version)
move, keep = result.partition {|s| s.version.to_s == version.to_s }
keep.concat(move)
end
def debug_format_result(dep, spec_groups)
a = [dep.to_s,
spec_groups.map {|sg| [sg.version, sg.dependencies_for_activated_platforms.map {|dp| [dp.name, dp.requirement.to_s] }] }]
last_map = a.last.map {|sg_data| [sg_data.first.version, sg_data.last.map {|aa| aa.join(" ") }] }
[a.first, last_map, level, strict ? :strict : :not_strict]
end
end
end
|