This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "package"
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
class Base
|
||||
attr_reader :packages, :requirements, :source_requirements
|
||||
|
||||
def initialize(source_requirements, dependencies, base, platforms, options)
|
||||
@source_requirements = source_requirements
|
||||
|
||||
@base = base
|
||||
|
||||
@packages = Hash.new do |hash, name|
|
||||
hash[name] = Package.new(name, platforms, **options)
|
||||
end
|
||||
|
||||
@requirements = dependencies.map do |dep|
|
||||
dep_platforms = dep.gem_platforms(platforms)
|
||||
|
||||
# Dependencies scoped to external platforms are ignored
|
||||
next if dep_platforms.empty?
|
||||
|
||||
name = dep.name
|
||||
|
||||
@packages[name] = Package.new(name, dep_platforms, **options.merge(dependency: dep))
|
||||
|
||||
dep
|
||||
end.compact
|
||||
end
|
||||
|
||||
def [](name)
|
||||
@base[name]
|
||||
end
|
||||
|
||||
def delete(specs)
|
||||
@base.delete(specs)
|
||||
end
|
||||
|
||||
def get_package(name)
|
||||
@packages[name]
|
||||
end
|
||||
|
||||
def base_requirements
|
||||
@base_requirements ||= build_base_requirements
|
||||
end
|
||||
|
||||
def unlock_names(names)
|
||||
indirect_pins = indirect_pins(names)
|
||||
|
||||
if indirect_pins.any?
|
||||
loosen_names(indirect_pins)
|
||||
else
|
||||
pins = pins(names)
|
||||
|
||||
if pins.any?
|
||||
loosen_names(pins)
|
||||
else
|
||||
unrestrict_names(names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def include_prereleases(names)
|
||||
names.each do |name|
|
||||
get_package(name).consider_prereleases!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def indirect_pins(names)
|
||||
names.select {|name| @base_requirements[name].exact? && @requirements.none? {|dep| dep.name == name } }
|
||||
end
|
||||
|
||||
def pins(names)
|
||||
names.select {|name| @base_requirements[name].exact? }
|
||||
end
|
||||
|
||||
def loosen_names(names)
|
||||
names.each do |name|
|
||||
version = @base_requirements[name].requirements.first[1]
|
||||
|
||||
@base_requirements[name] = Gem::Requirement.new(">= #{version}")
|
||||
|
||||
@base.delete_by_name(name)
|
||||
end
|
||||
end
|
||||
|
||||
def unrestrict_names(names)
|
||||
names.each do |name|
|
||||
@base_requirements.delete(name)
|
||||
end
|
||||
end
|
||||
|
||||
def build_base_requirements
|
||||
base_requirements = {}
|
||||
@base.each do |ls|
|
||||
req = Gem::Requirement.new(ls.version)
|
||||
base_requirements[ls.name] = req
|
||||
end
|
||||
base_requirements
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "spec_group"
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
#
|
||||
# This class is a PubGrub compatible "Version" class that takes Bundler
|
||||
# resolution complexities into account.
|
||||
#
|
||||
# Each Resolver::Candidate has a underlying `Gem::Version` plus a set of
|
||||
# platforms. For example, 1.1.0-x86_64-linux is a different resolution candidate
|
||||
# from 1.1.0 (generic). This is because different platform variants of the
|
||||
# same gem version can bring different dependencies, so they need to be
|
||||
# considered separately.
|
||||
#
|
||||
# Some candidates may also keep some information explicitly about the
|
||||
# package they refer to. These candidates are referred to as "canonical" and
|
||||
# are used when materializing resolution results back into RubyGems
|
||||
# specifications that can be installed, written to lock files, and so on.
|
||||
#
|
||||
class Candidate
|
||||
include Comparable
|
||||
|
||||
attr_reader :version
|
||||
|
||||
def initialize(version, specs: [])
|
||||
@spec_group = Resolver::SpecGroup.new(specs)
|
||||
@version = Gem::Version.new(version)
|
||||
@ruby_only = specs.map(&:platform).uniq == [Gem::Platform::RUBY]
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@spec_group.dependencies
|
||||
end
|
||||
|
||||
def to_specs(package)
|
||||
return [] if package.meta?
|
||||
|
||||
@spec_group.to_specs(package.force_ruby_platform?)
|
||||
end
|
||||
|
||||
def generic!
|
||||
@ruby_only = true
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def platform_specific!
|
||||
@ruby_only = false
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def prerelease?
|
||||
@version.prerelease?
|
||||
end
|
||||
|
||||
def segments
|
||||
@version.segments
|
||||
end
|
||||
|
||||
def sort_obj
|
||||
[@version, @ruby_only ? -1 : 1]
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
return unless other.is_a?(self.class)
|
||||
|
||||
sort_obj <=> other.sort_obj
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
return unless other.is_a?(self.class)
|
||||
|
||||
sort_obj == other.sort_obj
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
return unless other.is_a?(self.class)
|
||||
|
||||
sort_obj.eql?(other.sort_obj)
|
||||
end
|
||||
|
||||
def hash
|
||||
sort_obj.hash
|
||||
end
|
||||
|
||||
def to_s
|
||||
@version.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
class Incompatibility < PubGrub::Incompatibility
|
||||
attr_reader :extended_explanation
|
||||
|
||||
def initialize(terms, cause:, custom_explanation: nil, extended_explanation: nil)
|
||||
@extended_explanation = extended_explanation
|
||||
|
||||
super(terms, cause: cause, custom_explanation: custom_explanation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
#
|
||||
# Represents a gem being resolved, in a format PubGrub likes.
|
||||
#
|
||||
# The class holds the following information:
|
||||
#
|
||||
# * Platforms this gem will be resolved on.
|
||||
# * The locked version of this gem resolution should favor (if any).
|
||||
# * Whether the gem should be unlocked to its latest version.
|
||||
# * The dependency explicit set in the Gemfile for this gem (if any).
|
||||
#
|
||||
class Package
|
||||
attr_reader :name, :platforms, :dependency, :locked_version
|
||||
|
||||
def initialize(name, platforms, locked_specs:, unlock:, prerelease: false, dependency: nil)
|
||||
@name = name
|
||||
@platforms = platforms
|
||||
@locked_version = locked_specs[name].first&.version
|
||||
@unlock = unlock
|
||||
@dependency = dependency || Dependency.new(name, @locked_version)
|
||||
@top_level = !dependency.nil?
|
||||
@prerelease = @dependency.prerelease? || @locked_version&.prerelease? || prerelease ? :consider_first : :ignore
|
||||
end
|
||||
|
||||
def to_s
|
||||
@name.delete("\0")
|
||||
end
|
||||
|
||||
def root?
|
||||
false
|
||||
end
|
||||
|
||||
def top_level?
|
||||
@top_level
|
||||
end
|
||||
|
||||
def meta?
|
||||
@name.end_with?("\0")
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class == other.class && @name == other.name
|
||||
end
|
||||
|
||||
def hash
|
||||
@name.hash
|
||||
end
|
||||
|
||||
def unlock?
|
||||
@unlock.empty? || @unlock.include?(name)
|
||||
end
|
||||
|
||||
def ignores_prereleases?
|
||||
@prerelease == :ignore
|
||||
end
|
||||
|
||||
def prerelease_specified?
|
||||
@prerelease == :consider_first
|
||||
end
|
||||
|
||||
def consider_prereleases!
|
||||
@prerelease = :consider_last
|
||||
end
|
||||
|
||||
def force_ruby_platform?
|
||||
@dependency.force_ruby_platform
|
||||
end
|
||||
|
||||
def current_platform?
|
||||
@dependency.current_platform?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "package"
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
#
|
||||
# Represents the Gemfile from the resolver's perspective. It's the root
|
||||
# package and Gemfile entries depend on it.
|
||||
#
|
||||
class Root < Package
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def meta?
|
||||
true
|
||||
end
|
||||
|
||||
def root?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class Resolver
|
||||
class SpecGroup
|
||||
def initialize(specs)
|
||||
@specs = specs
|
||||
end
|
||||
|
||||
def empty?
|
||||
@specs.empty?
|
||||
end
|
||||
|
||||
def name
|
||||
@name ||= exemplary_spec.name
|
||||
end
|
||||
|
||||
def version
|
||||
@version ||= exemplary_spec.version
|
||||
end
|
||||
|
||||
def source
|
||||
@source ||= exemplary_spec.source
|
||||
end
|
||||
|
||||
def to_specs(force_ruby_platform)
|
||||
@specs.map do |s|
|
||||
lazy_spec = LazySpecification.from_spec(s)
|
||||
lazy_spec.force_ruby_platform = force_ruby_platform
|
||||
lazy_spec
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
sorted_spec_names.join(", ")
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= @specs.map do |spec|
|
||||
__dependencies(spec) + metadata_dependencies(spec)
|
||||
end.flatten.uniq
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def sorted_spec_names
|
||||
@sorted_spec_names ||= @specs.map(&:full_name).sort
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def exemplary_spec
|
||||
@specs.first
|
||||
end
|
||||
|
||||
def __dependencies(spec)
|
||||
dependencies = []
|
||||
spec.dependencies.each do |dep|
|
||||
next if dep.type == :development
|
||||
dependencies << Dependency.new(dep.name, dep.requirement)
|
||||
end
|
||||
dependencies
|
||||
end
|
||||
|
||||
def metadata_dependencies(spec)
|
||||
[
|
||||
metadata_dependency("Ruby", spec.required_ruby_version),
|
||||
metadata_dependency("RubyGems", spec.required_rubygems_version),
|
||||
].compact
|
||||
end
|
||||
|
||||
def metadata_dependency(name, requirement)
|
||||
return if requirement.nil? || requirement.none?
|
||||
|
||||
Dependency.new("#{name}\0", requirement)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user