-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathToolsetReader.cs
705 lines (623 loc) · 35.1 KB
/
ToolsetReader.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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Microsoft.Build.Collections;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using ErrorUtils = Microsoft.Build.Shared.ErrorUtilities;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
using InvalidToolsetDefinitionException = Microsoft.Build.Exceptions.InvalidToolsetDefinitionException;
using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames;
#nullable disable
namespace Microsoft.Build.Evaluation
{
/// <summary>
/// The abstract base class for all Toolset readers.
/// </summary>
internal abstract class ToolsetReader
{
/// <summary>
/// The environment properties used to read the toolset.
/// </summary>
private readonly PropertyDictionary<ProjectPropertyInstance> _environmentProperties;
/// <summary>
/// Constructor
/// </summary>
protected ToolsetReader(
PropertyDictionary<ProjectPropertyInstance> environmentProperties,
PropertyDictionary<ProjectPropertyInstance> globalProperties)
{
_environmentProperties = environmentProperties;
}
/// <summary>
/// Returns the list of tools versions
/// </summary>
protected abstract IEnumerable<ToolsetPropertyDefinition> ToolsVersions
{
get;
}
/// <summary>
/// Returns the default tools version, or null if none was specified
/// </summary>
protected abstract string DefaultToolsVersion
{
get;
}
/// <summary>
/// Returns the path to find override tasks, or null if none was specified
/// </summary>
protected abstract string MSBuildOverrideTasksPath
{
get;
}
/// <summary>
/// ToolsVersion to use as the default ToolsVersion for this version of MSBuild
/// </summary>
protected abstract string DefaultOverrideToolsVersion
{
get;
}
/// <summary>
/// Gathers toolset data from the registry and configuration file, if any:
/// allows you to specify which of the registry and configuration file to
/// read from by providing ToolsetInitialization
/// </summary>
internal static string ReadAllToolsets(Dictionary<string, Toolset> toolsets, PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, ToolsetDefinitionLocations locations)
{
return ReadAllToolsets(toolsets,
#if FEATURE_WIN32_REGISTRY
null,
#endif
null,
environmentProperties, globalProperties, locations);
}
/// <summary>
/// Gathers toolset data from the registry and configuration file, if any.
/// NOTE: this method is internal for unit testing purposes only.
/// </summary>
internal static string ReadAllToolsets(
Dictionary<string, Toolset> toolsets,
#if FEATURE_WIN32_REGISTRY
ToolsetRegistryReader registryReader,
#endif
ToolsetConfigurationReader configurationReader,
PropertyDictionary<ProjectPropertyInstance> environmentProperties,
PropertyDictionary<ProjectPropertyInstance> globalProperties,
ToolsetDefinitionLocations locations)
{
var initialProperties =
new PropertyDictionary<ProjectPropertyInstance>(environmentProperties);
initialProperties.ImportProperties(globalProperties);
// The ordering here is important because the configuration file should have greater precedence
// than the registry, and we do a check and don't read in the new toolset if there's already one.
string defaultToolsVersionFromConfiguration = null;
string overrideTasksPathFromConfiguration = null;
string defaultOverrideToolsVersionFromConfiguration = null;
if ((locations & ToolsetDefinitionLocations.ConfigurationFile) == ToolsetDefinitionLocations.ConfigurationFile)
{
if (configurationReader == null)
{
configurationReader = new ToolsetConfigurationReader(environmentProperties, globalProperties);
}
ReadConfigToolset();
// This is isolated into its own function in order to isolate loading of
// System.Configuration.ConfigurationManager.dll to codepaths that really
// need it as a way of mitigating the need to update references to that
// assembly in API consumers.
//
// https://github.com/microsoft/MSBuildLocator/issues/159
[MethodImplAttribute(MethodImplOptions.NoInlining)]
void ReadConfigToolset()
{
// Accumulation of properties is okay in the config file because it's deterministically ordered
defaultToolsVersionFromConfiguration = configurationReader.ReadToolsets(toolsets, globalProperties,
initialProperties, true /* accumulate properties */, out overrideTasksPathFromConfiguration,
out defaultOverrideToolsVersionFromConfiguration);
}
}
string defaultToolsVersionFromRegistry = null;
string overrideTasksPathFromRegistry = null;
string defaultOverrideToolsVersionFromRegistry = null;
if ((locations & ToolsetDefinitionLocations.Registry) == ToolsetDefinitionLocations.Registry)
{
#if FEATURE_WIN32_REGISTRY
if (NativeMethodsShared.IsWindows || registryReader != null)
{
// If we haven't been provided a registry reader (i.e. unit tests), create one
registryReader ??= new ToolsetRegistryReader(environmentProperties, globalProperties);
// We do not accumulate properties when reading them from the registry, because the order
// in which values are returned to us is essentially random: so we disallow one property
// in the registry to refer to another also in the registry
defaultToolsVersionFromRegistry = registryReader.ReadToolsets(toolsets, globalProperties,
initialProperties, false /* do not accumulate properties */, out overrideTasksPathFromRegistry,
out defaultOverrideToolsVersionFromRegistry);
}
else
#endif
{
var currentDir = BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory.TrimEnd(Path.DirectorySeparatorChar);
var props = new PropertyDictionary<ProjectPropertyInstance>();
var libraryPath = NativeMethodsShared.FrameworkBasePath;
if (!string.IsNullOrEmpty(libraryPath))
{
// The 4.0 toolset is installed in the framework directory
var v4Dir =
FrameworkLocationHelper.GetPathToDotNetFrameworkV40(DotNetFrameworkArchitecture.Current);
if (v4Dir != null && !toolsets.ContainsKey("4.0"))
{
toolsets.Add(
"4.0",
new Toolset(
"4.0",
v4Dir,
buildProperties: null,
environmentProperties,
globalProperties,
null,
currentDir,
string.Empty));
}
// Other toolsets are installed in the xbuild directory
var xbuildToolsetsDir = Path.Combine(libraryPath, $"xbuild{Path.DirectorySeparatorChar}");
if (FileSystems.Default.DirectoryExists(xbuildToolsetsDir))
{
var r = new Regex(Regex.Escape(xbuildToolsetsDir) + @"\d+\.\d+");
foreach (var d in Directory.GetDirectories(xbuildToolsetsDir).Where(d => r.IsMatch(d)))
{
var version = Path.GetFileName(d);
var binPath = Path.Combine(d, "bin");
if (toolsets.ContainsKey(version))
{
continue;
}
toolsets.Add(
version,
new Toolset(
version,
binPath,
buildProperties: null,
environmentProperties,
globalProperties,
null,
currentDir,
string.Empty));
}
}
}
if (!toolsets.ContainsKey(MSBuildConstants.CurrentToolsVersion))
{
toolsets.Add(
MSBuildConstants.CurrentToolsVersion,
new Toolset(
MSBuildConstants.CurrentToolsVersion,
currentDir,
props,
new PropertyDictionary<ProjectPropertyInstance>(),
currentDir,
string.Empty));
}
}
}
// The 2.0 .NET Framework installer did not write a ToolsVersion key for itself in the registry.
// The 3.5 installer writes one for 2.0, but 3.5 might not be installed.
// The 4.0 and subsequent installers can't keep writing the 2.0 one, because (a) it causes SxS issues and (b) we
// don't want it unless 2.0 is installed.
// So if the 2.0 framework is actually installed, we're reading the registry, and either the registry or the config
// file have not already created the 2.0 toolset, mock up a fake one.
if (((locations & ToolsetDefinitionLocations.Registry) != 0) && !toolsets.ContainsKey("2.0")
&& FrameworkLocationHelper.PathToDotNetFrameworkV20 != null)
{
var synthetic20Toolset = new Toolset(
"2.0",
FrameworkLocationHelper.PathToDotNetFrameworkV20,
environmentProperties,
globalProperties,
null /* 2.0 did not have override tasks */,
null /* 2.0 did not have a default override toolsversion */);
toolsets.Add("2.0", synthetic20Toolset);
}
string defaultToolsVersionFromLocal = null;
string overrideTasksPathFromLocal = null;
string defaultOverrideToolsVersionFromLocal = null;
if ((locations & ToolsetDefinitionLocations.Local) == ToolsetDefinitionLocations.Local)
{
var localReader = new ToolsetLocalReader(environmentProperties, globalProperties);
defaultToolsVersionFromLocal = localReader.ReadToolsets(
toolsets,
globalProperties,
initialProperties,
false /* accumulate properties */,
out overrideTasksPathFromLocal,
out defaultOverrideToolsVersionFromLocal);
}
// We'll use the path from the configuration file if it was specified, otherwise we'll try
// the one from the registry. It's possible (and valid) that neither the configuration file
// nor the registry specify a override in which case we'll just return null.
var overrideTasksPath = overrideTasksPathFromConfiguration ?? overrideTasksPathFromRegistry ?? overrideTasksPathFromLocal;
// We'll use the path from the configuration file if it was specified, otherwise we'll try
// the one from the registry. It's possible (and valid) that neither the configuration file
// nor the registry specify a override in which case we'll just return null.
var defaultOverrideToolsVersion = defaultOverrideToolsVersionFromConfiguration
?? defaultOverrideToolsVersionFromRegistry
?? defaultOverrideToolsVersionFromLocal;
// We'll use the default from the configuration file if it was specified, otherwise we'll try
// the one from the registry. It's possible (and valid) that neither the configuration file
// nor the registry specify a default, in which case we'll just return null.
var defaultToolsVersion = defaultToolsVersionFromConfiguration ?? defaultToolsVersionFromRegistry ?? defaultToolsVersionFromLocal;
// If we got a default version from the registry or config file, and it
// actually exists, fine.
// Otherwise we have to come up with one.
if (defaultToolsVersion != null && toolsets.ContainsKey(defaultToolsVersion))
{
return defaultToolsVersion;
}
// We're going to choose a hard coded default tools version of 2.0.
defaultToolsVersion = Constants.defaultToolsVersion;
// But don't overwrite any existing tools path for this default we're choosing.
if (toolsets.ContainsKey(Constants.defaultToolsVersion))
{
return defaultToolsVersion;
}
// There's no tools path already for 2.0, so use the path to the v2.0 .NET Framework.
// If an old-fashioned caller sets BinPath property, or passed a BinPath to the constructor,
// that will overwrite what we're setting here.
ErrorUtilities.VerifyThrow(
Constants.defaultToolsVersion == "2.0",
"Getting 2.0 FX path so default should be 2.0");
var pathToFramework = FrameworkLocationHelper.PathToDotNetFrameworkV20;
// We could not find the default toolsversion because it was not installed on the machine. Fallback to the
// one we expect to always be there when running msbuild 4.0.
if (pathToFramework == null)
{
pathToFramework = FrameworkLocationHelper.PathToDotNetFrameworkV40;
defaultToolsVersion = Constants.defaultFallbackToolsVersion;
}
// Again don't overwrite any existing tools path for this default we're choosing.
if (toolsets.ContainsKey(defaultToolsVersion))
{
return defaultToolsVersion;
}
var defaultToolset = new Toolset(
defaultToolsVersion,
pathToFramework,
environmentProperties,
globalProperties,
overrideTasksPath,
defaultOverrideToolsVersion);
toolsets.Add(defaultToolsVersion, defaultToolset);
return defaultToolsVersion;
}
/// <summary>
/// Populates the toolset collection passed in with the toolsets read from some location.
/// </summary>
/// <remarks>Internal for unit testing only</remarks>
/// <returns>the default tools version if available, or null otherwise</returns>
internal string ReadToolsets(
Dictionary<string, Toolset> toolsets,
PropertyDictionary<ProjectPropertyInstance> globalProperties,
PropertyDictionary<ProjectPropertyInstance> initialProperties,
bool accumulateProperties,
out string msBuildOverrideTasksPath,
out string defaultOverrideToolsVersion)
{
ErrorUtils.VerifyThrowArgumentNull(toolsets, "Toolsets");
ReadEachToolset(toolsets, globalProperties, initialProperties, accumulateProperties);
string defaultToolsVersion = DefaultToolsVersion;
msBuildOverrideTasksPath = MSBuildOverrideTasksPath;
defaultOverrideToolsVersion = DefaultOverrideToolsVersion;
// We don't check whether the default tools version actually
// corresponds to a toolset definition. That's because our default for
// the indefinite future is 2.0, and 2.0 might not be installed, which is fine.
// If a project tries to use 2.0 (or whatever the default is) in these circumstances
// they'll get a nice error saying that toolset isn't available and listing those that are.
return defaultToolsVersion;
}
/// <summary>
/// Provides an enumerator over property definitions for a specified tools version
/// </summary>
/// <param name="toolsVersion">The tools version.</param>
/// <returns>An enumeration of property definitions.</returns>
protected abstract IEnumerable<ToolsetPropertyDefinition> GetPropertyDefinitions(string toolsVersion);
/// <summary>
/// Provides an enumerator over the set of sub-toolset names available to a particular
/// toolsversion
/// </summary>
/// <param name="toolsVersion">The tools version.</param>
/// <returns>An enumeration of the sub-toolsets that belong to that toolsversion.</returns>
protected abstract IEnumerable<string> GetSubToolsetVersions(string toolsVersion);
/// <summary>
/// Provides an enumerator over property definitions for a specified sub-toolset version
/// under a specified toolset version.
/// </summary>
/// <param name="toolsVersion">The tools version.</param>
/// <param name="subToolsetVersion">The sub-toolset version.</param>
/// <returns>An enumeration of property definitions.</returns>
protected abstract IEnumerable<ToolsetPropertyDefinition> GetSubToolsetPropertyDefinitions(string toolsVersion, string subToolsetVersion);
/// <summary>
/// Returns a map of MSBuildExtensionsPath* property names/kind to list of search paths
/// </summary>
protected abstract Dictionary<string, ProjectImportPathMatch> GetProjectImportSearchPathsTable(string toolsVersion, string os);
/// <summary>
/// Reads all the toolsets and populates the given ToolsetCollection with them
/// </summary>
private void ReadEachToolset(
Dictionary<string, Toolset> toolsets,
PropertyDictionary<ProjectPropertyInstance> globalProperties,
PropertyDictionary<ProjectPropertyInstance> initialProperties,
bool accumulateProperties)
{
foreach (ToolsetPropertyDefinition toolsVersion in ToolsVersions)
{
// If there's already an existing toolset, it's of higher precedence, so
// don't even bother to read this toolset in.
if (!toolsets.ContainsKey(toolsVersion.Name))
{
// We clone here because we don't want to interfere with the evaluation
// of subsequent Toolsets; otherwise, properties found during the evaluation
// of this Toolset would be persisted in initialProperties and appear
// to later Toolsets as Global or Environment properties from the Engine.
PropertyDictionary<ProjectPropertyInstance> initialPropertiesClone = new PropertyDictionary<ProjectPropertyInstance>(initialProperties);
Toolset toolset = ReadToolset(toolsVersion, globalProperties, initialPropertiesClone, accumulateProperties);
FileClassifier.Shared.RegisterFrameworkLocations(p =>
initialPropertiesClone.GetProperty(p)?.EvaluatedValue);
if (toolset != null)
{
toolsets[toolset.ToolsVersion] = toolset;
}
}
}
}
/// <summary>
/// Reads the settings for a specified tools version
/// </summary>
private Toolset ReadToolset(
ToolsetPropertyDefinition toolsVersion,
PropertyDictionary<ProjectPropertyInstance> globalProperties,
PropertyDictionary<ProjectPropertyInstance> initialProperties,
bool accumulateProperties)
{
// Initial properties is the set of properties we're going to use to expand property expressions like $(foo)
// in the values we read out of the registry or config file. We'll add to it as we pick up properties (including binpath)
// from the registry or config file, so that properties there can be referenced in values below them.
// After processing all the properties, we don't need initialProperties anymore.
string toolsPath = null;
string binPath = null;
PropertyDictionary<ProjectPropertyInstance> properties = new PropertyDictionary<ProjectPropertyInstance>();
IEnumerable<ToolsetPropertyDefinition> rawProperties = GetPropertyDefinitions(toolsVersion.Name);
Expander<ProjectPropertyInstance, ProjectItemInstance> expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(initialProperties, FileSystems.Default);
foreach (ToolsetPropertyDefinition property in rawProperties)
{
EvaluateAndSetProperty(property, properties, globalProperties, initialProperties, accumulateProperties, ref toolsPath, ref binPath, ref expander);
}
Dictionary<string, SubToolset> subToolsets = new Dictionary<string, SubToolset>(StringComparer.OrdinalIgnoreCase);
IEnumerable<string> subToolsetVersions = GetSubToolsetVersions(toolsVersion.Name);
foreach (string subToolsetVersion in subToolsetVersions)
{
string subToolsetToolsPath = null;
string subToolsetBinPath = null;
IEnumerable<ToolsetPropertyDefinition> rawSubToolsetProperties = GetSubToolsetPropertyDefinitions(toolsVersion.Name, subToolsetVersion);
PropertyDictionary<ProjectPropertyInstance> subToolsetProperties = new PropertyDictionary<ProjectPropertyInstance>();
// If we have a sub-toolset, any values defined here will override the toolset properties.
foreach (ToolsetPropertyDefinition property in rawSubToolsetProperties)
{
EvaluateAndSetProperty(property, subToolsetProperties, globalProperties, initialProperties, false /* do not ever accumulate sub-toolset properties */, ref subToolsetToolsPath, ref subToolsetBinPath, ref expander);
}
if (subToolsetToolsPath != null || subToolsetBinPath != null)
{
InvalidToolsetDefinitionException.Throw("MSBuildToolsPathNotSupportedInSubToolsets", toolsVersion.Name, toolsVersion.Source.LocationString, subToolsetVersion);
}
subToolsets[subToolsetVersion] = new SubToolset(subToolsetVersion, subToolsetProperties);
}
// All tools versions must specify a value for MSBuildToolsPath (or MSBuildBinPath)
if (String.IsNullOrEmpty(toolsPath) && String.IsNullOrEmpty(binPath))
{
return null;
}
// If both MSBuildBinPath and MSBuildToolsPath are present, they must be the same
if (toolsPath != null && binPath != null && !toolsPath.Equals(binPath, StringComparison.OrdinalIgnoreCase))
{
InvalidToolsetDefinitionException.Throw("ConflictingValuesOfMSBuildToolsPath", toolsVersion.Name, toolsVersion.Source.LocationString);
}
Toolset toolset = null;
try
{
var importSearchPathsTable = GetProjectImportSearchPathsTable(toolsVersion.Name, NativeMethodsShared.GetOSNameForExtensionsPath());
toolset = new Toolset(toolsVersion.Name, toolsPath ?? binPath, properties, _environmentProperties, globalProperties, subToolsets, MSBuildOverrideTasksPath, DefaultOverrideToolsVersion, importSearchPathsTable);
}
catch (ArgumentException e)
{
InvalidToolsetDefinitionException.Throw("ErrorCreatingToolset", toolsVersion.Name, e.Message);
}
return toolset;
}
/// <summary>
/// Processes a particular ToolsetPropertyDefinition into the correct value and location in the initial and/or final property set.
/// </summary>
/// <param name="property">The ToolsetPropertyDefinition being analyzed.</param>
/// <param name="properties">The final set of properties that we wish this toolset property to be added to. </param>
/// <param name="globalProperties">The global properties, used for expansion and to make sure none are overridden.</param>
/// <param name="initialProperties">The initial properties, used for expansion and added to if "accumulateProperties" is true.</param>
/// <param name="accumulateProperties">If "true", we add this property to the initialProperties dictionary, as well, so that properties later in the toolset can use this value.</param>
/// <param name="toolsPath">If this toolset property is the "MSBuildToolsPath" property, we will return the value in this parameter.</param>
/// <param name="binPath">If this toolset property is the "MSBuildBinPath" property, we will return the value in this parameter.</param>
/// <param name="expander">The expander used to expand the value of the properties. Ref because if we are accumulating the properties, we need to re-create the expander to account for the new property value.</param>
private void EvaluateAndSetProperty(ToolsetPropertyDefinition property, PropertyDictionary<ProjectPropertyInstance> properties, PropertyDictionary<ProjectPropertyInstance> globalProperties, PropertyDictionary<ProjectPropertyInstance> initialProperties, bool accumulateProperties, ref string toolsPath, ref string binPath, ref Expander<ProjectPropertyInstance, ProjectItemInstance> expander)
{
if (String.Equals(property.Name, ReservedPropertyNames.toolsPath, StringComparison.OrdinalIgnoreCase))
{
toolsPath = ExpandPropertyUnescaped(property, expander);
toolsPath = ExpandRelativePathsRelativeToExeLocation(toolsPath);
if (accumulateProperties)
{
SetProperty(
new ToolsetPropertyDefinition(ReservedPropertyNames.toolsPath, toolsPath, property.Source),
initialProperties,
globalProperties);
}
}
else if (String.Equals(property.Name, ReservedPropertyNames.binPath, StringComparison.OrdinalIgnoreCase))
{
binPath = ExpandPropertyUnescaped(property, expander);
binPath = ExpandRelativePathsRelativeToExeLocation(binPath);
if (accumulateProperties)
{
SetProperty(
new ToolsetPropertyDefinition(ReservedPropertyNames.binPath, binPath, property.Source),
initialProperties,
globalProperties);
}
}
else if (ReservedPropertyNames.IsReservedProperty(property.Name))
{
// We don't allow toolsets to define reserved properties
string baseMessage = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CannotModifyReservedProperty", property.Name);
InvalidToolsetDefinitionException.Throw("InvalidPropertyNameInToolset", property.Name, property.Source.LocationString, baseMessage);
}
else
{
// It's an arbitrary property
property.Value = ExpandPropertyUnescaped(property, expander);
SetProperty(property, properties, globalProperties);
if (accumulateProperties)
{
SetProperty(property, initialProperties, globalProperties);
}
}
if (accumulateProperties)
{
expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(initialProperties, FileSystems.Default);
}
}
/// <summary>
/// Expands the given unexpanded property expression using the properties in the
/// given expander.
/// </summary>
private string ExpandPropertyUnescaped(ToolsetPropertyDefinition property, Expander<ProjectPropertyInstance, ProjectItemInstance> expander)
{
try
{
return expander.ExpandIntoStringAndUnescape(property.Value, ExpanderOptions.ExpandProperties, property.Source);
}
catch (InvalidProjectFileException ex)
{
InvalidToolsetDefinitionException.Throw(ex, "ErrorEvaluatingToolsetPropertyExpression", property.Value, property.Source.LocationString, ex.BaseMessage);
}
return string.Empty;
}
/// <summary>
/// Sets the given property in the given property group.
/// </summary>
private void SetProperty(ToolsetPropertyDefinition property, PropertyDictionary<ProjectPropertyInstance> propertyGroup, PropertyDictionary<ProjectPropertyInstance> globalProperties)
{
try
{
// Global properties cannot be overwritten
if (globalProperties[property.Name] == null)
{
propertyGroup.Set(ProjectPropertyInstance.Create(property.Name, EscapingUtilities.UnescapeAll(property.Value), true /* may be reserved */, false /* not immutable */));
}
}
catch (ArgumentException ex)
{
InvalidToolsetDefinitionException.Throw(ex, "InvalidPropertyNameInToolset", property.Name, property.Source.LocationString, ex.Message);
}
}
/// <summary>
/// Given a path, de-relativizes it using the location of the currently
/// executing .exe as the base directory. For example, the path "..\foo"
/// becomes "c:\windows\microsoft.net\framework\foo" if the current exe is
/// "c:\windows\microsoft.net\framework\v3.5.1234\msbuild.exe".
/// If the path is not relative, it is returned without modification.
/// If the path is invalid, it is returned without modification.
/// </summary>
private string ExpandRelativePathsRelativeToExeLocation(string path)
{
try
{
// Trim, because we don't want to do anything with empty values
// (those should cause an error)
string trimmedValue = path.Trim();
if (trimmedValue.Length > 0 && !Path.IsPathRooted(trimmedValue))
{
path = Path.GetFullPath(
Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, trimmedValue));
}
}
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
{
// This means that the path looked relative, but was an invalid path. In this case, we'll
// just not expand it, and carry on - to be consistent with what happens when there's a
// non-relative bin path with invalid characters. The problem will be detected later when
// it's used in a project file.
}
return path;
}
}
/// <summary>
/// struct representing a reference to MSBuildExtensionsPath* property
/// </summary>
internal struct MSBuildExtensionsPathReferenceKind
{
/// <summary>
/// MSBuildExtensionsPathReferenceKind instance for property named "MSBuildExtensionsPath"
/// </summary>
public static readonly MSBuildExtensionsPathReferenceKind Default = new MSBuildExtensionsPathReferenceKind("MSBuildExtensionsPath");
/// <summary>
/// MSBuildExtensionsPathReferenceKind instance for property named "MSBuildExtensionsPath32"
/// </summary>
public static readonly MSBuildExtensionsPathReferenceKind Path32 = new MSBuildExtensionsPathReferenceKind("MSBuildExtensionsPath32");
/// <summary>
/// MSBuildExtensionsPathReferenceKind instance for property named "MSBuildExtensionsPath64"
/// </summary>
public static readonly MSBuildExtensionsPathReferenceKind Path64 = new MSBuildExtensionsPathReferenceKind("MSBuildExtensionsPath64");
/// <summary>
/// MSBuildExtensionsPathReferenceKind instance representing no MSBuildExtensionsPath* property reference
/// </summary>
public static readonly MSBuildExtensionsPathReferenceKind None = new MSBuildExtensionsPathReferenceKind(String.Empty);
private MSBuildExtensionsPathReferenceKind(string value)
{
StringRepresentation = value;
}
/// <summary>
/// String representation of the property reference - eg. "MSBuildExtensionsPath32"
/// </summary>
public string StringRepresentation { get; private set; }
/// <summary>
/// Returns the corresponding property name - eg. "$(MSBuildExtensionsPath32)"
/// </summary>
public readonly string MSBuildPropertyName => String.Format($"$({StringRepresentation})");
/// <summary>
/// Tries to find a reference to MSBuildExtensionsPath* property in the given string
/// </summary>
public static MSBuildExtensionsPathReferenceKind FindIn(string expression)
{
const string PathBase = "$(MSBuildExtensionsPath";
int pos = expression.IndexOf(PathBase, StringComparison.Ordinal);
if (pos >= 0)
{
ReadOnlySpan<char> remainder = expression.AsSpan(pos + PathBase.Length);
if (remainder.StartsWith(")".AsSpan()))
{
return Default;
}
else if (remainder.StartsWith("32)".AsSpan()))
{
return Path32;
}
else if (remainder.StartsWith("64)".AsSpan()))
{
return Path64;
}
}
return None;
}
}
}