6
6
using System ;
7
7
using System . Collections . Generic ;
8
8
using System . Globalization ;
9
+ using System . Linq ;
9
10
10
11
namespace Microsoft . NET . Build . Tasks . ConflictResolution
11
12
{
13
+ internal delegate void ConflictCallback < T > ( T winner , T loser ) ;
14
+
12
15
// The conflict resolver finds conflicting items, and if there are any of them it reports the "losing" item via the foundConflict callback
13
- internal class ConflictResolver < TConflictItem > where TConflictItem : class , IConflictItem
16
+ internal class ConflictResolver < TConflictItem > : IDisposable where TConflictItem : class , IConflictItem
14
17
{
15
- private Dictionary < string , TConflictItem > winningItemsByKey = new Dictionary < string , TConflictItem > ( ) ;
16
- private ILog log ;
17
- private PackageRank packageRank ;
18
- private PackageOverrideResolver < TConflictItem > packageOverrideResolver ;
18
+ private Dictionary < string , TConflictItem > _winningItemsByKey = new Dictionary < string , TConflictItem > ( ) ;
19
+ private ILog _log ;
20
+ private PackageRank _packageRank ;
21
+ private PackageOverrideResolver < TConflictItem > _packageOverrideResolver ;
22
+ private Dictionary < string , List < TConflictItem > > _unresolvedConflictItems = new Dictionary < string , List < TConflictItem > > ( StringComparer . Ordinal ) ;
23
+
24
+ // Callback for unresolved conflicts, currently just used as a test hook
25
+ public Action < TConflictItem > UnresolvedConflictHandler { get ; set ; }
19
26
20
27
public ConflictResolver ( PackageRank packageRank , PackageOverrideResolver < TConflictItem > packageOverrideResolver , ILog log )
21
28
{
22
- this . log = log ;
23
- this . packageRank = packageRank ;
24
- this . packageOverrideResolver = packageOverrideResolver ;
29
+ this . _log = log ;
30
+ this . _packageRank = packageRank ;
31
+ this . _packageOverrideResolver = packageOverrideResolver ;
25
32
}
26
33
27
34
public void ResolveConflicts ( IEnumerable < TConflictItem > conflictItems , Func < TConflictItem , string > getItemKey ,
28
- Action < TConflictItem > foundConflict , bool commitWinner = true ,
29
- Action < TConflictItem > unresolvedConflict = null )
35
+ ConflictCallback < TConflictItem > foundConflict , bool commitWinner = true )
30
36
{
31
37
if ( conflictItems == null )
32
38
{
@@ -44,18 +50,31 @@ public void ResolveConflicts(IEnumerable<TConflictItem> conflictItems, Func<TCon
44
50
45
51
TConflictItem existingItem ;
46
52
47
- if ( winningItemsByKey . TryGetValue ( itemKey , out existingItem ) )
53
+ if ( _winningItemsByKey . TryGetValue ( itemKey , out existingItem ) )
48
54
{
49
55
// a conflict was found, determine the winner.
50
- var winner = ResolveConflict ( existingItem , conflictItem ) ;
56
+ var winner = ResolveConflict ( existingItem , conflictItem , logUnresolvedConflicts : false ) ;
51
57
52
58
if ( winner == null )
53
59
{
54
- // no winner, skip it.
55
- // don't add to conflict list and just use the existing item for future conflicts.
60
+ // No winner. Keep track of the conflictItem, so that if a subsequent
61
+ // item wins for this key, both items (for which there was no winner when
62
+ // compared to each other) can be counted as conflicts and removed from
63
+ // the corresponding list.
64
+
65
+ List < TConflictItem > unresolvedConflictsForKey ;
66
+ if ( ! _unresolvedConflictItems . TryGetValue ( itemKey , out unresolvedConflictsForKey ) )
67
+ {
68
+ unresolvedConflictsForKey = new List < TConflictItem > ( ) ;
69
+ _unresolvedConflictItems [ itemKey ] = unresolvedConflictsForKey ;
56
70
57
- // Report unresolved conflict (currently just used as a test hook)
58
- unresolvedConflict ? . Invoke ( conflictItem ) ;
71
+ // This is the first time we hit an unresolved conflict for this key, so
72
+ // add the existing item to the unresolved conflicts list
73
+ unresolvedConflictsForKey . Add ( existingItem ) ;
74
+ }
75
+
76
+ // Add the new item to the unresolved conflicts list
77
+ unresolvedConflictsForKey . Add ( conflictItem ) ;
59
78
60
79
continue ;
61
80
}
@@ -66,30 +85,69 @@ public void ResolveConflicts(IEnumerable<TConflictItem> conflictItems, Func<TCon
66
85
// replace existing item
67
86
if ( commitWinner )
68
87
{
69
- winningItemsByKey [ itemKey ] = conflictItem ;
88
+ _winningItemsByKey [ itemKey ] = conflictItem ;
70
89
}
71
90
else
72
91
{
73
- winningItemsByKey . Remove ( itemKey ) ;
92
+ _winningItemsByKey . Remove ( itemKey ) ;
74
93
}
75
94
loser = existingItem ;
76
-
77
95
}
78
96
79
- foundConflict ( loser ) ;
97
+ foundConflict ( winner , loser ) ;
98
+
99
+ // If there were any other items that tied with the loser, report them as conflicts here
100
+ List < TConflictItem > previouslyUnresolvedConflicts ;
101
+ if ( _unresolvedConflictItems . TryGetValue ( itemKey , out previouslyUnresolvedConflicts ) )
102
+ {
103
+ // Skip the first item in the list of items that had unresolved conflicts, as it should
104
+ // be the same as the losing item which was just reported.
105
+ foreach ( var previouslyUnresolvedItem in previouslyUnresolvedConflicts . Skip ( 1 ) )
106
+ {
107
+ // Call ResolveConflict with the new winner and item that previously had an unresolved
108
+ // conflict, so that the correct message will be logged recording that the winner
109
+ // won and why
110
+ ResolveConflict ( winner , previouslyUnresolvedItem , logUnresolvedConflicts : true ) ;
111
+
112
+ foundConflict ( winner , previouslyUnresolvedItem ) ;
113
+ }
114
+ _unresolvedConflictItems . Remove ( itemKey ) ;
115
+ }
80
116
}
81
117
else if ( commitWinner )
82
118
{
83
- winningItemsByKey [ itemKey ] = conflictItem ;
119
+ _winningItemsByKey [ itemKey ] = conflictItem ;
120
+ }
121
+ }
122
+ }
123
+
124
+ public void Dispose ( )
125
+ {
126
+ // Report unresolved conflict items that didn't end up losing subsequently
127
+ foreach ( var itemKey in _unresolvedConflictItems . Keys )
128
+ {
129
+ // Report the first item as an unresolved conflict
130
+ var firstItem = _unresolvedConflictItems [ itemKey ] [ 0 ] ;
131
+ UnresolvedConflictHandler ? . Invoke ( firstItem ) ;
132
+
133
+ // For subsequent items, report them as unresolved conflicts, and log a message
134
+ // that they were an unresolved conflict with the first item
135
+ foreach ( var unresolvedConflictItem in _unresolvedConflictItems [ itemKey ] . Skip ( 1 ) )
136
+ {
137
+ UnresolvedConflictHandler ? . Invoke ( unresolvedConflictItem ) ;
138
+
139
+ // Call ResolveConflict to generate the right log message about the unresolved conflict
140
+ ResolveConflict ( firstItem , unresolvedConflictItem , logUnresolvedConflicts : true ) ;
84
141
}
142
+
85
143
}
86
144
}
87
145
88
146
readonly string SENTENCE_SPACING = " " ;
89
147
90
- private TConflictItem ResolveConflict ( TConflictItem item1 , TConflictItem item2 )
148
+ private TConflictItem ResolveConflict ( TConflictItem item1 , TConflictItem item2 , bool logUnresolvedConflicts )
91
149
{
92
- var winner = packageOverrideResolver . Resolve ( item1 , item2 ) ;
150
+ var winner = _packageOverrideResolver . Resolve ( item1 , item2 ) ;
93
151
if ( winner != null )
94
152
{
95
153
return winner ;
@@ -111,10 +169,13 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2)
111
169
112
170
if ( ! exists1 || ! exists2 )
113
171
{
114
- string fileMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . CouldNotDetermineWinner_DoesntExist ,
115
- ! exists1 ? item1 . DisplayName : item2 . DisplayName ) ;
172
+ if ( logUnresolvedConflicts )
173
+ {
174
+ string fileMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . CouldNotDetermineWinner_DoesntExist ,
175
+ ! exists1 ? item1 . DisplayName : item2 . DisplayName ) ;
116
176
117
- log . LogMessage ( fileMessage ) ;
177
+ _log . LogMessage ( fileMessage ) ;
178
+ }
118
179
return null ;
119
180
}
120
181
@@ -124,11 +185,14 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2)
124
185
// if only one is missing version stop: something is wrong when we have a conflict between assembly and non-assembly
125
186
if ( assemblyVersion1 == null ^ assemblyVersion2 == null )
126
187
{
127
- var nonAssembly = assemblyVersion1 == null ? item1 . DisplayName : item2 . DisplayName ;
128
- string assemblyMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . CouldNotDetermineWinner_NotAnAssembly ,
129
- nonAssembly ) ;
130
-
131
- log . LogMessage ( assemblyMessage ) ;
188
+ if ( logUnresolvedConflicts )
189
+ {
190
+ var nonAssembly = assemblyVersion1 == null ? item1 . DisplayName : item2 . DisplayName ;
191
+ string assemblyMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . CouldNotDetermineWinner_NotAnAssembly ,
192
+ nonAssembly ) ;
193
+
194
+ _log . LogMessage ( assemblyMessage ) ;
195
+ }
132
196
return null ;
133
197
}
134
198
@@ -157,7 +221,7 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2)
157
221
winningVersion ,
158
222
losingVersion ) ;
159
223
160
- log . LogMessage ( assemblyMessage ) ;
224
+ _log . LogMessage ( assemblyMessage ) ;
161
225
162
226
if ( assemblyVersion1 > assemblyVersion2 )
163
227
{
@@ -176,9 +240,12 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2)
176
240
// if only one is missing version
177
241
if ( fileVersion1 == null ^ fileVersion2 == null )
178
242
{
179
- var nonVersion = fileVersion1 == null ? item1 . DisplayName : item2 . DisplayName ;
180
- string fileVersionMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . CouldNotDetermineWinner_FileVersion ,
181
- nonVersion ) ;
243
+ if ( logUnresolvedConflicts )
244
+ {
245
+ var nonVersion = fileVersion1 == null ? item1 . DisplayName : item2 . DisplayName ;
246
+ string fileVersionMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . CouldNotDetermineWinner_FileVersion ,
247
+ nonVersion ) ;
248
+ }
182
249
return null ;
183
250
}
184
251
@@ -206,7 +273,7 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2)
206
273
winningVersion ,
207
274
losingVersion ) ;
208
275
209
- log . LogMessage ( fileVersionMessage ) ;
276
+ _log . LogMessage ( fileVersionMessage ) ;
210
277
211
278
if ( fileVersion1 > fileVersion2 )
212
279
{
@@ -219,14 +286,14 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2)
219
286
}
220
287
}
221
288
222
- var packageRank1 = packageRank . GetPackageRank ( item1 . PackageId ) ;
223
- var packageRank2 = packageRank . GetPackageRank ( item2 . PackageId ) ;
289
+ var packageRank1 = _packageRank . GetPackageRank ( item1 . PackageId ) ;
290
+ var packageRank2 = _packageRank . GetPackageRank ( item2 . PackageId ) ;
224
291
225
292
if ( packageRank1 < packageRank2 )
226
293
{
227
294
string packageRankMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . ChoosingPreferredPackage ,
228
295
item1 . DisplayName ) ;
229
- log . LogMessage ( packageRankMessage ) ;
296
+ _log . LogMessage ( packageRankMessage ) ;
230
297
return item1 ;
231
298
}
232
299
@@ -244,21 +311,24 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2)
244
311
{
245
312
string platformMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . ChoosingPlatformItem ,
246
313
item1 . DisplayName ) ;
247
- log . LogMessage ( platformMessage ) ;
314
+ _log . LogMessage ( platformMessage ) ;
248
315
return item1 ;
249
316
}
250
317
251
318
if ( ! isPlatform1 && isPlatform2 )
252
319
{
253
320
string platformMessage = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . ChoosingPlatformItem ,
254
321
item2 . DisplayName ) ;
255
- log . LogMessage ( platformMessage ) ;
322
+ _log . LogMessage ( platformMessage ) ;
256
323
return item2 ;
257
324
}
258
325
259
- string message = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . ConflictCouldNotDetermineWinner ) ;
326
+ if ( logUnresolvedConflicts )
327
+ {
328
+ string message = conflictMessage + SENTENCE_SPACING + string . Format ( CultureInfo . CurrentCulture , Strings . ConflictCouldNotDetermineWinner ) ;
260
329
261
- log . LogMessage ( message ) ;
330
+ _log . LogMessage ( message ) ;
331
+ }
262
332
return null ;
263
333
}
264
334
}
0 commit comments