@@ -75,6 +75,100 @@ def _get_with_identifier(
75
75
return default
76
76
77
77
78
+ def conflicting_causes (
79
+ causes : Sequence ["PreferenceInformation" ],
80
+ ) -> Sequence ["PreferenceInformation" ]:
81
+ """Given causes return which causes conflict with each other
82
+ For each cause check one of two things:
83
+ 1. If it's specifier conflicts with another causes parent version
84
+ 2. If it's specifier conflicts with another causes specifier
85
+
86
+ Any causes which match this criteria are returned as conflicting causes
87
+ """
88
+ conflicting_ids : set [int ] = set ()
89
+
90
+ # Build a relationship between causes, cause ids, and cause parent names
91
+ causes_id_and_parents_by_name : dict [
92
+ str , list [tuple [int , Candidate ]]
93
+ ] = collections .defaultdict (list )
94
+ causes_by_id = {id (c ): c for c in causes }
95
+ for cause_id , cause in causes_by_id .items ():
96
+ if cause .parent :
97
+ causes_id_and_parents_by_name [cause .parent .name ].append (
98
+ (cause_id , cause .parent )
99
+ )
100
+
101
+ # From 1, check if each cause's specifier conflicts
102
+ # with another causes parent's version
103
+ for cause_id , cause in causes_by_id .items ():
104
+ if cause_id in conflicting_ids :
105
+ continue
106
+
107
+ cause_id_and_parents = causes_id_and_parents_by_name .get (cause .requirement .name )
108
+ if not cause_id_and_parents :
109
+ continue
110
+
111
+ conflicting_alternative_cause_ids : set [int ] = set ()
112
+ for alternative_cause_id , parent in cause_id_and_parents :
113
+ if not cause .requirement .is_satisfied_by (parent ):
114
+ conflicting_alternative_cause_ids .add (alternative_cause_id )
115
+
116
+ if conflicting_alternative_cause_ids :
117
+ conflicting_ids .add (cause_id )
118
+ conflicting_ids .update (conflicting_alternative_cause_ids )
119
+
120
+ # For comparing if two specifiers conflict first group causes
121
+ # by name, as comparing specifiers is O(n^2) so comparing the
122
+ # smaller groups is more efficent
123
+ causes_by_name : dict [str , list ["PreferenceInformation" ]] = collections .defaultdict (
124
+ list
125
+ )
126
+ for cause in causes :
127
+ causes_by_name [cause .requirement .name ].append (cause )
128
+
129
+ # From 2, check if each cause's specifier conflicts
130
+ # with another cause specifier
131
+ for causes_list in causes_by_name .values ():
132
+ if len (causes_list ) < 2 :
133
+ continue
134
+
135
+ while causes_list :
136
+ cause = causes_list .pop ()
137
+ for i , alternative_cause in enumerate (causes_list ):
138
+ candidate = cause .requirement .get_candidate_lookup ()[1 ]
139
+ if candidate is None :
140
+ continue
141
+ specifier = candidate .specifier
142
+
143
+ # Specifiers which provide no restrictions can be skipped
144
+ if len (specifier ) == 0 :
145
+ continue
146
+
147
+ alternative_candidate = (
148
+ alternative_cause .requirement .get_candidate_lookup ()[1 ]
149
+ )
150
+ if alternative_candidate is None :
151
+ continue
152
+
153
+ alternative_specifier = alternative_candidate .specifier
154
+
155
+ # Alternative specifiers which provide no
156
+ # restrictions can be skipped
157
+ if len (alternative_specifier ) == 0 :
158
+ continue
159
+
160
+ # If intersection of specifiers are empty they are
161
+ # impossibe to fill and therefore conflicting
162
+ specifier_intersection = specifier and alternative_specifier
163
+ if len (specifier_intersection ) == 0 :
164
+ conflicting_ids .add (id (cause ))
165
+ conflicting_ids .add (id (causes_list .pop (i )))
166
+
167
+ return [
168
+ cause for cause_id , cause in causes_by_id .items () if cause_id in conflicting_ids
169
+ ]
170
+
171
+
78
172
class PipProvider (_ProviderBase ):
79
173
"""Pip's provider implementation for resolvelib.
80
174
@@ -243,11 +337,18 @@ def filter_unsatisfied_names(
243
337
causes : Sequence ["PreferenceInformation" ],
244
338
) -> Iterable [str ]:
245
339
"""
246
- Prefer backtracking on unsatisfied names that are causes
340
+ Prefer backtracking on unsatisfied names that are conficting
341
+ causes, or secondly are causes
247
342
"""
248
343
if not causes :
249
344
return unsatisfied_names
250
345
346
+ # Check if backtrack causes are conflicting and prefer them
347
+ if len (causes ) > 2 :
348
+ _conflicting_causes = conflicting_causes (causes )
349
+ if len (_conflicting_causes ) > 1 :
350
+ causes = _conflicting_causes
351
+
251
352
# Extract the causes and parents names
252
353
causes_names = set ()
253
354
for cause in causes :
0 commit comments