[rubygems/rubygems] Keep track of materializations in the original resolve

This gives more flexibility to allow further improvements.

https://github.com/rubygems/rubygems/commit/f11a890f5e
This commit is contained in:
David Rodríguez 2024-11-16 00:20:09 +01:00 committed by Hiroshi SHIBATA
parent e15921c694
commit c76b1ea2a6
6 changed files with 124 additions and 56 deletions

View File

@ -62,6 +62,7 @@ module Bundler
autoload :LazySpecification, File.expand_path("bundler/lazy_specification", __dir__)
autoload :LockfileParser, File.expand_path("bundler/lockfile_parser", __dir__)
autoload :MatchRemoteMetadata, File.expand_path("bundler/match_remote_metadata", __dir__)
autoload :Materialization, File.expand_path("bundler/materialization", __dir__)
autoload :NULL, File.expand_path("bundler/constants", __dir__)
autoload :ProcessLock, File.expand_path("bundler/process_lock", __dir__)
autoload :RemoteSpecification, File.expand_path("bundler/remote_specification", __dir__)

View File

@ -241,7 +241,7 @@ module Bundler
end
def missing_specs
resolve.materialize(requested_dependencies).missing_specs
resolve.missing_specs_for(requested_dependencies)
end
def missing_specs?
@ -639,7 +639,7 @@ module Bundler
retry
end
missing_specs = specs.missing_specs
missing_specs = resolve.missing_specs
if missing_specs.any?
missing_specs.each do |s|
@ -668,7 +668,7 @@ module Bundler
raise GemNotFound, "Could not find #{missing_specs_list.join(" nor ")}"
end
incomplete_specs = specs.incomplete_specs
incomplete_specs = resolve.incomplete_specs
loop do
break if incomplete_specs.empty?
@ -677,7 +677,7 @@ module Bundler
reresolve_without(incomplete_specs)
specs = resolve.materialize(dependencies)
still_incomplete_specs = specs.incomplete_specs
still_incomplete_specs = resolve.incomplete_specs
if still_incomplete_specs == incomplete_specs
package = resolution_packages.get_package(incomplete_specs.first.name)
@ -687,7 +687,7 @@ module Bundler
incomplete_specs = still_incomplete_specs
end
insecurely_materialized_specs = specs.insecurely_materialized_specs
insecurely_materialized_specs = resolve.insecurely_materialized_specs
if insecurely_materialized_specs.any?
Bundler.ui.warn "The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version:\n" \

View File

@ -63,7 +63,7 @@ module Bundler
module_function :select_best_platform_match
def select_best_local_platform_match(specs, force_ruby: false)
select_best_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialize_for_installation)
select_best_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialized_for_installation)
end
module_function :select_best_local_platform_match

View File

@ -8,7 +8,7 @@ module Bundler
include MatchPlatform
include ForcePlatform
attr_reader :name, :version, :platform
attr_reader :name, :version, :platform, :materialization
attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version
#
@ -46,6 +46,15 @@ module Bundler
@force_ruby_platform = default_force_ruby_platform
@most_specific_locked_platform = nil
@materialization = nil
end
def missing?
@materialization == self
end
def incomplete?
@materialization.nil?
end
def source_changed?
@ -121,6 +130,12 @@ module Bundler
__materialize__(matching_specs)
end
def materialized_for_installation
@materialization = materialize_for_installation
self unless incomplete?
end
def materialize_for_installation
source.local!

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
module Bundler
#
# This class materializes a set of resolved specifications (`LazySpecification`)
# for a given gem into the most appropriate real specifications
# (`StubSepecification`, `EndpointSpecification`, etc), given a dependency and a
# target platform.
#
class Materialization
def initialize(dep, platform, candidates:)
@dep = dep
@platform = platform
@candidates = candidates
end
def complete?
specs.any?
end
def specs
@specs ||= if @candidates.nil?
[]
elsif platform
GemHelpers.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform)
else
GemHelpers.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform)
end
end
def dependencies
specs.first.runtime_dependencies.map {|d| [d, platform] }
end
def materialized_spec
specs.first&.materialization
end
def missing_specs
specs.select(&:missing?)
end
def incomplete_specs
return [] if complete?
@candidates || LazySpecification.new(dep.name, nil, nil)
end
private
attr_reader :dep, :platform
end
end

View File

@ -7,44 +7,14 @@ module Bundler
include Enumerable
include TSort
attr_reader :incomplete_specs
def initialize(specs, incomplete_specs = [])
def initialize(specs)
@specs = specs
@incomplete_specs = incomplete_specs
end
def for(dependencies, check = false, platforms = [nil])
handled = ["bundler"].product(platforms).map {|k| [k, true] }.to_h
deps = dependencies.product(platforms)
specs = []
materialize_dependencies(dependencies, platforms)
loop do
break unless dep = deps.shift
name = dep[0].name
platform = dep[1]
incomplete = false
key = [name, platform]
next if handled.key?(key)
handled[key] = true
specs_for_dep = specs_for_dependency(*dep)
if specs_for_dep.any?
specs.concat(specs_for_dep)
deps.concat(specs_for_dep.first.runtime_dependencies.map {|d| [d, platform] })
else
incomplete = true
end
if incomplete && check
@incomplete_specs += lookup[name] || [LazySpecification.new(name, nil, nil)]
end
end
specs.uniq
@materializations.flat_map(&:specs).uniq
end
def normalize_platforms!(deps, platforms)
@ -126,13 +96,12 @@ module Bundler
end
def materialize(deps)
materialized = self.for(deps, true)
materialize_dependencies(deps)
SpecSet.new(materialized, incomplete_specs)
SpecSet.new(materialized_specs)
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
@specs.map do |s|
@ -153,12 +122,22 @@ module Bundler
validation_set.incomplete_specs.any?
end
def missing_specs_for(dependencies)
materialize_dependencies(dependencies)
missing_specs
end
def missing_specs
@specs.select {|s| s.is_a?(LazySpecification) }
@materializations.flat_map(&:missing_specs)
end
def incomplete_specs
@materializations.flat_map(&:incomplete_specs)
end
def insecurely_materialized_specs
@specs.select(&:insecurely_materialized?)
materialized_specs.select(&:insecurely_materialized?)
end
def -(other)
@ -218,6 +197,37 @@ module Bundler
private
def materialize_dependencies(dependencies, platforms = [nil])
handled = ["bundler"].product(platforms).map {|k| [k, true] }.to_h
deps = dependencies.product(platforms)
@materializations = []
loop do
break unless dep = deps.shift
dependency = dep[0]
platform = dep[1]
name = dependency.name
key = [name, platform]
next if handled.key?(key)
handled[key] = true
materialization = Materialization.new(dependency, platform, candidates: lookup[name])
deps.concat(materialization.dependencies) if materialization.complete?
@materializations << materialization
end
@materializations
end
def materialized_specs
@materializations.filter_map(&:materialized_spec)
end
def reset!
@sorted = nil
@lookup = nil
@ -290,17 +300,6 @@ module Bundler
@specs.sort_by(&:name).each {|s| yield s }
end
def specs_for_dependency(dep, platform)
specs_for_name = lookup[dep.name]
return [] unless specs_for_name
if platform
GemHelpers.select_best_platform_match(specs_for_name, platform, force_ruby: dep.force_ruby_platform)
else
GemHelpers.select_best_local_platform_match(specs_for_name, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform)
end
end
def tsort_each_child(s)
s.dependencies.sort_by(&:name).each do |d|
next if d.type == :development