@@ -14,11 +14,15 @@ class Resolver
1414 require_relative "resolver/root"
1515 require_relative "resolver/strategy"
1616
17+ attr_reader :cooldown_skipped
18+
1719 def initialize ( base , gem_version_promoter , most_specific_locked_platform = nil )
1820 @source_requirements = base . source_requirements
1921 @base = base
2022 @gem_version_promoter = gem_version_promoter
2123 @most_specific_locked_platform = most_specific_locked_platform
24+ @cooldown_skipped = [ ]
25+ @cooldown_skipped_specs = { }
2226 end
2327
2428 def start
@@ -36,6 +40,8 @@ def setup_solver
3640 root = Resolver ::Root . new ( name_for_explicit_dependency_source )
3741 root_version = Resolver ::Candidate . new ( 0 )
3842
43+ @cooldown_skipped_specs = { }
44+
3945 @all_specs = Hash . new do |specs , name |
4046 source = source_for ( name )
4147 matches = source . specs . search ( name )
@@ -83,7 +89,9 @@ def solve_versions(root:, logger:)
8389 result = solver . solve
8490 resolved_specs = result . flat_map { |package , version | version . to_specs ( package , @most_specific_locked_platform ) }
8591 Override . attach ( resolved_specs , @base . overrides )
86- SpecSet . new ( resolved_specs ) . specs_with_additional_variants_from ( @base . locked_specs )
92+ spec_set = SpecSet . new ( resolved_specs ) . specs_with_additional_variants_from ( @base . locked_specs )
93+ @cooldown_skipped = cooldown_skipped_summary ( spec_set )
94+ spec_set
8795 rescue Gem ::PubGrub ::SolveFailure => e
8896 incompatibility = e . incompatibility
8997
@@ -447,6 +455,7 @@ def cooldown_excluded_versions(specs)
447455 specs . each do |spec |
448456 next unless cooldown_excluded? ( spec )
449457 excluded [ [ spec . name , spec . version ] ] = true
458+ ( @cooldown_skipped_specs ||= { } ) [ [ spec . name , spec . version ] ] ||= spec
450459 end
451460 excluded
452461 end
@@ -489,6 +498,50 @@ def cooldown_now
489498 @cooldown_now ||= Time . now
490499 end
491500
501+ # Reports, per gem, the newest version that cooldown kept out of a
502+ # successful resolution. A skipped version is only worth reporting when it
503+ # is newer than the version actually resolved and satisfies every
504+ # requirement the final resolution places on that gem, so we don't claim a
505+ # version the resolver could never have picked anyway.
506+ def cooldown_skipped_summary ( spec_set )
507+ return [ ] if @cooldown_skipped_specs . empty?
508+
509+ requirements = Hash . new { |h , name | h [ name ] = [ ] }
510+ @requirements . each { |dep | requirements [ dep . name ] << dep . requirement }
511+ spec_set . each do |spec |
512+ spec . dependencies . each { |dep | requirements [ dep . name ] << dep . requirement }
513+ end
514+
515+ resolved_versions = { }
516+ spec_set . each do |spec |
517+ version = resolved_versions [ spec . name ]
518+ resolved_versions [ spec . name ] = spec . version if version . nil? || spec . version > version
519+ end
520+
521+ newest_skipped = { }
522+ @cooldown_skipped_specs . each do |( name , version ) , spec |
523+ resolved = resolved_versions [ name ]
524+ next unless resolved && version > resolved
525+ next unless requirements [ name ] . all? { |req | req . satisfied_by? ( version ) }
526+ newest = newest_skipped [ name ]
527+ newest_skipped [ name ] = spec if newest . nil? || version > newest . version
528+ end
529+
530+ newest_skipped . values . sort_by ( &:name ) . map do |spec |
531+ {
532+ name : spec . name ,
533+ version : spec . version ,
534+ resolved : resolved_versions [ spec . name ] ,
535+ available_in_days : remaining_cooldown_days ( spec ) ,
536+ }
537+ end
538+ end
539+
540+ def remaining_cooldown_days ( spec )
541+ remaining = ( spec . remote . effective_cooldown * 86_400 ) - ( cooldown_now - spec . created_at )
542+ [ ( remaining / 86_400.0 ) . ceil , 1 ] . max
543+ end
544+
492545 def filter_remote_specs ( specs , package )
493546 if package . prefer_local?
494547 local_specs = specs . select { |s | s . is_a? ( StubSpecification ) }
0 commit comments