123456789_123456789_123456789_123456789_123456789_

Class: Bundler::GemVersionPromoter

Relationships & Source Files
Inherits: Object
Defined in: lib/bundler/gem_version_promoter.rb

Overview

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.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(locked_specs = SpecSet.new([]), unlock_gems = []) ⇒ GemVersionPromoter

Given a list of locked_specs and a list of gems to unlock creates a GemVersionPromoter instance.

Parameters:

  • locked_specs (SpecSet) (defaults to: SpecSet.new([]))

    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.

  • unlock_gems (String) (defaults to: [])

    List of gem names being unlocked. If empty, all gems will be considered unlocked.

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 38

def initialize(locked_specs = SpecSet.new([]), unlock_gems = [])
  @level = :major
  @strict = false
  @locked_specs = locked_specs
  @unlock_gems = unlock_gems
  @sort_versions = {}
  @prerelease_specified = {}
end

Instance Attribute Details

#level (rw)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 12

attr_reader :level, :locked_specs, :unlock_gems

#level=(value) (rw)

Parameters:

  • value (Symbol)

    One of three Symbols: :major, :minor or :patch.

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 48

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

#locked_specs (readonly)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 12

attr_reader :level, :locked_specs, :unlock_gems

#major?bool (readonly)

Returns:

  • (bool)

    Convenience method for testing value of level variable.

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 92

def major?
  level == :major
end

#minor?bool (readonly)

Returns:

  • (bool)

    Convenience method for testing value of level variable.

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 97

def minor?
  level == :minor
end

#prerelease_specified (rw)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 27

attr_accessor :prerelease_specified

#strict (rw)

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.

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 25

attr_accessor :strict

#unlock_gems (readonly)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 12

attr_reader :level, :locked_specs, :unlock_gems

#unlocking_gem?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 162

def unlocking_gem?
  unlock_gems.empty? || unlock_gems.include?(@gem_name)
end

Instance Method Details

#debug_format_result(dep, spec_groups) (private)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 183

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

#either_version_older_than_locked (private)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 153

def either_version_older_than_locked
  @a_ver < @locked_version || @b_ver < @locked_version
end

#filter_dep_specs(spec_groups, locked_spec) (private)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 103

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

#move_version_to_end(result, version) (private)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 178

def move_version_to_end(result, version)
  move, keep = result.partition {|s| s.version.to_s == version.to_s }
  keep.concat(move)
end

#post_sort(result) (private)

Specific version moves can’t always reliably be done during sorting as not all elements are compared against each other.

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 168

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

#segments_do_not_match(level) (private)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 157

def segments_do_not_match(level)
  index = [:major, :minor].index(level)
  @a_ver.segments[index] != @b_ver.segments[index]
end

#sort_dep_specs(spec_groups, locked_spec) (private)

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 121

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

    unless @prerelease_specified[@gem_name]
      a_pre = @a_ver.prerelease?
      b_pre = @b_ver.prerelease?

      next -1 if a_pre && !b_pre
      next  1 if b_pre && !a_pre
    end

    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

#sort_versions(dep, spec_groups) ⇒ SpecGroup

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.

Parameters:

  • dep (Dependency)

    The Dependency of the gem.

  • spec_groups (SpecGroup)

    An array of SpecGroups for the same gem named in the @dep param.

Returns:

  • (SpecGroup)

    A new instance of the SpecGroup Array sorted and possibly filtered.

[ GitHub ]

  
# File 'lib/bundler/gem_version_promoter.rb', line 68

def sort_versions(dep, spec_groups)
  before_result = "before sort_versions: #{debug_format_result(dep, spec_groups).inspect}" if DEBUG

  @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 DEBUG
        warn before_result
        warn " after sort_versions: #{debug_format_result(dep, specs).inspect}"
      end
    end
  end
end