class Bundler::SpecSet

Public Class Methods

new(specs) click to toggle source
# File lib/bundler/spec_set.rb, line 15
def initialize(specs)
  @specs = specs
end

Public Instance Methods

[](key) click to toggle source
# File lib/bundler/spec_set.rb, line 58
def [](key)
  key = key.name if key.respond_to?(:name)
  lookup[key].reverse
end
[]=(key, value) click to toggle source
# File lib/bundler/spec_set.rb, line 63
def []=(key, value)
  @specs << value
  @lookup = nil
  @sorted = nil
  value
end
extract_circular_gems(error) click to toggle source
# File lib/bundler/spec_set.rb, line 149
def extract_circular_gems(error)
  if Bundler.current_ruby.mri? && Bundler.current_ruby.on_19?
    error.message.scan(/(\w+) \([^)]/).flatten
  else
    error.message.scan(/@name="(.*?)"/).flatten
  end
end
find_by_name_and_platform(name, platform) click to toggle source
# File lib/bundler/spec_set.rb, line 124
def find_by_name_and_platform(name, platform)
  @specs.detect {|spec| spec.name == name && spec.match_platform(platform) }
end
for(dependencies, skip = [], check = false, match_current_platform = false, raise_on_missing = true) click to toggle source
# File lib/bundler/spec_set.rb, line 19
  def for(dependencies, skip = [], check = false, match_current_platform = false, raise_on_missing = true)
    handled = Set.new
    deps = dependencies.dup
    specs = []
    skip += ["bundler"]

    loop do
      break unless dep = deps.shift
      next if !handled.add?(dep) || skip.include?(dep.name)

      if spec = spec_for_dependency(dep, match_current_platform)
        specs << spec

        spec.dependencies.each do |d|
          next if d.type == :development
          d = DepProxy.new(d, dep.__platform) unless match_current_platform
          deps << d
        end
      elsif check
        return false
      elsif raise_on_missing
        others = lookup[dep.name] if match_current_platform
        message = "Unable to find a spec satisfying #{dep} in the set. Perhaps the lockfile is corrupted?"
        message += " Found #{others.join(", ")} that did not match the current platform." if others && !others.empty?
        raise GemNotFound, message
      end
    end

    if spec = lookup["bundler"].first
      specs << spec
    end

    check ? true : SpecSet.new(specs)
  end

  def valid_for?(deps)
    self.for(deps, [], true)
  end

  def [](key)
    key = key.name if key.respond_to?(:name)
    lookup[key].reverse
  end

  def []=(key, value)
    @specs << value
    @lookup = nil
    @sorted = nil
    value
  end

  def sort!
    self
  end

  def to_a
    sorted.dup
  end

  def to_hash
    lookup.dup
  end

  def materialize(deps, missing_specs = nil)
    materialized = self.for(deps, [], false, true, !missing_specs).to_a
    deps = materialized.map(&:name).uniq
    materialized.map! do |s|
      next s unless s.is_a?(LazySpecification)
      s.source.dependency_names = deps if s.source.respond_to?(:dependency_names=)
      spec = s.__materialize__
      unless spec
        unless missing_specs
          raise GemNotFound, "Could not find #{s.full_name} in any of the sources"
        end
        missing_specs << s
      end
      spec
    end
    SpecSet.new(missing_specs ? materialized.compact : materialized)
  end

  # Materialize for all the specs in the spec set, regardless of what platform they're for
  # This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform)
  # @return [Array<Gem::Specification>]
  def materialized_for_all_platforms
    names = @specs.map(&:name).uniq
    @specs.map do |s|
      next s unless s.is_a?(LazySpecification)
      s.source.dependency_names = names if s.source.respond_to?(:dependency_names=)
      spec = s.__materialize__
      raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec
      spec
    end
  end

  def merge(set)
    arr = sorted.dup
    set.each do |set_spec|
      full_name = set_spec.full_name
      next if arr.any? {|spec| spec.full_name == full_name }
      arr << set_spec
    end
    SpecSet.new(arr)
  end

  def find_by_name_and_platform(name, platform)
    @specs.detect {|spec| spec.name == name && spec.match_platform(platform) }
  end

  def what_required(spec)
    unless req = find {|s| s.dependencies.any? {|d| d.type == :runtime && d.name == spec.name } }
      return [spec]
    end
    what_required(req) << spec
  end

