summaryrefslogtreecommitdiff
path: root/lib/bundler/spec_set.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/spec_set.rb')
-rw-r--r--lib/bundler/spec_set.rb200
1 files changed, 154 insertions, 46 deletions
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
index 26d41cb9b8..96e1403bf7 100644
--- a/lib/bundler/spec_set.rb
+++ b/lib/bundler/spec_set.rb
@@ -1,57 +1,108 @@
# frozen_string_literal: true
-require "tsort"
+require_relative "vendored_tsort"
module Bundler
class SpecSet
include Enumerable
include TSort
- def initialize(specs)
+ attr_reader :incomplete_specs
+
+ def initialize(specs, incomplete_specs = [])
@specs = specs
+ @incomplete_specs = incomplete_specs
end
- def for(dependencies, check = false, match_current_platform = false)
- handled = []
- deps = dependencies.dup
+ def for(dependencies, check = false, platforms = [nil])
+ handled = ["bundler"].product(platforms).map {|k| [k, true] }.to_h
+ deps = dependencies.product(platforms)
specs = []
loop do
break unless dep = deps.shift
- next if handled.any?{|d| d.name == dep.name && (match_current_platform || d.__platform == dep.__platform) } || dep.name == "bundler"
- handled << dep
+ name = dep[0].name
+ platform = dep[1]
+ incomplete = false
+
+ key = [name, platform]
+ next if handled.key?(key)
- specs_for_dep = spec_for_dependency(dep, match_current_platform)
+ handled[key] = true
+
+ specs_for_dep = specs_for_dependency(*dep)
if specs_for_dep.any?
- match_current_platform ? specs += specs_for_dep : specs |= specs_for_dep
+ specs.concat(specs_for_dep)
specs_for_dep.first.dependencies.each do |d|
next if d.type == :development
- d = DepProxy.get_proxy(d, dep.__platform) unless match_current_platform
- deps << d
+ incomplete = true if d.name != "bundler" && lookup[d.name].nil?
+ deps << [d, dep[1]]
end
- elsif check
- return false
+ else
+ incomplete = true
+ end
+
+ if incomplete && check
+ @incomplete_specs += lookup[name] || [LazySpecification.new(name, nil, nil)]
end
end
- if spec = lookup["bundler"].first
- specs << spec
+ specs.uniq
+ end
+
+ def add_extra_platforms!(platforms)
+ return platforms.concat([Gem::Platform::RUBY]).uniq if @specs.empty?
+
+ new_platforms = all_platforms.select do |platform|
+ next if platforms.include?(platform)
+ next unless GemHelpers.generic(platform) == Gem::Platform::RUBY
+
+ complete_platform(platform)
+ end
+ return platforms if new_platforms.empty?
+
+ platforms.concat(new_platforms)
+
+ less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform }
+ platforms.delete(Bundler.local_platform) if less_specific_platform
+
+ platforms
+ end
+
+ def complete_platforms!(platforms)
+ platforms.each do |platform|
+ complete_platform(platform)
+ end
+ end
+
+ def validate_deps(s)
+ s.runtime_dependencies.each do |dep|
+ next if dep.name == "bundler"
+
+ return :missing unless names.include?(dep.name)
+ return :invalid if none? {|spec| dep.matches_spec?(spec) }
end
- check ? true : specs
+ :valid
end
def [](key)
key = key.name if key.respond_to?(:name)
- lookup[key].reverse
+ lookup[key]&.reverse || []
end
def []=(key, value)
@specs << value
- @lookup = nil
- @sorted = nil
+
+ reset!
+ end
+
+ def delete(specs)
+ specs.each {|spec| @specs.delete(spec) }
+
+ reset!
end
def sort!
@@ -67,14 +118,9 @@ module Bundler
end
def materialize(deps)
- materialized = self.for(deps, false, true)
+ materialized = self.for(deps, true)
- materialized.map! do |s|
- next s unless s.is_a?(LazySpecification)
- s.source.local!
- s.__materialize__ || s
- end
- SpecSet.new(materialized)
+ SpecSet.new(materialized, incomplete_specs)
end
# Materialize for all the specs in the spec set, regardless of what platform they're for
@@ -83,34 +129,43 @@ module Bundler
def materialized_for_all_platforms
@specs.map do |s|
next s unless s.is_a?(LazySpecification)
- s.source.local!
s.source.remote!
- spec = s.__materialize__
+ spec = s.materialize_for_installation
raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec
spec
end
end
+ def incomplete_for_platform?(deps, platform)
+ return false if @specs.empty?
+
+ @incomplete_specs = []
+
+ self.for(deps, true, [platform])
+
+ @incomplete_specs.any?
+ end
+
def missing_specs
@specs.select {|s| s.is_a?(LazySpecification) }
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)
+ def -(other)
+ SpecSet.new(to_a - other.to_a)
end
def find_by_name_and_platform(name, platform)
@specs.detect {|spec| spec.name == name && spec.match_platform(platform) }
end
+ def delete_by_name(name)
+ @specs.reject! {|spec| spec.name == name }
+
+ reset!
+ end
+
def what_required(spec)
- unless req = find {|s| s.dependencies.any? {|d| d.type == :runtime && d.name == spec.name } }
+ unless req = find {|s| s.runtime_dependencies.any? {|d| d.name == spec.name } }
return [spec]
end
what_required(req) << spec
@@ -136,8 +191,52 @@ module Bundler
sorted.each(&b)
end
+ def names
+ lookup.keys
+ end
+
private
+ def reset!
+ @sorted = nil
+ @lookup = nil
+ end
+
+ def complete_platform(platform)
+ new_specs = []
+
+ valid_platform = lookup.all? do |_, specs|
+ spec = specs.first
+ matching_specs = spec.source.specs.search([spec.name, spec.version])
+ platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s|
+ s.matches_current_metadata? && valid_dependencies?(s)
+ end
+
+ if platform_spec
+ new_specs << LazySpecification.from_spec(platform_spec) unless specs.include?(platform_spec)
+ true
+ else
+ false
+ end
+ end
+
+ if valid_platform && new_specs.any?
+ @specs.concat(new_specs)
+
+ reset!
+ end
+
+ valid_platform
+ end
+
+ def all_platforms
+ @specs.flat_map {|spec| spec.source.specs.search([spec.name, spec.version]).map(&:platform) }.uniq
+ end
+
+ def valid_dependencies?(s)
+ validate_deps(s) == :valid
+ end
+
def sorted
rake = @specs.find {|s| s.name == "rake" }
begin
@@ -146,7 +245,7 @@ module Bundler
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."
+ " gem '#{cgems[0]}' or gem '#{cgems[1]}' and try again."
end
end
@@ -156,8 +255,9 @@ module Bundler
def lookup
@lookup ||= begin
- lookup = Hash.new {|h, k| h[k] = [] }
- Index.sort_specs(@specs).reverse_each do |s|
+ lookup = {}
+ @specs.each do |s|
+ lookup[s.name] ||= []
lookup[s.name] << s
end
lookup
@@ -169,19 +269,27 @@ module Bundler
@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
- GemHelpers.select_best_platform_match(specs_for_platforms.select{|s| Gem::Platform.match_spec?(s) }, Bundler.local_platform)
+ def specs_for_dependency(dep, platform)
+ specs_for_name = lookup[dep.name]
+ return [] unless specs_for_name
+
+ matching_specs = if dep.force_ruby_platform
+ GemHelpers.force_ruby_platform(specs_for_name)
else
- GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform)
+ GemHelpers.select_best_platform_match(specs_for_name, platform || Bundler.local_platform)
end
+ matching_specs.map!(&:materialize_for_installation).compact! if platform.nil?
+ matching_specs
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 }
+
+ specs_for_name = lookup[d.name]
+ next unless specs_for_name
+
+ specs_for_name.each {|s2| yield s2 }
end
end
end