@@ -138,24 +138,16 @@ def materialize_for_installation
138138 source . local!
139139
140140 if use_exact_resolved_specifications?
141- materialize ( self ) do |matching_specs |
142- choose_compatible ( matching_specs )
143- end
144- else
145- materialize ( [ name , version ] ) do |matching_specs |
146- target_platform = source . is_a? ( Source ::Path ) ? platform : Bundler . local_platform
147-
148- installable_candidates = MatchPlatform . select_best_platform_match ( matching_specs , target_platform )
149-
150- specification = choose_compatible ( installable_candidates , fallback_to_non_installable : false )
151- return specification unless specification . nil?
141+ spec = materialize ( self ) { |specs | choose_compatible ( specs , fallback_to_non_installable : false ) }
142+ return spec if spec
152143
153- if target_platform != platform
154- installable_candidates = MatchPlatform . select_best_platform_match ( matching_specs , platform )
155- end
156-
157- choose_compatible ( installable_candidates )
144+ # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant
145+ # In non-frozen mode, return nil to trigger re-resolution and lockfile update
146+ if Bundler . frozen_bundle?
147+ materialize ( [ name , version ] ) { |specs | resolve_best_platform ( specs ) }
158148 end
149+ else
150+ materialize ( [ name , version ] ) { |specs | resolve_best_platform ( specs ) }
159151 end
160152 end
161153
@@ -190,6 +182,39 @@ def use_exact_resolved_specifications?
190182 !source . is_a? ( Source ::Path ) && ruby_platform_materializes_to_ruby_platform?
191183 end
192184
185+ # Try platforms in order of preference until finding a compatible spec.
186+ # Used for legacy lockfiles and as a fallback when the exact locked spec
187+ # is incompatible. Falls back to frozen bundle behavior if none match.
188+ def resolve_best_platform ( specs )
189+ find_compatible_platform_spec ( specs ) || frozen_bundle_fallback ( specs )
190+ end
191+
192+ def find_compatible_platform_spec ( specs )
193+ candidate_platforms . each do |plat |
194+ candidates = MatchPlatform . select_best_platform_match ( specs , plat )
195+ spec = choose_compatible ( candidates , fallback_to_non_installable : false )
196+ return spec if spec
197+ end
198+ nil
199+ end
200+
201+ # Platforms to try in order of preference. Ruby platform is last since it
202+ # requires compilation, but works when precompiled gems are incompatible.
203+ def candidate_platforms
204+ target = source . is_a? ( Source ::Path ) ? platform : Bundler . local_platform
205+ [ target , platform , Gem ::Platform ::RUBY ] . uniq
206+ end
207+
208+ # In frozen mode, accept any candidate. Will error at install time.
209+ # When target differs from locked platform, prefer locked platform's candidates
210+ # to preserve lockfile integrity.
211+ def frozen_bundle_fallback ( specs )
212+ target = source . is_a? ( Source ::Path ) ? platform : Bundler . local_platform
213+ fallback_platform = target == platform ? target : platform
214+ candidates = MatchPlatform . select_best_platform_match ( specs , fallback_platform )
215+ choose_compatible ( candidates )
216+ end
217+
193218 def ruby_platform_materializes_to_ruby_platform?
194219 generic_platform = Bundler . generic_local_platform == Gem ::Platform ::JAVA ? Gem ::Platform ::JAVA : Gem ::Platform ::RUBY
195220
0 commit comments