-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathProjectInterpretation.cs
More file actions
657 lines (557 loc) · 35.3 KB
/
Copy pathProjectInterpretation.cs
File metadata and controls
657 lines (557 loc) · 35.3 KB
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
// 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.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Xml;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
#nullable disable
namespace Microsoft.Build.Graph
{
internal sealed class ProjectInterpretation
{
private const string FullPathMetadataName = "FullPath";
private const string ToolsVersionMetadataName = "ToolsVersion";
private const string SetConfigurationMetadataName = "SetConfiguration";
private const string SetPlatformMetadataName = "SetPlatform";
private const string SetTargetFrameworkMetadataName = "SetTargetFramework";
private const string GlobalPropertiesToRemoveMetadataName = "GlobalPropertiesToRemove";
private const string ProjectReferenceTargetIsOuterBuildMetadataName = "OuterBuild";
private const string InnerBuildReferenceItemName = "_ProjectSelfReference";
internal static string TransitiveReferenceItemName = "_TransitiveProjectReference";
internal const string AddTransitiveProjectReferencesInStaticGraphPropertyName = "AddTransitiveProjectReferencesInStaticGraph";
private const string PlatformLookupTableMetadataName = "PlatformLookupTable";
private const string PlatformMetadataName = "Platform";
private const string PlatformsMetadataName = "Platforms";
private const string EnableDynamicPlatformResolutionPropertyName = "EnableDynamicPlatformResolution";
private const string OverridePlatformNegotiationValue = "OverridePlatformNegotiationValue";
private const string ShouldUnsetParentConfigurationAndPlatformPropertyName = "ShouldUnsetParentConfigurationAndPlatform";
private const string ProjectMetadataName = "Project";
private const string ConfigurationMetadataName = "Configuration";
private static readonly char[] PropertySeparator = MSBuildConstants.SemicolonChar;
public static ProjectInterpretation Instance = new ProjectInterpretation();
private static readonly ImmutableList<GlobalPropertiesModifier> ModifierForNonMultitargetingNodes = [(GlobalPropertiesModifier)ProjectReferenceGlobalPropertiesModifier];
internal enum ProjectType
{
OuterBuild,
InnerBuild,
NonMultitargeting,
}
internal readonly record struct ReferenceInfo(ConfigurationMetadata ReferenceConfiguration, ProjectItemInstance ProjectReferenceItem);
private readonly struct TargetSpecification
{
public TargetSpecification(string target, bool skipIfNonexistent)
{
// Verify that if this target is skippable then it equals neither
// ".default" nor ".projectReferenceTargetsOrDefaultTargets".
Assumed.True(!skipIfNonexistent || (!target.Equals(MSBuildConstants.DefaultTargetsMarker)
&& !target.Equals(MSBuildConstants.ProjectReferenceTargetsOrDefaultTargetsMarker)), $"{target} cannot be marked as SkipNonexistentTargets");
Target = target;
SkipIfNonexistent = skipIfNonexistent;
}
public string Target { get; }
public bool SkipIfNonexistent { get; }
}
public IEnumerable<ReferenceInfo> GetReferences(ProjectGraphNode projectGraphNode, ProjectCollection projectCollection, ProjectGraph.ProjectInstanceFactoryFunc projectInstanceFactory, ProjectGraphMode graphMode)
{
IEnumerable<ProjectItemInstance> projectReferenceItems;
IEnumerable<GlobalPropertiesModifier> globalPropertiesModifiers = null;
ProjectInstance requesterInstance = projectGraphNode.ProjectInstance;
switch (projectGraphNode.ProjectType)
{
case ProjectType.OuterBuild:
projectReferenceItems = ConstructInnerBuildReferences(requesterInstance);
break;
case ProjectType.InnerBuild:
globalPropertiesModifiers = ModifierForNonMultitargetingNodes.Add((parts, reference) => parts.AddPropertyToUndefine(GetInnerBuildPropertyName(requesterInstance)));
projectReferenceItems = requesterInstance.GetItems(ItemTypeNames.ProjectReference);
break;
case ProjectType.NonMultitargeting:
globalPropertiesModifiers = ModifierForNonMultitargetingNodes;
projectReferenceItems = requesterInstance.GetItems(ItemTypeNames.ProjectReference);
break;
default:
throw new ArgumentOutOfRangeException();
}
SolutionConfiguration solutionConfiguration = null;
string solutionConfigurationXml = requesterInstance.GetEngineRequiredPropertyValue(SolutionProjectGenerator.CurrentSolutionConfigurationContents);
if (!string.IsNullOrWhiteSpace(solutionConfigurationXml))
{
solutionConfiguration = new SolutionConfiguration(solutionConfigurationXml);
}
foreach (ProjectItemInstance projectReferenceItem in projectReferenceItems)
{
if (!String.IsNullOrEmpty(projectReferenceItem.GetMetadataValue(ToolsVersionMetadataName)))
{
throw new InvalidOperationException(
String.Format(
CultureInfo.InvariantCulture,
ResourceUtilities.GetResourceString(
"ProjectGraphDoesNotSupportProjectReferenceWithToolset"),
projectReferenceItem.EvaluatedInclude,
requesterInstance.FullPath));
}
string projectReferenceFullPath = projectReferenceItem.GetMetadataValue(FullPathMetadataName);
bool enableDynamicPlatformResolution = ConversionUtilities.ValidBooleanTrue(requesterInstance.GetEngineRequiredPropertyValue(EnableDynamicPlatformResolutionPropertyName));
PropertyDictionary<ProjectPropertyInstance> referenceGlobalProperties = GetGlobalPropertiesForItem(
projectReferenceItem,
requesterInstance.GlobalPropertiesDictionary,
// Only allow reuse in scenarios where we will not mutate the collection.
// TODO: Should these mutations be moved to globalPropertiesModifiers in the future?
allowCollectionReuse: solutionConfiguration == null && !enableDynamicPlatformResolution,
globalPropertiesModifiers);
bool configurationDefined = false;
// Match what AssignProjectConfiguration does to resolve project references.
if (solutionConfiguration != null)
{
string projectGuid = projectReferenceItem.GetMetadataValue(ProjectMetadataName);
if (solutionConfiguration.TryGetProjectByGuid(projectGuid, out XmlElement projectElement)
|| solutionConfiguration.TryGetProjectByAbsolutePath(projectReferenceFullPath, out projectElement))
{
// Note: AssignProjectConfiguration sets various metadata on the ProjectReference item, but ultimately it just translates to the Configuration and Platform global properties on the MSBuild task.
string projectConfiguration = projectElement.InnerText;
string[] configurationPlatformParts = projectConfiguration.Split(SolutionConfiguration.ConfigPlatformSeparator[0]);
SetProperty(referenceGlobalProperties, ConfigurationMetadataName, configurationPlatformParts[0]);
if (configurationPlatformParts.Length > 1)
{
SetProperty(referenceGlobalProperties, PlatformMetadataName, configurationPlatformParts[1]);
}
else
{
referenceGlobalProperties.Remove(PlatformMetadataName);
}
configurationDefined = true;
}
else
{
// Note: ShouldUnsetParentConfigurationAndPlatform defaults to true in the AssignProjectConfiguration target when building a solution, so check that it's not false instead of checking that it's true.
bool shouldUnsetParentConfigurationAndPlatform = !ConversionUtilities.ValidBooleanFalse(requesterInstance.GetEngineRequiredPropertyValue(ShouldUnsetParentConfigurationAndPlatformPropertyName));
if (shouldUnsetParentConfigurationAndPlatform)
{
referenceGlobalProperties.Remove(ConfigurationMetadataName);
referenceGlobalProperties.Remove(PlatformMetadataName);
}
else
{
configurationDefined = true;
}
}
}
// Note: Dynamic platform resolution is not enabled for sln-based builds,
// unless the project isn't known to the solution.
if (enableDynamicPlatformResolution && !configurationDefined && !projectReferenceItem.HasMetadata(SetPlatformMetadataName))
{
string requesterPlatform = requesterInstance.GetEngineRequiredPropertyValue("Platform");
string requesterPlatformLookupTable = requesterInstance.GetEngineRequiredPropertyValue("PlatformLookupTable");
var projectInstance = projectInstanceFactory(
projectReferenceFullPath,
null, // Platform negotiation requires an evaluation with no global properties first
projectCollection);
string overridePlatformNegotiationMetadataValue = projectReferenceItem.GetMetadataValue(OverridePlatformNegotiationValue);
var selectedPlatform = PlatformNegotiation.GetNearestPlatform(overridePlatformNegotiationMetadataValue, projectInstance.GetEngineRequiredPropertyValue(PlatformMetadataName), projectInstance.GetEngineRequiredPropertyValue(PlatformsMetadataName), projectInstance.GetEngineRequiredPropertyValue(PlatformLookupTableMetadataName), requesterInstance.GetEngineRequiredPropertyValue(PlatformLookupTableMetadataName), projectInstance.FullPath, requesterInstance.GetEngineRequiredPropertyValue(PlatformMetadataName));
if (selectedPlatform.Equals(String.Empty))
{
referenceGlobalProperties.Remove(PlatformMetadataName);
}
else
{
SetProperty(referenceGlobalProperties, PlatformMetadataName, selectedPlatform);
}
}
var referenceConfig = new ConfigurationMetadata(projectReferenceFullPath, referenceGlobalProperties);
// When in Full graph mode, enumerate all target frameworks regardless of SetTargetFramework
if (graphMode == ProjectGraphMode.Full && projectReferenceItem.HasMetadata(SetTargetFrameworkMetadataName))
{
// Evaluate the referenced project with no global properties to discover all its target frameworks
var projectInstanceForDiscovery = projectInstanceFactory(
projectReferenceFullPath,
null,
projectCollection);
string targetFrameworksValue = projectInstanceForDiscovery.GetPropertyValue(PropertyNames.TargetFrameworks);
// If the project has multiple target frameworks, yield the outer build only
// The normal ConstructInnerBuildReferences logic will discover and create all inner builds
if (!string.IsNullOrWhiteSpace(targetFrameworksValue))
{
// Create outer build reference by removing TargetFramework from the global properties
// This allows all inner builds to be discovered while respecting other modifiers
var outerBuildProperties = new PropertyDictionary<ProjectPropertyInstance>(referenceGlobalProperties);
outerBuildProperties.Remove(PropertyNames.TargetFramework);
// Yield only the outer build (no TargetFramework override)
// The ConstructInnerBuildReferences logic will discover and create the inner builds
yield return new ReferenceInfo(new ConfigurationMetadata(projectReferenceFullPath, outerBuildProperties), projectReferenceItem);
continue;
}
else
{
string targetFrameworkValue = projectInstanceForDiscovery.GetPropertyValue(PropertyNames.TargetFramework);
// Single target framework project - yield it without SetTargetFramework constraint
if (!string.IsNullOrWhiteSpace(targetFrameworkValue))
{
// Remove TargetFramework that was set by SetTargetFramework metadata
var singleTargetProperties = new PropertyDictionary<ProjectPropertyInstance>(referenceGlobalProperties);
singleTargetProperties.Remove(PropertyNames.TargetFramework);
yield return new ReferenceInfo(new ConfigurationMetadata(projectReferenceFullPath, singleTargetProperties), projectReferenceItem);
continue;
}
}
}
yield return new ReferenceInfo(referenceConfig, projectReferenceItem);
static void SetProperty(PropertyDictionary<ProjectPropertyInstance> properties, string propertyName, string propertyValue)
{
ProjectPropertyInstance propertyInstance = ProjectPropertyInstance.Create(propertyName, propertyValue);
properties[propertyName] = propertyInstance;
}
}
}
internal static string GetInnerBuildPropertyValue(ProjectInstance project)
{
return project.GetPropertyValue(GetInnerBuildPropertyName(project));
}
internal static string GetInnerBuildPropertyName(ProjectInstance project)
{
return project.GetPropertyValue(PropertyNames.InnerBuildProperty);
}
internal static string GetInnerBuildPropertyValues(ProjectInstance project)
{
return project.GetPropertyValue(project.GetPropertyValue(PropertyNames.InnerBuildPropertyValues));
}
internal static ProjectType GetProjectType(ProjectInstance project)
{
var isOuterBuild = String.IsNullOrWhiteSpace(GetInnerBuildPropertyValue(project)) && !String.IsNullOrWhiteSpace(GetInnerBuildPropertyValues(project));
var isInnerBuild = !String.IsNullOrWhiteSpace(GetInnerBuildPropertyValue(project));
Assumed.False((isOuterBuild && isInnerBuild), $"A project cannot be an outer and inner build at the same time: ${project.FullPath}");
return isOuterBuild
? ProjectType.OuterBuild
: isInnerBuild
? ProjectType.InnerBuild
: ProjectType.NonMultitargeting;
}
/// <summary>
/// To avoid calling nuget at graph construction time, the graph is initially constructed with nodes referencing outer build nodes which in turn
/// reference inner build nodes. However at build time, the inner builds are referenced directly by the nodes referencing the outer build.
/// Change the graph to mimic this behaviour.
/// Example: Node -> Outer -> Inner go to: Node -> Outer; Node->Inner; Outer -> Inner. Inner build edges get added to Node.
/// </summary>
public void AddInnerBuildEdges(Dictionary<ConfigurationMetadata, ParsedProject> allNodes, GraphBuilder graphBuilder)
{
foreach (KeyValuePair<ConfigurationMetadata, ParsedProject> node in allNodes)
{
ProjectGraphNode outerBuild = node.Value.GraphNode;
if (outerBuild.ProjectType == ProjectType.OuterBuild && outerBuild.ReferencingProjects.Count != 0)
{
foreach (ProjectGraphNode innerBuild in outerBuild.ProjectReferences)
{
foreach (ProjectGraphNode outerBuildReferencingProject in outerBuild.ReferencingProjects)
{
// Which edge should be used to connect the outerBuildReferencingProject to the inner builds?
// Decided to use the outerBuildBuildReferencingProject -> outerBuild edge in order to preserve any extra metadata
// information that may be present on the edge, like the "Targets" metadata which specifies what
// targets to call on the references.
ProjectItemInstance newInnerBuildEdge = graphBuilder.Edges[(outerBuildReferencingProject, outerBuild)];
if (outerBuildReferencingProject.ProjectReferences.Contains(innerBuild))
{
Assumed.True(graphBuilder.Edges[(outerBuildReferencingProject, innerBuild)]
.ItemType.Equals(
TransitiveReferenceItemName,
StringComparison.OrdinalIgnoreCase), "Only transitive references may reference inner builds that got generated by outer builds");
outerBuildReferencingProject.RemoveReference(innerBuild, graphBuilder.Edges);
}
outerBuildReferencingProject.AddProjectReference(innerBuild, newInnerBuildEdge, graphBuilder.Edges);
}
}
}
}
}
private static IEnumerable<ProjectItemInstance> ConstructInnerBuildReferences(ProjectInstance outerBuild)
{
var globalPropertyName = GetInnerBuildPropertyName(outerBuild);
var globalPropertyValues = GetInnerBuildPropertyValues(outerBuild);
Assumed.False(string.IsNullOrWhiteSpace(globalPropertyName), "Must have an inner build property");
Assumed.False(string.IsNullOrWhiteSpace(globalPropertyValues), "Must have values for the inner build property");
foreach (var globalPropertyValue in ExpressionShredder.SplitSemiColonSeparatedList(globalPropertyValues))
{
yield return new ProjectItemInstance(
project: outerBuild,
itemType: InnerBuildReferenceItemName,
includeEscaped: outerBuild.FullPath,
directMetadata: [new KeyValuePair<string, string>(ItemMetadataNames.PropertiesMetadataName, $"{globalPropertyName}={globalPropertyValue}")],
definingFileEscaped: outerBuild.FullPath);
}
}
/// <summary>
/// Gets the effective global properties for a project reference item.
/// </summary>
/// <remarks>
/// The behavior of this method should match the logic in the SDK
/// </remarks>
private static GlobalPropertyPartsForMSBuildTask ProjectReferenceGlobalPropertiesModifier(
GlobalPropertyPartsForMSBuildTask defaultParts,
ProjectItemInstance projectReference)
{
// ProjectReference defines yet another metadata name containing properties to undefine. Merge it in if non empty.
var globalPropertiesToRemove = SplitPropertyNames(projectReference.GetMetadataValue(GlobalPropertiesToRemoveMetadataName));
var newUndefineProperties = defaultParts.UndefineProperties;
newUndefineProperties = newUndefineProperties.AddRange(defaultParts.UndefineProperties);
newUndefineProperties = newUndefineProperties.AddRange(globalPropertiesToRemove);
newUndefineProperties.Add("InnerBuildProperty");
var newProperties = defaultParts.Properties;
// The properties on the project reference supersede the ones from the MSBuild task instead of appending.
if (newProperties.Count == 0)
{
// TODO: Mimic AssignProjectConfiguration's behavior for determining the values for these.
var setConfigurationString = projectReference.GetMetadataValue(SetConfigurationMetadataName);
var setPlatformString = projectReference.GetMetadataValue(SetPlatformMetadataName);
var setTargetFrameworkString = projectReference.GetMetadataValue(SetTargetFrameworkMetadataName);
if (!String.IsNullOrEmpty(setConfigurationString) || !String.IsNullOrEmpty(setPlatformString) || !String.IsNullOrEmpty(setTargetFrameworkString))
{
newProperties = SplitPropertyNameValuePairs(
ItemMetadataNames.PropertiesMetadataName,
$"{setConfigurationString};{setPlatformString};{setTargetFrameworkString}").ToImmutableDictionary();
}
}
return new GlobalPropertyPartsForMSBuildTask(newProperties, defaultParts.AdditionalProperties, newUndefineProperties);
}
private readonly struct GlobalPropertyPartsForMSBuildTask
{
public ImmutableDictionary<string, string> Properties { get; }
public ImmutableDictionary<string, string> AdditionalProperties { get; }
public ImmutableList<string> UndefineProperties { get; }
public GlobalPropertyPartsForMSBuildTask(
ImmutableDictionary<string, string> properties,
ImmutableDictionary<string, string> additionalProperties,
ImmutableList<string> undefineProperties)
{
Properties = properties;
AdditionalProperties = additionalProperties;
UndefineProperties = undefineProperties;
}
public bool AllEmpty()
{
return Properties.Count == 0 && AdditionalProperties.Count == 0 && UndefineProperties.Count == 0;
}
public GlobalPropertyPartsForMSBuildTask AddPropertyToUndefine(string propertyToUndefine)
{
return new GlobalPropertyPartsForMSBuildTask(Properties, AdditionalProperties, UndefineProperties.Add(propertyToUndefine));
}
}
private delegate GlobalPropertyPartsForMSBuildTask GlobalPropertiesModifier(GlobalPropertyPartsForMSBuildTask defaultParts, ProjectItemInstance projectReference);
/// <summary>
/// Gets the effective global properties for an item that will get passed to <see cref="MSBuild.Projects"/>.
/// </summary>
/// <remarks>
/// The behavior of this method matches the hardcoded behaviour of the msbuild task
/// and the <paramref name="globalPropertyModifiers"/> parameter can contain other mutations done at build time in targets / tasks
/// </remarks>
private static PropertyDictionary<ProjectPropertyInstance> GetGlobalPropertiesForItem(
ProjectItemInstance projectReference,
PropertyDictionary<ProjectPropertyInstance> requesterGlobalProperties,
bool allowCollectionReuse,
IEnumerable<GlobalPropertiesModifier> globalPropertyModifiers)
{
Assumed.NotNull(projectReference);
ArgumentNullException.ThrowIfNull(requesterGlobalProperties);
var properties = SplitPropertyNameValuePairs(ItemMetadataNames.PropertiesMetadataName, projectReference.GetMetadataValue(ItemMetadataNames.PropertiesMetadataName));
var additionalProperties = SplitPropertyNameValuePairs(ItemMetadataNames.AdditionalPropertiesMetadataName, projectReference.GetMetadataValue(ItemMetadataNames.AdditionalPropertiesMetadataName));
var undefineProperties = SplitPropertyNames(projectReference.GetMetadataValue(ItemMetadataNames.UndefinePropertiesMetadataName));
var defaultParts = new GlobalPropertyPartsForMSBuildTask(properties.ToImmutableDictionary(), additionalProperties.ToImmutableDictionary(), undefineProperties.ToImmutableList());
var globalPropertyParts = globalPropertyModifiers?.Aggregate(defaultParts, (currentProperties, modifier) => modifier(currentProperties, projectReference)) ?? defaultParts;
if (globalPropertyParts.AllEmpty() && allowCollectionReuse)
{
return requesterGlobalProperties;
}
// Make a copy to avoid mutating the requester
var globalProperties = new PropertyDictionary<ProjectPropertyInstance>(requesterGlobalProperties);
// Append and remove properties as specified by the various metadata
MergeIntoPropertyDictionary(globalProperties, globalPropertyParts.Properties);
MergeIntoPropertyDictionary(globalProperties, globalPropertyParts.AdditionalProperties);
RemoveFromPropertyDictionary(globalProperties, globalPropertyParts.UndefineProperties);
return globalProperties;
}
private static void MergeIntoPropertyDictionary(
PropertyDictionary<ProjectPropertyInstance> destination,
IReadOnlyDictionary<string, string> source)
{
foreach (var pair in source)
{
destination[pair.Key] = ProjectPropertyInstance.Create(pair.Key, pair.Value);
}
}
private static IReadOnlyDictionary<string, string> SplitPropertyNameValuePairs(string syntaxName, string propertyNameAndValuesString)
{
if (String.IsNullOrEmpty(propertyNameAndValuesString))
{
return ImmutableDictionary<string, string>.Empty;
}
if (PropertyParser.GetTableWithEscaping(
null,
null,
null,
propertyNameAndValuesString.Split(PropertySeparator, StringSplitOptions.RemoveEmptyEntries),
out var propertiesTable))
{
return propertiesTable;
}
throw new InvalidProjectFileException(
String.Format(
CultureInfo.InvariantCulture,
ResourceUtilities.GetResourceString("General.InvalidPropertyError"),
syntaxName,
propertyNameAndValuesString));
}
private static IReadOnlyCollection<string> SplitPropertyNames(string propertyNamesString)
{
if (String.IsNullOrEmpty(propertyNamesString))
{
return ImmutableArray<string>.Empty;
}
return propertyNamesString.Split(PropertySeparator, StringSplitOptions.RemoveEmptyEntries);
}
private static void RemoveFromPropertyDictionary(
PropertyDictionary<ProjectPropertyInstance> properties,
IReadOnlyCollection<string> propertyNamesToRemove)
{
foreach (var propertyName in propertyNamesToRemove)
{
properties.Remove(propertyName);
}
}
public readonly struct TargetsToPropagate
{
// Outer build targets occupy the first _outerBuildTargetCount entries of _allTargets,
// followed by inner build (non-outer-build) targets. Non-multitargeting projects use
// the full array because they act as both outer and inner builds.
private readonly TargetSpecification[] _allTargets;
private readonly int _outerBuildTargetCount;
private TargetsToPropagate(List<TargetSpecification> outerBuildTargets, List<TargetSpecification> nonOuterBuildTargets)
{
int outerCount = outerBuildTargets?.Count ?? 0;
int innerCount = nonOuterBuildTargets?.Count ?? 0;
int total = outerCount + innerCount;
_outerBuildTargetCount = outerCount;
if (total == 0)
{
_allTargets = [];
return;
}
// Single backing array; each source list is copied exactly once.
TargetSpecification[] combined = new TargetSpecification[total];
if (outerCount > 0)
{
outerBuildTargets.CopyTo(combined, 0);
}
if (innerCount > 0)
{
nonOuterBuildTargets.CopyTo(combined, outerCount);
}
_allTargets = combined;
}
/// <summary>
/// Given a project and a set of entry targets the project would get called with,
/// parse the project's project reference target specification and compute how the target would call its references.
///
/// The calling code should then call <see cref="GetApplicableTargetsForReference"/> for each of the project's references
/// to get the concrete targets for each reference.
/// </summary>
/// <param name="project">Project containing the PRT protocol</param>
/// <param name="entryTargets">Targets with which <paramref name="project"/> will get called</param>
/// <returns></returns>
public static TargetsToPropagate FromProjectAndEntryTargets(ProjectInstance project, string[] entryTargets)
{
List<TargetSpecification> targetsForOuterBuild = null;
List<TargetSpecification> targetsForInnerBuild = null;
ICollection<ProjectItemInstance> projectReferenceTargets = project.GetItems(ItemTypeNames.ProjectReferenceTargets);
foreach (string entryTarget in entryTargets)
{
foreach (ProjectItemInstance projectReferenceTarget in projectReferenceTargets)
{
if (projectReferenceTarget.EvaluatedInclude.Equals(entryTarget, StringComparison.OrdinalIgnoreCase))
{
string targetsMetadataValue = projectReferenceTarget.GetMetadataValue(ItemMetadataNames.ProjectReferenceTargetsMetadataName);
bool skipNonexistentTargets = MSBuildStringIsTrue(projectReferenceTarget.GetMetadataValue("SkipNonexistentTargets"));
bool targetsAreForOuterBuild = MSBuildStringIsTrue(projectReferenceTarget.GetMetadataValue(ProjectReferenceTargetIsOuterBuildMetadataName));
// Append directly into the destination list. SemiColonTokenizer is a
// struct enumerator, so foreach avoids boxing it through IEnumerable<string>.
// This skips the LINQ Select state machine, its captured-locals closure,
// and an intermediate TargetSpecification[] vs the previous
// .Select(...).ToArray() + AddRange pattern.
ref List<TargetSpecification> dest = ref targetsAreForOuterBuild ? ref targetsForOuterBuild : ref targetsForInnerBuild;
foreach (string target in ExpressionShredder.SplitSemiColonSeparatedList(targetsMetadataValue))
{
dest ??= [];
dest.Add(new TargetSpecification(target, skipNonexistentTargets));
}
}
}
}
return new TargetsToPropagate(targetsForOuterBuild, targetsForInnerBuild);
}
public string[] GetApplicableTargetsForReference(ProjectGraphNode projectGraphNode)
{
int end = projectGraphNode.ProjectType switch
{
ProjectType.OuterBuild => _outerBuildTargetCount,
ProjectType.InnerBuild => _allTargets.Length,
ProjectType.NonMultitargeting => _allTargets.Length,
_ => throw new ArgumentOutOfRangeException(),
};
if (end == 0)
{
return [];
}
// Keep targets that are non-skippable or that exist but are skippable. The common
// case is "every target passes the filter", so pre-size a string[] and trim only
// if something was actually skipped.
string[] result = new string[end];
int writeIndex = 0;
for (int i = 0; i < end; i++)
{
TargetSpecification t = _allTargets[i];
if (!t.SkipIfNonexistent || projectGraphNode.ProjectInstance.Targets.ContainsKey(t.Target))
{
result[writeIndex++] = t.Target;
}
}
if (writeIndex == end)
{
return result;
}
if (writeIndex == 0)
{
return [];
}
Array.Resize(ref result, writeIndex);
return result;
}
}
public bool RequiresTransitiveProjectReferences(ProjectGraphNode projectGraphNode)
{
// Outer builds do not get edges based on ProjectReference or their transitive closure, only inner builds do.
if (projectGraphNode.ProjectType == ProjectType.OuterBuild)
{
return false;
}
ProjectInstance projectInstance = projectGraphNode.ProjectInstance;
// special case for Quickbuild which updates msbuild binaries independent of props/targets. Remove this when all QB repos will have
// migrated to new enough Visual Studio versions whose Microsoft.Managed.After.Targets enable transitive references.
if (string.IsNullOrWhiteSpace(projectInstance.GetEngineRequiredPropertyValue(AddTransitiveProjectReferencesInStaticGraphPropertyName)) &&
MSBuildStringIsTrue(projectInstance.GetEngineRequiredPropertyValue(PropertyNames.UsingMicrosoftNETSdk)) &&
MSBuildStringIsFalse(projectInstance.GetEngineRequiredPropertyValue("DisableTransitiveProjectReferences")))
{
return true;
}
return MSBuildStringIsTrue(
projectInstance.GetEngineRequiredPropertyValue(AddTransitiveProjectReferencesInStaticGraphPropertyName));
}
private static bool MSBuildStringIsTrue(string msbuildString) =>
ConversionUtilities.ConvertStringToBool(msbuildString, nullOrWhitespaceIsFalse: true);
private static bool MSBuildStringIsFalse(string msbuildString) => !MSBuildStringIsTrue(msbuildString);
}
}