-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathAssemblyFoldersExResolver.cs
335 lines (290 loc) · 13.6 KB
/
AssemblyFoldersExResolver.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#if FEATURE_WIN32_REGISTRY
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using ProcessorArchitecture = System.Reflection.ProcessorArchitecture;
#nullable disable
namespace Microsoft.Build.Tasks
{
/// <summary>
/// Resolve searchpath type {Registry: *}
/// </summary>
internal class AssemblyFoldersExResolver : Resolver
{
/// <summary>
/// Regex for breaking up the searchpath pieces.
/// </summary>
private static readonly Lazy<Regex> s_crackAssemblyFoldersExSentinel = new Lazy<Regex>(
() => new Regex(
AssemblyResolutionConstants.assemblyFoldersExSentinel +
"(?<REGISTRYKEYROOT>[^,]*),(?<TARGETRUNTIMEVERSION>[^,]*),(?<REGISTRYKEYSUFFIX>[^,]*)([,]*)(?<CONDITIONS>.*)}",
RegexOptions.IgnoreCase | RegexOptions.Compiled));
/// <summary>
/// Delegate.
/// </summary>
private readonly GetRegistrySubKeyNames _getRegistrySubKeyNames;
/// <summary>
/// Delegate
/// </summary>
private readonly GetRegistrySubKeyDefaultValue _getRegistrySubKeyDefaultValue;
/// <summary>
/// Open the base registry key given a hive and a view
/// </summary>
private readonly OpenBaseKey _openBaseKey;
/// <summary>
/// Whether or not the search path could be cracked.
/// </summary>
private bool _wasMatch;
/// <summary>
/// From the search path.
/// </summary>
private string _registryKeyRoot;
/// <summary>
/// From the search path.
/// </summary>
private string _targetRuntimeVersion;
/// <summary>
/// From the search path.
/// </summary>
private string _registryKeySuffix;
/// <summary>
/// From the search path.
/// </summary>
private string _osVersion;
/// <summary>
/// From the search path.
/// </summary>
private string _platform;
/// <summary>
/// Whether regex initialization has happened.
/// </summary>
private bool _isInitialized; // is initialized to false automatically
/// <summary>
/// List of assembly folders to search for keys in.
/// </summary>
private AssemblyFoldersExCache _assemblyFoldersCache;
/// <summary>
/// BuildEngine
/// </summary>
private readonly IBuildEngine4 _buildEngine;
/// <summary>
/// If it is not initialized then just return the null object, that would mean the resolver was not called.
/// </summary>
internal AssemblyFoldersEx AssemblyFoldersExLocations => _assemblyFoldersCache?.AssemblyFoldersEx;
/// <summary>
/// Construct.
/// </summary>
public AssemblyFoldersExResolver(string searchPathElement, GetAssemblyName getAssemblyName, FileExists fileExists, GetRegistrySubKeyNames getRegistrySubKeyNames, GetRegistrySubKeyDefaultValue getRegistrySubKeyDefaultValue, GetAssemblyRuntimeVersion getRuntimeVersion, OpenBaseKey openBaseKey, Version targetedRuntimeVesion, ProcessorArchitecture targetProcessorArchitecture, bool compareProcessorArchitecture, IBuildEngine buildEngine)
: base(searchPathElement, getAssemblyName, fileExists, getRuntimeVersion, targetedRuntimeVesion, targetProcessorArchitecture, compareProcessorArchitecture)
{
_buildEngine = buildEngine as IBuildEngine4;
_getRegistrySubKeyNames = getRegistrySubKeyNames;
_getRegistrySubKeyDefaultValue = getRegistrySubKeyDefaultValue;
_openBaseKey = openBaseKey;
}
/// <summary>
/// Initialize this class if it hasn't been initialized yet.
/// </summary>
private void LazyInitialize()
{
if (_isInitialized)
{
return;
}
_isInitialized = true;
// Crack the search path just one time.
Match match = s_crackAssemblyFoldersExSentinel.Value.Match(this.searchPathElement);
_wasMatch = false;
if (match.Success)
{
_registryKeyRoot = match.Groups["REGISTRYKEYROOT"].Value.Trim();
_targetRuntimeVersion = match.Groups["TARGETRUNTIMEVERSION"].Value.Trim();
_registryKeySuffix = match.Groups["REGISTRYKEYSUFFIX"].Value.Trim();
_osVersion = null;
_platform = null;
Group conditions = match.Groups["CONDITIONS"];
// Disregard if there are any empty values in the {Registry} tag.
if (_registryKeyRoot.Length != 0 && _targetRuntimeVersion.Length != 0 && _registryKeySuffix.Length != 0)
{
// Tolerate version keys that don't begin with "v" as these could come from user input
if (!_targetRuntimeVersion.StartsWith("v", StringComparison.OrdinalIgnoreCase))
{
_targetRuntimeVersion = _targetRuntimeVersion.Insert(0, "v");
}
if (conditions?.Value != null && conditions.Length > 0 && conditions.Value.Length > 0)
{
string value = conditions.Value.Trim();
// Parse the condition statement for OSVersion and Platform
foreach (string c in value.Split(MSBuildConstants.ColonChar))
{
if (String.Compare(c, 0, "OSVERSION=", 0, 10, StringComparison.OrdinalIgnoreCase) == 0)
{
_osVersion = c.Substring(10);
}
else if (String.Compare(c, 0, "PLATFORM=", 0, 9, StringComparison.OrdinalIgnoreCase) == 0)
{
_platform = c.Substring(9);
}
}
}
_wasMatch = true;
bool useCache = Environment.GetEnvironmentVariable("MSBUILDDISABLEASSEMBLYFOLDERSEXCACHE") == null;
string key = "ca22615d-aa83-444b-80b9-b32f3d5db097" + this.searchPathElement;
if (useCache && _buildEngine != null)
{
_assemblyFoldersCache = _buildEngine.GetRegisteredTaskObject(key, RegisteredTaskObjectLifetime.Build) as AssemblyFoldersExCache;
}
if (_assemblyFoldersCache == null)
{
AssemblyFoldersEx assemblyFolders = new AssemblyFoldersEx(_registryKeyRoot, _targetRuntimeVersion, _registryKeySuffix, _osVersion, _platform, _getRegistrySubKeyNames, _getRegistrySubKeyDefaultValue, this.targetProcessorArchitecture, _openBaseKey);
_assemblyFoldersCache = new AssemblyFoldersExCache(assemblyFolders, fileExists);
if (useCache)
{
_buildEngine?.RegisterTaskObject(key, _assemblyFoldersCache, RegisteredTaskObjectLifetime.Build, true /* dispose early ok*/);
}
}
fileExists = _assemblyFoldersCache.FileExists;
}
}
}
/// <inheritdoc/>
public override bool Resolve(
AssemblyNameExtension assemblyName,
string sdkName,
string rawFileNameCandidate,
bool isPrimaryProjectReference,
bool isImmutableFrameworkReference,
bool wantSpecificVersion,
string[] executableExtensions,
string hintPath,
string assemblyFolderKey,
List<ResolutionSearchLocation> assembliesConsideredAndRejected,
out string foundPath,
out bool userRequestedSpecificFile)
{
foundPath = null;
userRequestedSpecificFile = false;
if (assemblyName != null)
{
LazyInitialize();
if (_wasMatch)
{
string resolvedPath = null;
if (_assemblyFoldersCache != null)
{
foreach (AssemblyFoldersExInfo assemblyFolder in _assemblyFoldersCache.AssemblyFoldersEx)
{
string candidatePath = ResolveFromDirectory(assemblyName, isPrimaryProjectReference, wantSpecificVersion, executableExtensions, assemblyFolder.DirectoryPath, assembliesConsideredAndRejected);
// We have a full path returned
if (candidatePath != null)
{
if (resolvedPath == null)
{
resolvedPath = candidatePath;
}
// We are not targeting MSIL thus we must have a match because ResolveFromDirectory only will return a match if we find an assembly matching the targeted processor architecture
if (targetProcessorArchitecture != ProcessorArchitecture.MSIL && targetProcessorArchitecture != ProcessorArchitecture.None)
{
foundPath = candidatePath;
return true;
}
else
{
// Lets see if the processor architecture matches, note this this method will cache the result when it was first called.
AssemblyNameExtension foundAssembly = getAssemblyName(candidatePath);
// If the processor architecture does not match the we should continue to see if there is a better match.
if (foundAssembly != null && (foundAssembly.AssemblyName.ProcessorArchitecture == ProcessorArchitecture.MSIL || foundAssembly.AssemblyName.ProcessorArchitecture == ProcessorArchitecture.None))
{
foundPath = candidatePath;
return true;
}
}
}
}
}
// If we get to this point and have not returned then we have the best assembly we could find, lets return it.
if (resolvedPath != null)
{
foundPath = resolvedPath;
return true;
}
}
}
return false;
}
}
/// <summary>
/// Contains information about entries in the AssemblyFoldersEx registry keys.
/// </summary>
internal class AssemblyFoldersExCache
{
/// <summary>
/// Set of files in ALL assemblyfoldersEx directories
/// </summary>
private readonly HashSet<string> _filesInDirectories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// File exists delegate we are replacing
/// </summary>
private readonly FileExists _fileExists;
/// <summary>
/// Should we use the original on or use our own
/// </summary>
private readonly bool _useOriginalFileExists;
/// <summary>
/// Constructor
/// </summary>
internal AssemblyFoldersExCache(AssemblyFoldersEx assemblyFoldersEx, FileExists fileExists)
{
AssemblyFoldersEx = assemblyFoldersEx;
_fileExists = fileExists;
if (Environment.GetEnvironmentVariable("MSBUILDDISABLEASSEMBLYFOLDERSEXCACHE") != null)
{
_useOriginalFileExists = true;
}
else
{
var lockobject = new Object();
Parallel.ForEach(assemblyFoldersEx.UniqueDirectoryPaths, assemblyFolder =>
{
if (FileUtilities.DirectoryExistsNoThrow(assemblyFolder))
{
string[] files = Directory.GetFiles(assemblyFolder, "*.*", SearchOption.TopDirectoryOnly);
lock (lockobject)
{
foreach (string file in files)
{
_filesInDirectories.Add(file);
}
}
}
});
}
}
/// <summary>
/// AssemblyfoldersEx object which contains the set of directories in assmblyfoldersex
/// </summary>
internal AssemblyFoldersEx AssemblyFoldersEx { get; }
/// <summary>
/// Fast file exists for assemblyfoldersex.
/// </summary>
internal bool FileExists(string path)
{
// Make sure that the file is in one of the directories under the assembly folders ex location
// if it is not then we can not use this fast file existence check
if (!_useOriginalFileExists)
{
bool exists = _filesInDirectories.Contains(path);
return exists;
}
return _fileExists(path);
}
}
}
#endif