private

  def sorted
    rake = @specs.find {|s| s.name == "rake" }
    begin
      @sorted ||= ([rake] + tsort).compact.uniq
    rescue TSort::Cyclic => error
      cgems = extract_circular_gems(error)
      raise CyclicDependencyError, "Your bundle requires gems that depend" \
        " on each other, creating an infinite loop. Please remove either" \
        " gem '#{cgems[1]}' or gem '#{cgems[0]}' and try again."
    end
  end

  def extract_circular_gems(error)
    if Bundler.current_ruby.mri? && Bundler.current_ruby.on_19?
      error.message.scan(/(\w+) \([^)]/).flatten
    else
      error.message.scan(/@name="(.*?)"/).flatten
    end
  end

  def lookup
    @lookup ||= begin
      lookup = Hash.new {|h, k| h[k] = [] }
      Index.sort_specs(@specs).reverse_each do |s|
        lookup[s.name] << s
      end
      lookup
    end
  end

  def tsort_each_node
    # MUST sort by name for backwards compatibility
    @specs.sort_by(&:name).each {|s| yield s }
  end

  def spec_for_dependency(dep, match_current_platform)
    specs_for_platforms = lookup[dep.name]
    if match_current_platform
      Bundler.rubygems.platforms.reverse_each do |pl|
        match = GemHelpers.select_best_platform_match(specs_for_platforms, pl)
        return match if match
      end
      nil
    else
      GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform)
    end
  end

  def tsort_each_child(s)
    s.dependencies.sort_by(&:name).each do |d|
      next if d.type == :development
      lookup[d.name].each {|s2| yield s2 }
    end
  end
end
lookup() click to toggle source
# File lib/bundler/spec_set.rb, line 157
def lookup
  @lookup ||= begin
    lookup = Hash.new {|h, k| h[k] = [] }
    Index.sort_specs(@specs).reverse_each do |s|
      lookup[s.name] << s
    end
    lookup
  end
end
materialize(deps, missing_specs = nil) click to toggle source
# File lib/bundler/spec_set.rb, line 82
def materialize(deps, missing_specs = nil)
  materialized = self.for(deps, [], false, true, !missing_specs).to_a
  deps = materialized.map(&:name).uniq
  materialized.map! do |s|
    next s unless s.is_a?(LazySpecification)
    s.source.dependency_names = deps if s.source.respond_to?(:dependency_names=)
    spec = s.__materialize__
    unless spec
      unless missing_specs
        raise GemNotFound, "Could not find #{s.full_name} in any of the sources"
      end
      missing_specs << s
    end
    spec
  end
  SpecSet.new(missing_specs ? materialized.compact : materialized)
end
materialized_for_all_platforms() click to toggle source

Materialize for all the specs in the spec set, regardless of what platform they're for This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform) @return [Array<Gem::Specification>]

# File lib/bundler/spec_set.rb, line 103
def materialized_for_all_platforms
  names = @specs.map(&:name).uniq
  @specs.map do |s|
    next s unless s.is_a?(LazySpecification)
    s.source.dependency_names = names if s.source.respond_to?(:dependency_names=)
    spec = s.__materialize__
    raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec
    spec
  end
end
merge(set) click to toggle source
# File lib/bundler/spec_set.rb, line 114
def merge(set)
  arr = sorted.dup
  set.each do |set_spec|
    full_name = set_spec.full_name
    next if arr.any? {|spec| spec.full_name == full_name }
    arr << set_spec
  end
  SpecSet.new(arr)
end
sort!() click to toggle source
# File lib/bundler/spec_set.rb, line 70
def sort!
  self
end
sorted() click to toggle source
# File lib/bundler/spec_set.rb, line 137
def sorted
  rake = @specs.find {|s| s.name == "rake" }
  begin
    @sorted ||= ([rake] + tsort).compact.uniq
  rescue TSort::Cyclic => error
    cgems = extract_circular_gems(error)
    raise CyclicDependencyError, "Your bundle requires gems that depend" \
      " on each other, creating an infinite loop. Please remove either" \
      " gem '#{cgems[1]}' or gem '#{cgems[0]}' and try again."
  end
end
spec_for_dependency(dep, match_current_platform) click to toggle source
# File lib/bundler/spec_set.rb, line 172
def spec_for_dependency(dep, match_current_platform)
  specs_for_platforms = lookup[dep.name]
  if match_current_platform
    Bundler.rubygems.platforms.reverse_each do |pl|
      match = GemHelpers.select_best_platform_match(specs_for_platforms, pl)
      return match if match
    end
    nil
  else
    GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform)
  end
end
to_a() click to toggle source
# File lib/bundler/spec_set.rb, line 74
def to_a
  sorted.dup
end
to_hash() click to toggle source
# File lib/bundler/spec_set.rb, line 78
def to_hash
  lookup.dup
end
tsort_each_child(s) { |s2| ... } click to toggle source
# File lib/bundler/spec_set.rb, line 185
def tsort_each_child(s)
  s.dependencies.sort_by(&:name).each do |d|
    next if d.type == :development
    lookup[d.name].each {|s2| yield s2 }
  end
end
tsort_each_node() { |s| ... } click to toggle source
# File lib/bundler/spec_set.rb, line 167
def tsort_each_node
  # MUST sort by name for backwards compatibility
  @specs.sort_by(&:name).each {|s| yield s }
end
valid_for?(deps) click to toggle source
# File lib/bundler/spec_set.rb, line 54
def valid_for?(deps)
  self.for(deps, [], true)
end
what_required(spec) click to toggle source
# File lib/bundler/spec_set.rb, line 128
def what_required(spec)
  unless req = find {|s| s.dependencies.any? {|d| d.type == :runtime && d.name == spec.name } }
    return [spec]
  end
  what_required(req) << spec
end