forked from dotnet/msbuild
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ResultsCache.cs
347 lines (305 loc) · 14.5 KB
/
ResultsCache.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
namespace Microsoft.Build.BackEnd
{
/// <summary>
/// Implementation of the results cache.
/// </summary>
internal class ResultsCache : IResultsCache
{
/// <summary>
/// The table of all build results. This table is indexed by configuration id and
/// contains BuildResult objects which have all of the target information.
/// </summary>
private ConcurrentDictionary<int, BuildResult> _resultsByConfiguration;
/// <summary>
/// Creates an empty results cache.
/// </summary>
public ResultsCache()
{
_resultsByConfiguration = new ConcurrentDictionary<int, BuildResult>();
}
public ResultsCache(ITranslator translator)
{
Translate(translator);
}
/// <summary>
/// Returns the internal cache for testing purposes.
/// </summary>
internal IDictionary<int, BuildResult> ResultsDictionary
{
get
{
return _resultsByConfiguration;
}
}
#region IResultsCache Members
/// <summary>
/// Adds the specified build result to the cache
/// </summary>
/// <param name="result">The result to add.</param>
public void AddResult(BuildResult result)
{
lock (_resultsByConfiguration)
{
if (_resultsByConfiguration.ContainsKey(result.ConfigurationId))
{
if (Object.ReferenceEquals(_resultsByConfiguration[result.ConfigurationId], result))
{
// Merging results would be meaningless as we would be merging the object with itself.
return;
}
_resultsByConfiguration[result.ConfigurationId].MergeResults(result);
}
else
{
// Note that we are not making a copy here. This is by-design. The TargetBuilder uses this behavior
// to ensure that re-entering a project will be able to see all previously built targets and avoid
// building them again.
if (!_resultsByConfiguration.TryAdd(result.ConfigurationId, result))
{
ErrorUtilities.ThrowInternalError("Failed to add result for configuration {0}", result.ConfigurationId);
}
}
}
}
/// <summary>
/// Clears the results for the specified build.
/// </summary>
public void ClearResults()
{
lock (_resultsByConfiguration)
{
foreach (KeyValuePair<int, BuildResult> result in _resultsByConfiguration)
{
result.Value.ClearCachedFiles();
}
_resultsByConfiguration.Clear();
}
}
/// <summary>
/// Retrieves the results for the specified build request.
/// </summary>
/// <param name="request">The request for which results should be retrieved.</param>
/// <returns>The build results for the specified request.</returns>
public BuildResult GetResultForRequest(BuildRequest request)
{
ErrorUtilities.VerifyThrowArgument(request.IsConfigurationResolved, "UnresolvedConfigurationInRequest");
lock (_resultsByConfiguration)
{
if (_resultsByConfiguration.ContainsKey(request.ConfigurationId))
{
BuildResult result = _resultsByConfiguration[request.ConfigurationId];
foreach (string target in request.Targets)
{
ErrorUtilities.VerifyThrow(result.HasResultsForTarget(target), "No results in cache for target " + target);
}
return result;
}
}
return null;
}
/// <summary>
/// Retrieves the results for the specified configuration
/// </summary>
/// <param name="configurationId">The configuration for which results should be returned.</param>
/// <returns>The results, if any</returns>
public BuildResult GetResultsForConfiguration(int configurationId)
{
BuildResult results;
lock (_resultsByConfiguration)
{
_resultsByConfiguration.TryGetValue(configurationId, out results);
}
return results;
}
/// <summary>
/// Attempts to satisfy the request from the cache. The request can be satisfied only if:
/// 1. All specified targets in the request have successful results in the cache or if the sequence of target results
/// includes 0 or more successful targets followed by at least one failed target.
/// 2. All initial targets in the configuration for the request have non-skipped results in the cache.
/// 3. If there are no specified targets, then all default targets in the request must have non-skipped results
/// in the cache.
/// </summary>
/// <param name="request">The request whose results we should return</param>
/// <param name="configInitialTargets">The initial targets for the request's configuration.</param>
/// <param name="configDefaultTargets">The default targets for the request's configuration.</param>
/// <param name="skippedResultsDoNotCauseCacheMiss">If false, a cached skipped target will cause this method to return "NotSatisfied".
/// If true, then as long as there is a result in the cache (regardless of whether it was skipped or not), this method
/// will return "Satisfied". In most cases this should be false, but it may be set to true in a situation where there is no
/// chance of re-execution (which is the usual response to missing / skipped targets), and the caller just needs the data.</param>
/// <returns>A response indicating the results, if any, and the targets needing to be built, if any.</returns>
public ResultsCacheResponse SatisfyRequest(BuildRequest request, List<string> configInitialTargets, List<string> configDefaultTargets, bool skippedResultsDoNotCauseCacheMiss)
{
ErrorUtilities.VerifyThrowArgument(request.IsConfigurationResolved, "UnresolvedConfigurationInRequest");
ResultsCacheResponse response = new ResultsCacheResponse(ResultsCacheResponseType.NotSatisfied);
lock (_resultsByConfiguration)
{
if (_resultsByConfiguration.ContainsKey(request.ConfigurationId))
{
BuildResult allResults = _resultsByConfiguration[request.ConfigurationId];
// Check for targets explicitly specified.
bool explicitTargetsSatisfied = CheckResults(allResults, request.Targets, response.ExplicitTargetsToBuild, skippedResultsDoNotCauseCacheMiss);
if (explicitTargetsSatisfied)
{
// All of the explicit targets, if any, have been satisfied
response.Type = ResultsCacheResponseType.Satisfied;
// Check for the initial targets. If we don't know what the initial targets are, we assume they are not satisfied.
if (configInitialTargets == null || !CheckResults(allResults, configInitialTargets, null, skippedResultsDoNotCauseCacheMiss))
{
response.Type = ResultsCacheResponseType.NotSatisfied;
}
// We could still be missing implicit targets, so check those...
if (request.Targets.Count == 0)
{
// Check for the default target, if necessary. If we don't know what the default targets are, we
// assume they are not satisfied.
if (configDefaultTargets == null || !CheckResults(allResults, configDefaultTargets, null, skippedResultsDoNotCauseCacheMiss))
{
response.Type = ResultsCacheResponseType.NotSatisfied;
}
}
// Now report those results requested, if they are satisfied.
if (response.Type == ResultsCacheResponseType.Satisfied)
{
List<string> targetsToAddResultsFor = new List<string>(configInitialTargets);
// Now report either the explicit targets or the default targets
if (request.Targets.Count > 0)
{
targetsToAddResultsFor.AddRange(request.Targets);
}
else
{
targetsToAddResultsFor.AddRange(configDefaultTargets);
}
response.Results = new BuildResult(request, allResults, targetsToAddResultsFor.ToArray(), null);
}
}
else
{
// Some targets were not satisfied.
response.Type = ResultsCacheResponseType.NotSatisfied;
}
}
}
return response;
}
/// <summary>
/// Removes the results for a particular configuration.
/// </summary>
/// <param name="configurationId">The configuration</param>
public void ClearResultsForConfiguration(int configurationId)
{
lock (_resultsByConfiguration)
{
BuildResult removedResult;
_resultsByConfiguration.TryRemove(configurationId, out removedResult);
removedResult?.ClearCachedFiles();
}
}
public void Translate(ITranslator translator)
{
IDictionary<int, BuildResult> localReference = _resultsByConfiguration;
translator.TranslateDictionary(
ref localReference,
(ITranslator aTranslator, ref int i) => aTranslator.Translate(ref i),
(ITranslator aTranslator, ref BuildResult result) => aTranslator.Translate(ref result),
capacity => new ConcurrentDictionary<int, BuildResult>(NativeMethodsShared.GetLogicalCoreCount(), capacity));
if (translator.Mode == TranslationDirection.ReadFromStream)
{
_resultsByConfiguration = (ConcurrentDictionary<int, BuildResult>) localReference;
}
}
/// <summary>
/// Cache as many results as we can.
/// </summary>
public void WriteResultsToDisk()
{
lock (_resultsByConfiguration)
{
foreach (BuildResult resultToCache in _resultsByConfiguration.Values)
{
resultToCache.CacheIfPossible();
}
}
}
#endregion
#region IBuildComponent Members
/// <summary>
/// Sets the build component host.
/// </summary>
/// <param name="host">The component host.</param>
public void InitializeComponent(IBuildComponentHost host)
{
ErrorUtilities.VerifyThrowArgumentNull(host, nameof(host));
}
/// <summary>
/// Shuts down this component
/// </summary>
public void ShutdownComponent()
{
_resultsByConfiguration.Clear();
}
#endregion
/// <summary>
/// Factory for component creation.
/// </summary>
internal static IBuildComponent CreateComponent(BuildComponentType componentType)
{
ErrorUtilities.VerifyThrow(componentType == BuildComponentType.ResultsCache, "Cannot create components of type {0}", componentType);
return new ResultsCache();
}
/// <summary>
/// Looks for results for the specified targets.
/// </summary>
/// <param name="result">The result to examine</param>
/// <param name="targets">The targets to search for</param>
/// <param name="targetsMissingResults">An optional list to be populated with missing targets</param>
/// <param name="skippedResultsAreOK">If true, a status of "skipped" counts as having valid results
/// for that target. Otherwise, a skipped target is treated as equivalent to a missing target.</param>
/// <returns>False if there were missing results, true otherwise.</returns>
private static bool CheckResults(BuildResult result, List<string> targets, HashSet<string> targetsMissingResults, bool skippedResultsAreOK)
{
bool returnValue = true;
foreach (string target in targets)
{
if (!result.HasResultsForTarget(target) || (result[target].ResultCode == TargetResultCode.Skipped && !skippedResultsAreOK))
{
if (targetsMissingResults != null)
{
targetsMissingResults.Add(target);
returnValue = false;
}
else
{
return false;
}
}
else
{
// If the result was a failure and we have not seen any skipped targets up to this point, then we conclude we do
// have results for this request, and they indicate failure.
if (result[target].ResultCode == TargetResultCode.Failure && (targetsMissingResults == null || targetsMissingResults.Count == 0))
{
return true;
}
}
}
return returnValue;
}
public IEnumerator<BuildResult> GetEnumerator()
{
return _resultsByConfiguration.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}