1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
4 // </copyright>
5 // <summary>Evaluates a ProjectRootElement into a Project.</summary>
6 //-----------------------------------------------------------------------
7 
8 using System;
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.IO;
12 using System.Linq;
13 using System.Reflection;
14 using System.Text;
15 using Microsoft.Build.Construction;
16 using Microsoft.Build.Shared;
17 using Microsoft.Build.Execution;
18 using ObjectModel = System.Collections.ObjectModel;
19 using Microsoft.Build.Collections;
20 using Microsoft.Build.BackEnd;
21 using System.Globalization;
22 using System.Threading;
23 using Microsoft.Build.BackEnd.Components.Logging;
24 using Microsoft.Build.BackEnd.Logging;
25 using Microsoft.Build.BackEnd.SdkResolution;
26 using Microsoft.Build.Evaluation.Context;
27 #if MSBUILDENABLEVSPROFILING
28 using Microsoft.VisualStudio.Profiler;
29 #endif
30 
31 using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
32 using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames;
33 using Constants = Microsoft.Build.Internal.Constants;
34 using EngineFileUtilities = Microsoft.Build.Internal.EngineFileUtilities;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Framework.Profiler;
37 using Microsoft.Build.Internal;
38 using Microsoft.Build.Utilities;
39 using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
40 using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult;
41 
42 #if (!STANDALONEBUILD)
43 using Microsoft.Internal.Performance;
44 #endif
45 
46 namespace Microsoft.Build.Evaluation
47 {
48     /// <summary>
49     /// Evaluates a ProjectRootElement, updating the fresh Project.Data passed in.
50     /// Handles evaluating conditions, expanding expressions, and building up the
51     /// lists of applicable properties, items, and itemdefinitions, as well as gathering targets and tasks
52     /// and creating a TaskRegistry from the using tasks.
53     /// </summary>
54     /// <typeparam name="P">The type of properties to produce.</typeparam>
55     /// <typeparam name="I">The type of items to produce.</typeparam>
56     /// <typeparam name="M">The type of metadata on those items.</typeparam>
57     /// <typeparam name="D">The type of item definitions to be produced.</typeparam>
58     /// <remarks>
59     /// This class could be improved to do partial (minimal) reevaluation: at present we wipe all state and start over.
60     /// </remarks>
61     internal class Evaluator<P, I, M, D>
62         where P : class, IProperty, IEquatable<P>, IValued
63         where I : class, IItem<M>, IMetadataTable
64         where M : class, IMetadatum
65         where D : class, IItemDefinition<M>
66     {
67         /// <summary>
68         /// Character used to split InitialTargets and DefaultTargets lists
69         /// </summary>
70         private static readonly char[] s_splitter = new char[] { ';' };
71 
72         /// <summary>
73         /// Locals types names. We only have these because 'Built In' has a space,
74         /// else we would use LocalsTypes enum names.
75         /// Note: This should match LocalsTypes enum.
76         /// </summary>
77         private static readonly string[] s_localsTypesNames = new string[]
78         {
79                 "Project",
80                 "Built In",
81                 "Environment",
82                 "Toolset",
83                 "SubToolset",
84                 "Global",
85                 "EvaluateExpression",
86                 "EvaluateCondition",
87                 "ToolsVersion",
88                 "Properties",
89                 "ItemDefinitions",
90                 "Items"
91         };
92 
93         /// <summary>
94         /// Expander for evaluating conditions
95         /// </summary>
96         private readonly Expander<P, I> _expander;
97 
98         /// <summary>
99         /// Data containing the ProjectRootElement to evaluate and the slots for
100         /// items, properties, etc originating from the evaluation.
101         /// </summary>
102         private readonly IEvaluatorData<P, I, M, D> _data;
103 
104         /// <summary>
105         /// List of ProjectItemElement's traversing into imports.
106         /// Gathered during the first pass to avoid traversing again.
107         /// </summary>
108         private readonly List<ProjectItemGroupElement> _itemGroupElements;
109 
110         /// <summary>
111         /// List of ProjectItemDefinitionElement's traversing into imports.
112         /// Gathered during the first pass to avoid traversing again.
113         /// </summary>
114         private readonly List<ProjectItemDefinitionGroupElement> _itemDefinitionGroupElements;
115 
116         /// <summary>
117         /// List of ProjectUsingTaskElement's traversing into imports.
118         /// Gathered during the first pass to avoid traversing again.
119         /// Key is the directory of the file importing the usingTask, which is needed
120         /// to handle any relative paths in the usingTask.
121         /// </summary>
122         private readonly List<Pair<string, ProjectUsingTaskElement>> _usingTaskElements;
123 
124         /// <summary>
125         /// List of ProjectTargetElement's traversing into imports.
126         /// Gathered during the first pass to avoid traversing again.
127         /// </summary>
128         private readonly List<ProjectTargetElement> _targetElements;
129 
130         /// <summary>
131         /// Paths to imports already seen and where they were imported from; used to flag duplicate imports
132         /// </summary>
133         private readonly Dictionary<string, ProjectImportElement> _importsSeen;
134 
135         /// <summary>
136         /// Depth first collection of InitialTargets strings declared in the main
137         /// Project and all its imported files, split on semicolons.
138         /// </summary>
139         private readonly List<string> _initialTargetsList;
140 
141         /// <summary>
142         /// Dictionary of project full paths and a boolean that indicates whether at least one
143         /// of their targets has the "Returns" attribute set.
144         /// </summary>
145         private readonly Dictionary<ProjectRootElement, NGen<bool>> _projectSupportsReturnsAttribute;
146 
147         /// <summary>
148         /// The Project Xml to be evaluated.
149         /// </summary>
150         private readonly ProjectRootElement _projectRootElement;
151 
152         /// <summary>
153         /// The item factory used to create items from Xml.
154         /// </summary>
155         private readonly IItemFactory<I, I> _itemFactory;
156 
157         /// <summary>
158         /// Load settings, such as whether to ignore missing imports.
159         /// </summary>
160         private readonly ProjectLoadSettings _loadSettings;
161 
162         /// <summary>
163         /// The maximum number of nodes to report for evaluation.
164         /// </summary>
165         private readonly int _maxNodeCount;
166 
167         /// <summary>
168         /// This optional ProjectInstance is only exposed when doing debugging. It is not used by the evaluator.
169         /// </summary>
170         private readonly ProjectInstance _projectInstanceIfAnyForDebuggerOnly;
171 
172         /// <summary>
173         /// The <see cref="ISdkResolverService"/> to use.
174         /// </summary>
175         private readonly ISdkResolverService _sdkResolverService;
176 
177         /// <summary>
178         /// The current build submission ID.
179         /// </summary>
180         private readonly int _submissionId;
181 
182         private readonly EvaluationContext _evaluationContext;
183 
184         /// <summary>
185         /// The environment properties with which evaluation should take place.
186         /// </summary>
187         private readonly PropertyDictionary<ProjectPropertyInstance> _environmentProperties;
188 
189         /// <summary>
190         /// The cache to consult for any imports that need loading.
191         /// </summary>
192         private readonly ProjectRootElementCache _projectRootElementCache;
193 
194         /// <summary>
195         /// The logging context to be used and piped down throughout evaluation
196         /// </summary>
197         private EvaluationLoggingContext _evaluationLoggingContext;
198 
199         private bool _logProjectImportedEvents = true;
200 
201         /// <summary>
202         /// The search paths are machine specific and should not change during builds
203         /// </summary>
204         private static readonly EngineFileUtilities.IOCache _fallbackSearchPathsCache = new EngineFileUtilities.IOCache();
205 
206         private readonly EvaluationProfiler _evaluationProfiler;
207 
208         /// <summary>
209         /// Private constructor called by the static Evaluate method.
210         /// </summary>
Evaluator( IEvaluatorData<P, I, M, D> data, ProjectRootElement projectRootElement, ProjectLoadSettings loadSettings, int maxNodeCount, PropertyDictionary<ProjectPropertyInstance> environmentProperties, IItemFactory<I, I> itemFactory, IToolsetProvider toolsetProvider, ProjectRootElementCache projectRootElementCache, ProjectInstance projectInstanceIfAnyForDebuggerOnly, ISdkResolverService sdkResolverService, int submissionId, EvaluationContext evaluationContext)211         private Evaluator(
212             IEvaluatorData<P, I, M, D> data,
213             ProjectRootElement projectRootElement,
214             ProjectLoadSettings loadSettings,
215             int maxNodeCount,
216             PropertyDictionary<ProjectPropertyInstance> environmentProperties,
217             IItemFactory<I, I> itemFactory,
218             IToolsetProvider toolsetProvider,
219             ProjectRootElementCache projectRootElementCache,
220             ProjectInstance projectInstanceIfAnyForDebuggerOnly,
221             ISdkResolverService sdkResolverService,
222             int submissionId,
223             EvaluationContext evaluationContext)
224         {
225             ErrorUtilities.VerifyThrowInternalNull(data, "data");
226             ErrorUtilities.VerifyThrowInternalNull(projectRootElementCache, "projectRootElementCache");
227 
228             // Create containers for the evaluation results
229             data.InitializeForEvaluation(toolsetProvider);
230 
231             _expander = new Expander<P, I>(data, data);
232 
233             // This setting may change after the build has started, therefore if the user has not set the property to true on the build parameters we need to check to see if it is set to true on the environment variable.
234             _expander.WarnForUninitializedProperties = BuildParameters.WarnOnUninitializedProperty || Traits.Instance.EscapeHatches.WarnOnUninitializedProperty;
235             _data = data;
236             _itemGroupElements = new List<ProjectItemGroupElement>();
237             _itemDefinitionGroupElements = new List<ProjectItemDefinitionGroupElement>();
238             _usingTaskElements = new List<Pair<string, ProjectUsingTaskElement>>();
239             _targetElements = new List<ProjectTargetElement>();
240             _importsSeen = new Dictionary<string, ProjectImportElement>(StringComparer.OrdinalIgnoreCase);
241             _initialTargetsList = new List<string>();
242             _projectSupportsReturnsAttribute = new Dictionary<ProjectRootElement, NGen<bool>>();
243             _projectRootElement = projectRootElement;
244             _loadSettings = loadSettings;
245             _maxNodeCount = maxNodeCount;
246             _environmentProperties = environmentProperties;
247             _itemFactory = itemFactory;
248             _projectRootElementCache = projectRootElementCache;
249             _projectInstanceIfAnyForDebuggerOnly = projectInstanceIfAnyForDebuggerOnly;
250             _sdkResolverService = sdkResolverService;
251             _submissionId = submissionId;
252             _evaluationContext = evaluationContext;
253             _evaluationProfiler = new EvaluationProfiler((loadSettings & ProjectLoadSettings.ProfileEvaluation) != 0);
254         }
255 
256         /// <summary>
257         /// Delegate passed to methods to provide basic expression evaluation
258         /// ability, without having a language service.
259         /// </summary>
ExpandExpression(string unexpandedString)260         internal delegate string ExpandExpression(string unexpandedString);
261 
262         /// <summary>
263         /// Delegate passed to methods to provide basic expression evaluation
264         /// ability, without having a language service.
265         /// </summary>
EvaluateConditionalExpression(string unexpandedExpression)266         internal delegate bool EvaluateConditionalExpression(string unexpandedExpression);
267 
268         /// <summary>
269         /// Enumeration for locals types
270         /// Note: This should match LocalsTypesNames
271         /// </summary>
272         private enum LocalsTypes : int
273         {
274             /// <summary>
275             /// Project,
276             /// </summary>
277             Project,
278 
279             /// <summary>
280             /// BuiltIn,
281             /// </summary>
282             BuiltIn,
283 
284             /// <summary>
285             /// Environment,
286             /// </summary>
287             Environment,
288 
289             /// <summary>
290             /// Toolset,
291             /// </summary>
292             Toolset,
293 
294             /// <summary>
295             /// SubToolset,
296             /// </summary>
297             SubToolset,
298 
299             /// <summary>
300             /// Global,
301             /// </summary>
302             Global,
303 
304             /// <summary>
305             /// EvaluateExpression,
306             /// </summary>
307             EvaluateExpression,
308 
309             /// <summary>
310             /// EvaluateCondition,
311             /// </summary>
312             EvaluateCondition,
313 
314             /// <summary>
315             /// ToolsVersion,
316             /// </summary>
317             ToolsVersion,
318 
319             /// <summary>
320             /// Properties,
321             /// </summary>
322             Properties,
323 
324             /// <summary>
325             /// ItemDefinitions,
326             /// </summary>
327             ItemDefinitions,
328 
329             /// <summary>
330             /// Items
331             /// </summary>
332             Items
333         }
334 
335         /// <summary>
336         /// Evaluates the project data passed in.
337         /// If debugging is enabled, returns a dictionary of name/value pairs such as properties, for debugger display.
338         /// </summary>
339         /// <remarks>
340         /// This is the only non-private member of this class.
341         /// This is a helper static method so that the caller can just do "Evaluator.Evaluate(..)" without
342         /// newing one up, yet the whole class need not be static.
343         /// The optional ProjectInstance is only exposed when doing debugging. It is not used by the evaluator.
344         /// </remarks>
Evaluate( IEvaluatorData<P, I, M, D> data, ProjectRootElement root, ProjectLoadSettings loadSettings, int maxNodeCount, PropertyDictionary<ProjectPropertyInstance> environmentProperties, ILoggingService loggingService, IItemFactory<I, I> itemFactory, IToolsetProvider toolsetProvider, ProjectRootElementCache projectRootElementCache, BuildEventContext buildEventContext, ProjectInstance projectInstanceIfAnyForDebuggerOnly, ISdkResolverService sdkResolverService, int submissionId, EvaluationContext evaluationContext = null)345         internal static IDictionary<string, object> Evaluate(
346             IEvaluatorData<P, I, M, D> data,
347             ProjectRootElement root,
348             ProjectLoadSettings loadSettings,
349             int maxNodeCount,
350             PropertyDictionary<ProjectPropertyInstance> environmentProperties,
351             ILoggingService loggingService,
352             IItemFactory<I, I> itemFactory,
353             IToolsetProvider toolsetProvider,
354             ProjectRootElementCache projectRootElementCache,
355             BuildEventContext buildEventContext,
356             ProjectInstance projectInstanceIfAnyForDebuggerOnly,
357             ISdkResolverService sdkResolverService,
358             int submissionId,
359             EvaluationContext evaluationContext = null)
360         {
361 #if (!STANDALONEBUILD)
362             using (new CodeMarkerStartEnd(CodeMarkerEvent.perfMSBuildProjectEvaluateBegin, CodeMarkerEvent.perfMSBuildProjectEvaluateEnd))
363 #endif
364             {
365 #if MSBUILDENABLEVSPROFILING
366             try
367             {
368                 string projectFile = String.IsNullOrEmpty(root.ProjectFileLocation.File) ? "(null)" : root.ProjectFileLocation.File;
369                 string beginProjectEvaluate = String.Format(CultureInfo.CurrentCulture, "Evaluate Project {0} - Begin", projectFile);
370                 DataCollection.CommentMarkProfile(8812, beginProjectEvaluate);
371 #endif
372                 var evaluator = new Evaluator<P, I, M, D>(
373                     data,
374                     root,
375                     loadSettings,
376                     maxNodeCount,
377                     environmentProperties,
378                     itemFactory,
379                     toolsetProvider,
380                     projectRootElementCache,
381                     projectInstanceIfAnyForDebuggerOnly,
382                     sdkResolverService,
383                     submissionId,
384                     evaluationContext);
385 
386                 return evaluator.Evaluate(loggingService, buildEventContext);
387 #if MSBUILDENABLEVSPROFILING
388             }
389             finally
390             {
391                 string projectFile = String.IsNullOrEmpty(root.ProjectFileLocation.File) ? "(null)" : root.ProjectFileLocation.File;
392                 string beginProjectEvaluate = String.Format(CultureInfo.CurrentCulture, "Evaluate Project {0} - End", projectFile);
393                 DataCollection.CommentMarkProfile(8813, beginProjectEvaluate);
394             }
395 #endif
396             }
397         }
398 
399         /// <summary>
400         /// Helper that creates a list of ProjectItem's given an unevaluated Include and a ProjectRootElement.
401         /// Used by both Evaluator.EvaluateItemElement and by Project.AddItem.
402         /// </summary>
CreateItemsFromInclude(string rootDirectory, ProjectItemElement itemElement, IItemFactory<I, I> itemFactory, string unevaluatedIncludeEscaped, Expander<P, I> expander)403         internal static List<I> CreateItemsFromInclude(string rootDirectory, ProjectItemElement itemElement, IItemFactory<I, I> itemFactory, string unevaluatedIncludeEscaped, Expander<P, I> expander)
404         {
405             ErrorUtilities.VerifyThrowArgumentLength(unevaluatedIncludeEscaped, "unevaluatedIncludeEscaped");
406 
407             List<I> items = new List<I>();
408             itemFactory.ItemElement = itemElement;
409 
410             // STEP 1: Expand properties in Include
411             string evaluatedIncludeEscaped = expander.ExpandIntoStringLeaveEscaped(unevaluatedIncludeEscaped, ExpanderOptions.ExpandProperties, itemElement.IncludeLocation);
412 
413             // STEP 2: Split Include on any semicolons, and take each split in turn
414             if (evaluatedIncludeEscaped.Length > 0)
415             {
416                 var includeSplitsEscaped = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedIncludeEscaped);
417 
418                 foreach (string includeSplitEscaped in includeSplitsEscaped)
419                 {
420                     // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string
421                     bool throwaway;
422                     IList<I> itemsFromSplit = expander.ExpandSingleItemVectorExpressionIntoItems(includeSplitEscaped, itemFactory, ExpanderOptions.ExpandItems, false /* do not include null expansion results */, out throwaway, itemElement.IncludeLocation);
423 
424                     if (itemsFromSplit != null)
425                     {
426                         // Expression is in form "@(X)"
427                         foreach (I item in itemsFromSplit)
428                         {
429                             items.Add(item);
430                         }
431                     }
432                     else
433                     {
434                         // The expression is not of the form "@(X)". Treat as string
435                         string[] includeSplitFilesEscaped = EngineFileUtilities.GetFileListEscaped(rootDirectory, includeSplitEscaped);
436 
437                         if (includeSplitFilesEscaped.Length > 0)
438                         {
439                             foreach (string includeSplitFileEscaped in includeSplitFilesEscaped)
440                             {
441                                 items.Add(itemFactory.CreateItem(includeSplitFileEscaped, includeSplitEscaped, itemElement.ContainingProject.FullPath));
442                             }
443                         }
444                     }
445                 }
446             }
447 
448             return items;
449         }
450 
451         /// <summary>
452         /// Read the task into an instance.
453         /// Do not evaluate anything: this occurs during build.
454         /// </summary>
ReadTaskElement(ProjectTaskElement taskElement)455         private static ProjectTaskInstance ReadTaskElement(ProjectTaskElement taskElement)
456         {
457             List<ProjectTaskInstanceChild> taskOutputs = new List<ProjectTaskInstanceChild>(taskElement.Count);
458 
459             foreach (ProjectOutputElement output in taskElement.Outputs)
460             {
461                 if (output.IsOutputItem)
462                 {
463                     ProjectTaskOutputItemInstance outputItem = new ProjectTaskOutputItemInstance
464                         (
465                         output.ItemType,
466                         output.TaskParameter,
467                         output.Condition,
468                         output.Location,
469                         output.ItemTypeLocation,
470                         output.TaskParameterLocation,
471                         output.ConditionLocation
472                         );
473 
474                     taskOutputs.Add(outputItem);
475                 }
476                 else
477                 {
478                     ProjectTaskOutputPropertyInstance outputItem = new ProjectTaskOutputPropertyInstance
479                         (
480                         output.PropertyName,
481                         output.TaskParameter,
482                         output.Condition,
483                         output.Location,
484                         output.PropertyNameLocation,
485                         output.TaskParameterLocation,
486                         output.ConditionLocation
487                         );
488 
489                     taskOutputs.Add(outputItem);
490                 }
491             }
492 
493             ProjectTaskInstance task = new ProjectTaskInstance(taskElement, taskOutputs);
494             return task;
495         }
496 
497         /// <summary>
498         /// Read the property-group-under-target into an instance.
499         /// Do not evaluate anything: this occurs during build.
500         /// </summary>
ReadPropertyGroupUnderTargetElement(ProjectPropertyGroupElement propertyGroupElement)501         private static ProjectPropertyGroupTaskInstance ReadPropertyGroupUnderTargetElement(ProjectPropertyGroupElement propertyGroupElement)
502         {
503             List<ProjectPropertyGroupTaskPropertyInstance> properties = new List<ProjectPropertyGroupTaskPropertyInstance>(propertyGroupElement.Count);
504 
505             foreach (ProjectPropertyElement propertyElement in propertyGroupElement.Properties)
506             {
507                 ProjectPropertyGroupTaskPropertyInstance property = new ProjectPropertyGroupTaskPropertyInstance(propertyElement.Name, propertyElement.Value, propertyElement.Condition, propertyElement.Location, propertyElement.ConditionLocation);
508                 properties.Add(property);
509             }
510 
511             ProjectPropertyGroupTaskInstance propertyGroup = new ProjectPropertyGroupTaskInstance(propertyGroupElement.Condition, propertyGroupElement.Location, propertyGroupElement.ConditionLocation, properties);
512 
513             return propertyGroup;
514         }
515 
516         /// <summary>
517         /// Read an onError tag.
518         /// Do not evaluate anything: this occurs during build.
519         /// </summary>
ReadOnErrorElement(ProjectOnErrorElement projectOnErrorElement)520         private static ProjectOnErrorInstance ReadOnErrorElement(ProjectOnErrorElement projectOnErrorElement)
521         {
522             ProjectOnErrorInstance onError = new ProjectOnErrorInstance(projectOnErrorElement.ExecuteTargetsAttribute, projectOnErrorElement.Condition, projectOnErrorElement.Location, projectOnErrorElement.ExecuteTargetsLocation, projectOnErrorElement.ConditionLocation);
523 
524             return onError;
525         }
526 
527         /// <summary>
528         /// Read the item-group-under-target into an instance.
529         /// Do not evaluate anything: this occurs during build.
530         /// </summary>
ReadItemGroupUnderTargetElement(ProjectItemGroupElement itemGroupElement)531         private static ProjectItemGroupTaskInstance ReadItemGroupUnderTargetElement(ProjectItemGroupElement itemGroupElement)
532         {
533             List<ProjectItemGroupTaskItemInstance> items = new List<ProjectItemGroupTaskItemInstance>(itemGroupElement.Count);
534 
535             foreach (ProjectItemElement itemElement in itemGroupElement.Items)
536             {
537                 List<ProjectItemGroupTaskMetadataInstance> metadata = null;
538 
539                 foreach (ProjectMetadataElement metadataElement in itemElement.Metadata)
540                 {
541                     if (metadata == null)
542                     {
543                         metadata = new List<ProjectItemGroupTaskMetadataInstance>();
544                     }
545 
546                     ProjectItemGroupTaskMetadataInstance metadatum = new ProjectItemGroupTaskMetadataInstance
547                         (
548                         metadataElement.Name,
549                         metadataElement.Value,
550                         metadataElement.Condition,
551                         metadataElement.Location,
552                         metadataElement.ConditionLocation
553                         );
554 
555                     metadata.Add(metadatum);
556                 }
557 
558                 ProjectItemGroupTaskItemInstance item = new ProjectItemGroupTaskItemInstance
559                     (
560                     itemElement.ItemType,
561                     itemElement.Include,
562                     itemElement.Exclude,
563                     itemElement.Remove,
564                     itemElement.KeepMetadata,
565                     itemElement.RemoveMetadata,
566                     itemElement.KeepDuplicates,
567                     itemElement.Condition,
568                     itemElement.Location,
569                     itemElement.IncludeLocation,
570                     itemElement.ExcludeLocation,
571                     itemElement.RemoveLocation,
572                     itemElement.KeepMetadataLocation,
573                     itemElement.RemoveMetadataLocation,
574                     itemElement.KeepDuplicatesLocation,
575                     itemElement.ConditionLocation,
576                     metadata
577                     );
578 
579                 items.Add(item);
580             }
581 
582             ProjectItemGroupTaskInstance itemGroup = new ProjectItemGroupTaskInstance(itemGroupElement.Condition, itemGroupElement.Location, itemGroupElement.ConditionLocation, items);
583 
584             return itemGroup;
585         }
586 
587         /// <summary>
588         /// Read the provided target into a target instance.
589         /// Do not evaluate anything: this occurs during build.
590         /// </summary>
ReadNewTargetElement(ProjectTargetElement targetElement, bool parentProjectSupportsReturnsAttribute, EvaluationProfiler evaluationProfiler)591         private static ProjectTargetInstance ReadNewTargetElement(ProjectTargetElement targetElement, bool parentProjectSupportsReturnsAttribute, EvaluationProfiler evaluationProfiler)
592         {
593             List<ProjectTargetInstanceChild> targetChildren = new List<ProjectTargetInstanceChild>(targetElement.Count);
594             List<ProjectOnErrorInstance> targetOnErrorChildren = new List<ProjectOnErrorInstance>();
595 
596             foreach (ProjectElement targetChildElement in targetElement.Children)
597             {
598                 using (evaluationProfiler.TrackElement(targetChildElement))
599                 {
600                     ProjectTaskElement task = targetChildElement as ProjectTaskElement;
601 
602                     if (task != null)
603                     {
604                         ProjectTaskInstance taskInstance = ReadTaskElement(task);
605 
606                         targetChildren.Add(taskInstance);
607                         continue;
608                     }
609 
610                     ProjectPropertyGroupElement propertyGroup = targetChildElement as ProjectPropertyGroupElement;
611 
612                     if (propertyGroup != null)
613                     {
614                         ProjectPropertyGroupTaskInstance propertyGroupInstance = ReadPropertyGroupUnderTargetElement(propertyGroup);
615 
616                         targetChildren.Add(propertyGroupInstance);
617                         continue;
618                     }
619 
620                     ProjectItemGroupElement itemGroup = targetChildElement as ProjectItemGroupElement;
621 
622                     if (itemGroup != null)
623                     {
624                         ProjectItemGroupTaskInstance itemGroupInstance = ReadItemGroupUnderTargetElement(itemGroup);
625 
626                         targetChildren.Add(itemGroupInstance);
627                         continue;
628                     }
629 
630                     ProjectOnErrorElement onError = targetChildElement as ProjectOnErrorElement;
631 
632                     if (onError != null)
633                     {
634                         ProjectOnErrorInstance onErrorInstance = ReadOnErrorElement(onError);
635 
636                         targetOnErrorChildren.Add(onErrorInstance);
637                         continue;
638                     }
639 
640                     ErrorUtilities.ThrowInternalError("Unexpected child");
641                 }
642             }
643 
644             // ObjectModel.ReadOnlyCollection is actually a poorly named ReadOnlyList
645 
646             // UNDONE: (Cloning.) This should be cloning these collections, but it isn't. ProjectTargetInstance will be able to see modifications.
647             ObjectModel.ReadOnlyCollection<ProjectTargetInstanceChild> readOnlyTargetChildren = new ObjectModel.ReadOnlyCollection<ProjectTargetInstanceChild>(targetChildren);
648             ObjectModel.ReadOnlyCollection<ProjectOnErrorInstance> readOnlyTargetOnErrorChildren = new ObjectModel.ReadOnlyCollection<ProjectOnErrorInstance>(targetOnErrorChildren);
649 
650             ProjectTargetInstance targetInstance = new ProjectTargetInstance
651                 (
652                 targetElement.Name,
653                 targetElement.Condition,
654                 targetElement.Inputs,
655                 targetElement.Outputs,
656                 targetElement.Returns,
657                 targetElement.KeepDuplicateOutputs,
658                 targetElement.DependsOnTargets,
659                 targetElement.Location,
660                 targetElement.ConditionLocation,
661                 targetElement.InputsLocation,
662                 targetElement.OutputsLocation,
663                 targetElement.ReturnsLocation,
664                 targetElement.KeepDuplicateOutputsLocation,
665                 targetElement.DependsOnTargetsLocation,
666                 targetElement.BeforeTargetsLocation,
667                 targetElement.AfterTargetsLocation,
668                 readOnlyTargetChildren,
669                 readOnlyTargetOnErrorChildren,
670                 parentProjectSupportsReturnsAttribute
671                 );
672 
673             targetElement.TargetInstance = targetInstance;
674             return targetInstance;
675         }
676 
677         /// <summary>
678         /// Do the evaluation.
679         /// Called by the static helper method.
680         /// If debugging is enabled, returns a dictionary of name/value pairs such as properties, for debugger display.
681         /// </summary>
Evaluate(ILoggingService loggingService, BuildEventContext buildEventContext)682         private IDictionary<string, object> Evaluate(ILoggingService loggingService, BuildEventContext buildEventContext)
683         {
684             string projectFile;
685             using (_evaluationProfiler.TrackPass(EvaluationPass.TotalEvaluation))
686             {
687                 ErrorUtilities.VerifyThrow(_data.EvaluationId == BuildEventContext.InvalidEvaluationId, "There is no prior evaluation ID. The evaluator data needs to be reset at this point");
688 
689                 _logProjectImportedEvents = Traits.Instance.EscapeHatches.LogProjectImports;
690 
691                 ICollection<P> builtInProperties;
692                 ICollection<P> environmentProperties;
693                 ICollection<P> toolsetProperties;
694                 ICollection<P> globalProperties;
695 
696                 using (_evaluationProfiler.TrackPass(EvaluationPass.InitialProperties))
697                 {
698                     // Pass0: load initial properties
699                     // Follow the order of precedence so that Global properties overwrite Environment properties
700                     builtInProperties = AddBuiltInProperties();
701                     environmentProperties = AddEnvironmentProperties();
702                     toolsetProperties = AddToolsetProperties();
703                     globalProperties = AddGlobalProperties();
704                 }
705 
706 #if (!STANDALONEBUILD)
707             CodeMarkers.Instance.CodeMarker(CodeMarkerEvent.perfMSBuildProjectEvaluatePass0End);
708 #endif
709                 projectFile = String.IsNullOrEmpty(_projectRootElement.ProjectFileLocation.File) ? "(null)" : _projectRootElement.ProjectFileLocation.File;
710 
711                 _evaluationLoggingContext = new EvaluationLoggingContext(loggingService, buildEventContext, projectFile);
712                 _data.EvaluationId = _evaluationLoggingContext.BuildEventContext.EvaluationId;
713 
714                 _evaluationLoggingContext.LogProjectEvaluationStarted();
715 
716                 ErrorUtilities.VerifyThrow(_data.EvaluationId != BuildEventContext.InvalidEvaluationId, "Evaluation should produce an evaluation ID");
717 
718 #if MSBUILDENABLEVSPROFILING
719         string endPass0 = String.Format(CultureInfo.CurrentCulture, "Evaluate Project {0} - End Pass 0 (Initial properties)", projectFile);
720         DataCollection.CommentMarkProfile(8816, endPass0);
721 #endif
722 
723                 // Pass1: evaluate properties, load imports, and gather everything else
724                 using (_evaluationProfiler.TrackPass(EvaluationPass.Properties))
725                 {
726                     PerformDepthFirstPass(_projectRootElement);
727                 }
728 
729                 List<string> initialTargets = new List<string>(_initialTargetsList.Count);
730                 foreach (var initialTarget in _initialTargetsList)
731                 {
732                     initialTargets.Add(EscapingUtilities.UnescapeAll(initialTarget.Trim()));
733                 }
734 
735                 _data.InitialTargets = initialTargets;
736 #if (!STANDALONEBUILD)
737         CodeMarkers.Instance.CodeMarker(CodeMarkerEvent.perfMSBuildProjectEvaluatePass1End);
738 #endif
739 #if MSBUILDENABLEVSPROFILING
740         string endPass1 = String.Format(CultureInfo.CurrentCulture, "Evaluate Project {0} - End Pass 1 (Properties and Imports)", projectFile);
741         DataCollection.CommentMarkProfile(8817, endPass1);
742 #endif
743                 // Pass2: evaluate item definitions
744                 // Don't box via IEnumerator and foreach; cache count so not to evaluate via interface each iteration
745                 using (_evaluationProfiler.TrackPass(EvaluationPass.ItemDefinitionGroups))
746                 {
747                     foreach (var itemDefinitionGroupElement in _itemDefinitionGroupElements)
748                     {
749                         using (_evaluationProfiler.TrackElement(itemDefinitionGroupElement))
750                         {
751                             EvaluateItemDefinitionGroupElement(itemDefinitionGroupElement);
752                         }
753                     }
754                 }
755 #if (!STANDALONEBUILD)
756         CodeMarkers.Instance.CodeMarker(CodeMarkerEvent.perfMSBuildProjectEvaluatePass2End);
757 #endif
758 #if MSBUILDENABLEVSPROFILING
759         string endPass2 = String.Format(CultureInfo.CurrentCulture, "Evaluate Project {0} - End Pass 2 (Item Definitions)", projectFile);
760         DataCollection.CommentMarkProfile(8818, endPass2);
761 #endif
762                 LazyItemEvaluator<P, I, M, D> lazyEvaluator = null;
763                 using (_evaluationProfiler.TrackPass(EvaluationPass.Items))
764                 {
765                     // comment next line to turn off lazy Evaluation
766                     lazyEvaluator = new LazyItemEvaluator<P, I, M, D>(_data, _itemFactory, _evaluationLoggingContext, _evaluationProfiler);
767 
768                     // Pass3: evaluate project items
769                     foreach (ProjectItemGroupElement itemGroup in _itemGroupElements)
770                     {
771                         using (_evaluationProfiler.TrackElement(itemGroup))
772                         {
773                             EvaluateItemGroupElement(itemGroup, lazyEvaluator);
774                         }
775                     }
776                 }
777 
778                 if (lazyEvaluator != null)
779                 {
780                     using (_evaluationProfiler.TrackPass(EvaluationPass.LazyItems))
781                     {
782                         // Tell the lazy evaluator to compute the items and add them to _data
783                         foreach (var itemData in lazyEvaluator.GetAllItemsDeferred())
784                         {
785                             if (itemData.ConditionResult)
786                             {
787                                 _data.AddItem(itemData.Item);
788 
789                                 if (_data.ShouldEvaluateForDesignTime)
790                                 {
791                                     _data.AddToAllEvaluatedItemsList(itemData.Item);
792                                 }
793                             }
794 
795                             if (_data.ShouldEvaluateForDesignTime)
796                             {
797                                 _data.AddItemIgnoringCondition(itemData.Item);
798                             }
799                         }
800 
801                         // lazy evaluator can be collected now, the rest of evaluation does not need it anymore
802                         lazyEvaluator = null;
803                     }
804                 }
805 
806 #if (!STANDALONEBUILD)
807         CodeMarkers.Instance.CodeMarker(CodeMarkerEvent.perfMSBuildProjectEvaluatePass3End);
808 #endif
809 #if MSBUILDENABLEVSPROFILING
810         string endPass3 = String.Format(CultureInfo.CurrentCulture, "Evaluate Project {0} - End Pass 3 (Items)", projectFile);
811         DataCollection.CommentMarkProfile(8819, endPass3);
812 #endif
813                 // Pass4: evaluate using-tasks
814                 using (_evaluationProfiler.TrackPass(EvaluationPass.UsingTasks))
815                 {
816                     foreach (var entry in _usingTaskElements)
817                     {
818                         EvaluateUsingTaskElement(entry.Key, entry.Value);
819                     }
820                 }
821 
822                 // If there was no DefaultTargets attribute found in the depth first pass,
823                 // use the name of the first target. If there isn't any target, don't error until build time.
824 
825                 if (_data.DefaultTargets == null)
826                 {
827                     _data.DefaultTargets = new List<string>(1);
828                 }
829 
830                 var targetElementsCount = _targetElements.Count;
831                 if (_data.DefaultTargets.Count == 0 && targetElementsCount > 0)
832                 {
833                     _data.DefaultTargets.Add(_targetElements[0].Name);
834                 }
835 
836                 Dictionary<string, List<TargetSpecification>> targetsWhichRunBeforeByTarget = new Dictionary<string, List<TargetSpecification>>(StringComparer.OrdinalIgnoreCase);
837                 Dictionary<string, List<TargetSpecification>> targetsWhichRunAfterByTarget = new Dictionary<string, List<TargetSpecification>>(StringComparer.OrdinalIgnoreCase);
838                 LinkedList<ProjectTargetElement> activeTargetsByEvaluationOrder = new LinkedList<ProjectTargetElement>();
839                 Dictionary<string, LinkedListNode<ProjectTargetElement>> activeTargets = new Dictionary<string, LinkedListNode<ProjectTargetElement>>(StringComparer.OrdinalIgnoreCase);
840 #if (!STANDALONEBUILD)
841         CodeMarkers.Instance.CodeMarker(CodeMarkerEvent.perfMSBuildProjectEvaluatePass4End);
842 #endif
843 #if MSBUILDENABLEVSPROFILING
844         string endPass4 = String.Format(CultureInfo.CurrentCulture, "Evaluate Project {0} - End Pass 4 (UsingTasks)", projectFile);
845         DataCollection.CommentMarkProfile(8820, endPass4);
846 #endif
847 
848                 using (_evaluationProfiler.TrackPass(EvaluationPass.Targets))
849                 {
850                     // Pass5: read targets (but don't evaluate them: that happens during build)
851                     for (var i = 0; i < targetElementsCount; i++)
852                     {
853                         var element = _targetElements[i];
854                         using (_evaluationProfiler.TrackElement(element))
855                         {
856                             ReadTargetElement(element, activeTargetsByEvaluationOrder, activeTargets);
857                         }
858                     }
859 
860                     foreach (ProjectTargetElement target in activeTargetsByEvaluationOrder)
861                     {
862                         using (_evaluationProfiler.TrackElement(target))
863                         {
864                             AddBeforeAndAfterTargetMappings(target, activeTargets, targetsWhichRunBeforeByTarget, targetsWhichRunAfterByTarget);
865                         }
866                     }
867 
868                     _data.BeforeTargets = targetsWhichRunBeforeByTarget;
869                     _data.AfterTargets = targetsWhichRunAfterByTarget;
870 
871                     if (Traits.Instance.EscapeHatches.DebugEvaluation)
872                     {
873                         // This is so important for VS performance it's worth always tracing; accidentally having
874                         // inconsistent sets of global properties will cause reevaluations, which are wasteful and incorrect
875                         if (_projectRootElement.Count > 0) // VB/C# will new up empty projects; they aren't worth recording
876                         {
877                             ProjectPropertyInstance configurationData = _data.GlobalPropertiesDictionary["currentsolutionconfigurationcontents"];
878                             int hash = (configurationData != null) ? configurationData.EvaluatedValue.GetHashCode() : 0;
879                             string propertyDump = null;
880 
881                             foreach (var entry in _data.GlobalPropertiesDictionary)
882                             {
883                                 if (!String.Equals(entry.Name, "currentsolutionconfigurationcontents", StringComparison.OrdinalIgnoreCase))
884                                 {
885                                     propertyDump += entry.Name + "=" + entry.EvaluatedValue + "\n";
886                                 }
887                             }
888 
889                             string line = new string('#', 100) + "\n";
890 
891                             string output = String.Format(CultureInfo.CurrentUICulture, "###: MSBUILD: Evaluating or reevaluating project {0} with {1} global properties and {2} tools version, child count {3}, CurrentSolutionConfigurationContents hash {4} other properties:\n{5}", _projectRootElement.FullPath, globalProperties.Count, _data.Toolset.ToolsVersion, _projectRootElement.Count, hash, propertyDump);
892 
893                             Trace.WriteLine(line + output + line);
894                         }
895                     }
896 
897                     _data.FinishEvaluation();
898                 }
899             }
900 
901             ErrorUtilities.VerifyThrow(_evaluationProfiler.IsEmpty(), "Evaluation profiler stack is not empty.");
902             _evaluationLoggingContext.LogBuildEvent(new ProjectEvaluationFinishedEventArgs(ResourceUtilities.GetResourceString("EvaluationFinished"), projectFile)
903             {
904                 BuildEventContext = _evaluationLoggingContext.BuildEventContext,
905                 ProjectFile = projectFile,
906                 ProfilerResult = (_loadSettings & ProjectLoadSettings.ProfileEvaluation) != 0 ? (ProfilerResult?)_evaluationProfiler.ProfiledResult : null
907             });
908 
909             return null;
910         }
911 
912         /// <summary>
913         /// Evaluate the properties in the passed in XML, into the project.
914         /// Does a depth first traversal into Imports.
915         /// In the process, populates the item, itemdefinition, target, and usingtask lists as well.
916         /// </summary>
PerformDepthFirstPass(ProjectRootElement currentProjectOrImport)917         private void PerformDepthFirstPass(ProjectRootElement currentProjectOrImport)
918         {
919             using (_evaluationProfiler.TrackFile(currentProjectOrImport.FullPath))
920             {
921                 // We accumulate InitialTargets from the project and each import
922                 var initialTargets = _expander.ExpandIntoStringListLeaveEscaped(currentProjectOrImport.InitialTargets, ExpanderOptions.ExpandProperties, currentProjectOrImport.InitialTargetsLocation);
923                 _initialTargetsList.AddRange(initialTargets);
924 
925                 if (!Traits.Instance.EscapeHatches.IgnoreTreatAsLocalProperty)
926                 {
927                     foreach (string propertyName in _expander.ExpandIntoStringListLeaveEscaped(currentProjectOrImport.TreatAsLocalProperty, ExpanderOptions.ExpandProperties, currentProjectOrImport.TreatAsLocalPropertyLocation))
928                     {
929                         XmlUtilities.VerifyThrowProjectValidElementName(propertyName, currentProjectOrImport.Location);
930                         _data.GlobalPropertiesToTreatAsLocal.Add(propertyName);
931                     }
932                 }
933 
934                 UpdateDefaultTargets(currentProjectOrImport);
935 
936                 // Get all the implicit imports (e.g. <Project Sdk="" />, but not <Import Sdk="" />)
937                 List<ProjectImportElement> implicitImports = currentProjectOrImport.GetImplicitImportNodes(currentProjectOrImport);
938 
939                 // Evaluate the "top" implicit imports as if they were the first entry in the file.
940                 foreach (var import in implicitImports)
941                 {
942                     if (import.ImplicitImportLocation == ImplicitImportLocation.Top)
943                     {
944                         EvaluateImportElement(currentProjectOrImport.DirectoryPath, import);
945                     }
946                 }
947 
948                 foreach (ProjectElement element in currentProjectOrImport.Children)
949                 {
950                     ProjectPropertyGroupElement propertyGroup = element as ProjectPropertyGroupElement;
951 
952                     if (propertyGroup != null)
953                     {
954                         EvaluatePropertyGroupElement(propertyGroup);
955                         continue;
956                     }
957 
958                     ProjectItemGroupElement itemGroup = element as ProjectItemGroupElement;
959 
960                     if (itemGroup != null)
961                     {
962                         _itemGroupElements.Add(itemGroup);
963 
964                         continue;
965                     }
966 
967                     ProjectItemDefinitionGroupElement itemDefinitionGroup = element as ProjectItemDefinitionGroupElement;
968 
969                     if (itemDefinitionGroup != null)
970                     {
971                         _itemDefinitionGroupElements.Add(itemDefinitionGroup);
972 
973                         continue;
974                     }
975 
976                     ProjectTargetElement target = element as ProjectTargetElement;
977 
978                     if (target != null)
979                     {
980                         if (_projectSupportsReturnsAttribute.ContainsKey(currentProjectOrImport))
981                         {
982                             _projectSupportsReturnsAttribute[currentProjectOrImport] |= (target.Returns != null);
983                         }
984                         else
985                         {
986                             _projectSupportsReturnsAttribute[currentProjectOrImport] = (target.Returns != null);
987                         }
988 
989                         _targetElements.Add(target);
990 
991                         continue;
992                     }
993 
994                     ProjectImportElement import = element as ProjectImportElement;
995                     if (import != null)
996                     {
997                         EvaluateImportElement(currentProjectOrImport.DirectoryPath, import);
998                         continue;
999                     }
1000 
1001                     ProjectImportGroupElement importGroup = element as ProjectImportGroupElement;
1002 
1003                     if (importGroup != null)
1004                     {
1005                         EvaluateImportGroupElement(currentProjectOrImport.DirectoryPath, importGroup);
1006                         continue;
1007                     }
1008 
1009                     ProjectUsingTaskElement usingTask = element as ProjectUsingTaskElement;
1010 
1011                     if (usingTask != null)
1012                     {
1013                         _usingTaskElements.Add(new Pair<string, ProjectUsingTaskElement>(currentProjectOrImport.DirectoryPath, usingTask));
1014                         continue;
1015                     }
1016 
1017                     ProjectChooseElement choose = element as ProjectChooseElement;
1018 
1019                     if (choose != null)
1020                     {
1021                         EvaluateChooseElement(choose);
1022                         continue;
1023                     }
1024 
1025                     if (element is ProjectExtensionsElement)
1026                     {
1027                         continue;
1028                     }
1029 
1030                     if (element is ProjectSdkElement)
1031                     {
1032                         continue; // This case is handled by implicit imports.
1033                     }
1034 
1035                     ErrorUtilities.ThrowInternalError("Unexpected child type");
1036                 }
1037 
1038                 // Evaluate the "bottom" implicit imports as if they were the last entry in the file.
1039                 foreach (var import in implicitImports)
1040                 {
1041                     if (import.ImplicitImportLocation == ImplicitImportLocation.Bottom)
1042                     {
1043                         EvaluateImportElement(currentProjectOrImport.DirectoryPath, import);
1044                     }
1045                 }
1046             }
1047         }
1048 
1049         /// <summary>
1050         /// Update the default targets value.
1051         /// We only take the first DefaultTargets value we encounter in a project or import.
1052         /// </summary>
UpdateDefaultTargets(ProjectRootElement currentProjectOrImport)1053         private void UpdateDefaultTargets(ProjectRootElement currentProjectOrImport)
1054         {
1055             if (_data.DefaultTargets == null)
1056             {
1057                 string expanded = _expander.ExpandIntoStringLeaveEscaped(currentProjectOrImport.DefaultTargets, ExpanderOptions.ExpandProperties, currentProjectOrImport.DefaultTargetsLocation);
1058 
1059                 if (expanded.Length > 0)
1060                 {
1061                     SetBuiltInProperty(ReservedPropertyNames.projectDefaultTargets, EscapingUtilities.UnescapeAll(expanded));
1062 
1063                     List<string> temp = new List<string>(expanded.Split(s_splitter, StringSplitOptions.RemoveEmptyEntries));
1064 
1065                     for (int i = 0; i < temp.Count; i++)
1066                     {
1067                         string target = EscapingUtilities.UnescapeAll(temp[i].Trim());
1068                         if (target.Length > 0)
1069                         {
1070                             _data.DefaultTargets = _data.DefaultTargets ?? new List<string>(temp.Count);
1071                             _data.DefaultTargets.Add(target);
1072                         }
1073                     }
1074                 }
1075             }
1076         }
1077 
1078         /// <summary>
1079         /// Evaluate the properties in the propertygroup and set the applicable ones on the data passed in
1080         /// </summary>
EvaluatePropertyGroupElement(ProjectPropertyGroupElement propertyGroupElement)1081         private void EvaluatePropertyGroupElement(ProjectPropertyGroupElement propertyGroupElement)
1082         {
1083             using (_evaluationProfiler.TrackElement(propertyGroupElement))
1084             {
1085                 if (EvaluateConditionCollectingConditionedProperties(propertyGroupElement, ExpanderOptions.ExpandProperties, ParserOptions.AllowProperties))
1086                 {
1087                     foreach (ProjectPropertyElement propertyElement in propertyGroupElement.Properties)
1088                     {
1089                         EvaluatePropertyElement(propertyElement);
1090                     }
1091                 }
1092             }
1093         }
1094 
1095         /// <summary>
1096         /// Evaluate the itemdefinitiongroup and update the definitions library
1097         /// </summary>
EvaluateItemDefinitionGroupElement(ProjectItemDefinitionGroupElement itemDefinitionGroupElement)1098         private void EvaluateItemDefinitionGroupElement(ProjectItemDefinitionGroupElement itemDefinitionGroupElement)
1099         {
1100             if (EvaluateCondition(itemDefinitionGroupElement, ExpanderOptions.ExpandProperties, ParserOptions.AllowProperties))
1101             {
1102                 foreach (ProjectItemDefinitionElement itemDefinitionElement in itemDefinitionGroupElement.ItemDefinitions)
1103                 {
1104                     using (_evaluationProfiler.TrackElement(itemDefinitionElement))
1105                     {
1106                         EvaluateItemDefinitionElement(itemDefinitionElement);
1107                     }
1108                 }
1109             }
1110         }
1111 
1112         /// <summary>
1113         /// Evaluate the items in the itemgroup and add the applicable ones to the data passed in
1114         /// </summary>
EvaluateItemGroupElement(ProjectItemGroupElement itemGroupElement, LazyItemEvaluator<P, I, M, D> lazyEvaluator)1115         private void EvaluateItemGroupElement(ProjectItemGroupElement itemGroupElement, LazyItemEvaluator<P, I, M, D> lazyEvaluator)
1116         {
1117             bool itemGroupConditionResult;
1118             if (lazyEvaluator != null)
1119             {
1120                 itemGroupConditionResult = lazyEvaluator.EvaluateConditionWithCurrentState(itemGroupElement, ExpanderOptions.ExpandPropertiesAndItems, ParserOptions.AllowPropertiesAndItemLists);
1121             }
1122             else
1123             {
1124                 itemGroupConditionResult = EvaluateCondition(itemGroupElement, ExpanderOptions.ExpandPropertiesAndItems, ParserOptions.AllowPropertiesAndItemLists);
1125             }
1126 
1127             if (itemGroupConditionResult || (_data.ShouldEvaluateForDesignTime && _data.CanEvaluateElementsWithFalseConditions))
1128             {
1129                 foreach (ProjectItemElement itemElement in itemGroupElement.Items)
1130                 {
1131                     using (_evaluationProfiler.TrackElement(itemElement))
1132                     {
1133                         EvaluateItemElement(itemGroupConditionResult, itemElement, lazyEvaluator);
1134                     }
1135                 }
1136             }
1137         }
1138 
1139         /// <summary>
1140         /// Evaluate the usingtask and add the result into the data passed in
1141         /// </summary>
EvaluateUsingTaskElement(string directoryOfImportingFile, ProjectUsingTaskElement projectUsingTaskElement)1142         private void EvaluateUsingTaskElement(string directoryOfImportingFile, ProjectUsingTaskElement projectUsingTaskElement)
1143         {
1144             TaskRegistry.RegisterTasksFromUsingTaskElement<P, I>
1145                 (
1146                 _evaluationLoggingContext.LoggingService,
1147                 _evaluationLoggingContext.BuildEventContext,
1148                 directoryOfImportingFile,
1149                 projectUsingTaskElement,
1150                 _data.TaskRegistry,
1151                 _expander,
1152                 ExpanderOptions.ExpandPropertiesAndItems
1153                 );
1154         }
1155 
1156         /// <summary>
1157         /// Retrieve the matching ProjectTargetInstance from the cache and add it to the provided collection.
1158         /// If it is not cached already, read it and cache it.
1159         /// Do not evaluate anything: this occurs during build.
1160         /// </summary>
ReadTargetElement(ProjectTargetElement targetElement, LinkedList<ProjectTargetElement> activeTargetsByEvaluationOrder, Dictionary<string, LinkedListNode<ProjectTargetElement>> activeTargets)1161         private void ReadTargetElement(ProjectTargetElement targetElement, LinkedList<ProjectTargetElement> activeTargetsByEvaluationOrder, Dictionary<string, LinkedListNode<ProjectTargetElement>> activeTargets)
1162         {
1163             ProjectTargetInstance targetInstance = null;
1164 
1165             // If we already have read a target instance for this element, use that.
1166             targetInstance = targetElement.TargetInstance;
1167 
1168             if (targetInstance == null)
1169             {
1170                 targetInstance = ReadNewTargetElement(targetElement, _projectSupportsReturnsAttribute[(ProjectRootElement)targetElement.Parent], _evaluationProfiler);
1171             }
1172 
1173             string targetName = targetElement.Name;
1174             ProjectTargetInstance otherTarget = _data.GetTarget(targetName);
1175             if (otherTarget != null)
1176             {
1177                 _evaluationLoggingContext.LogComment(MessageImportance.Low, "OverridingTarget", otherTarget.Name, otherTarget.Location.File, targetName, targetElement.Location.File);
1178             }
1179 
1180             LinkedListNode<ProjectTargetElement> node;
1181             if (activeTargets.TryGetValue(targetName, out node))
1182             {
1183                 activeTargetsByEvaluationOrder.Remove(node);
1184             }
1185 
1186             activeTargets[targetName] = activeTargetsByEvaluationOrder.AddLast(targetElement);
1187             _data.AddTarget(targetInstance);
1188         }
1189 
1190         /// <summary>
1191         /// Updates the evaluation maps for BeforeTargets and AfterTargets
1192         /// </summary>
AddBeforeAndAfterTargetMappings(ProjectTargetElement targetElement, Dictionary<string, LinkedListNode<ProjectTargetElement>> activeTargets, Dictionary<string, List<TargetSpecification>> targetsWhichRunBeforeByTarget, Dictionary<string, List<TargetSpecification>> targetsWhichRunAfterByTarget)1193         private void AddBeforeAndAfterTargetMappings(ProjectTargetElement targetElement, Dictionary<string, LinkedListNode<ProjectTargetElement>> activeTargets, Dictionary<string, List<TargetSpecification>> targetsWhichRunBeforeByTarget, Dictionary<string, List<TargetSpecification>> targetsWhichRunAfterByTarget)
1194         {
1195             var beforeTargets = _expander.ExpandIntoStringListLeaveEscaped(targetElement.BeforeTargets, ExpanderOptions.ExpandPropertiesAndItems, targetElement.BeforeTargetsLocation);
1196             var afterTargets = _expander.ExpandIntoStringListLeaveEscaped(targetElement.AfterTargets, ExpanderOptions.ExpandPropertiesAndItems, targetElement.AfterTargetsLocation);
1197 
1198             foreach (string beforeTarget in beforeTargets)
1199             {
1200                 string unescapedBeforeTarget = EscapingUtilities.UnescapeAll(beforeTarget);
1201 
1202                 if (activeTargets.ContainsKey(unescapedBeforeTarget))
1203                 {
1204                     List<TargetSpecification> beforeTargetsForTarget = null;
1205                     if (!targetsWhichRunBeforeByTarget.TryGetValue(unescapedBeforeTarget, out beforeTargetsForTarget))
1206                     {
1207                         beforeTargetsForTarget = new List<TargetSpecification>();
1208                         targetsWhichRunBeforeByTarget[unescapedBeforeTarget] = beforeTargetsForTarget;
1209                     }
1210 
1211                     beforeTargetsForTarget.Add(new TargetSpecification(targetElement.Name, targetElement.BeforeTargetsLocation));
1212                 }
1213                 else
1214                 {
1215                     // This is a message, not a warning, because that enables people to speculatively extend the build of a project
1216                     // It's low importance as it's addressed to build authors
1217                     _evaluationLoggingContext.LogComment(MessageImportance.Low, "TargetDoesNotExistBeforeTargetMessage", unescapedBeforeTarget, targetElement.BeforeTargetsLocation.LocationString);
1218                 }
1219             }
1220 
1221             foreach (string afterTarget in afterTargets)
1222             {
1223                 string unescapedAfterTarget = EscapingUtilities.UnescapeAll(afterTarget);
1224 
1225                 if (activeTargets.ContainsKey(unescapedAfterTarget))
1226                 {
1227                     List<TargetSpecification> afterTargetsForTarget = null;
1228                     if (!targetsWhichRunAfterByTarget.TryGetValue(unescapedAfterTarget, out afterTargetsForTarget))
1229                     {
1230                         afterTargetsForTarget = new List<TargetSpecification>();
1231                         targetsWhichRunAfterByTarget[unescapedAfterTarget] = afterTargetsForTarget;
1232                     }
1233 
1234                     afterTargetsForTarget.Add(new TargetSpecification(targetElement.Name, targetElement.AfterTargetsLocation));
1235                 }
1236                 else
1237                 {
1238                     // This is a message, not a warning, because that enables people to speculatively extend the build of a project
1239                     // It's low importance as it's addressed to build authors
1240                     _evaluationLoggingContext.LogComment(MessageImportance.Low, "TargetDoesNotExistAfterTargetMessage", unescapedAfterTarget, targetElement.AfterTargetsLocation.LocationString);
1241                 }
1242             }
1243         }
1244 
1245         /// <summary>
1246         /// Set the built-in properties, most of which are read-only
1247         /// </summary>
AddBuiltInProperties()1248         private ICollection<P> AddBuiltInProperties()
1249         {
1250             string startupDirectory = BuildParameters.StartupDirectory;
1251 
1252             List<P> builtInProperties = new List<P>(19);
1253 
1254             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.toolsVersion, _data.Toolset.ToolsVersion));
1255             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.toolsPath, _data.Toolset.ToolsPath));
1256             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.binPath, _data.Toolset.ToolsPath));
1257             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.startupDirectory, startupDirectory));
1258             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.buildNodeCount, _maxNodeCount.ToString(CultureInfo.CurrentCulture)));
1259             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.programFiles32, FrameworkLocationHelper.programFiles32));
1260             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion));
1261             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.version, MSBuildAssemblyFileVersion.Instance.MajorMinorBuild));
1262 
1263             // Fake OS env variables when not on Windows
1264             if (!NativeMethodsShared.IsWindows)
1265             {
1266                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.osName, NativeMethodsShared.OSName));
1267                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.frameworkToolsRoot, NativeMethodsShared.FrameworkBasePath));
1268             }
1269 
1270 #if RUNTIME_TYPE_NETCORE
1271             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.msbuildRuntimeType, "Core"));
1272 #elif MONO
1273             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.msbuildRuntimeType,
1274                                                         NativeMethodsShared.IsMono ? "Mono" : "Full"));
1275 #else
1276             builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.msbuildRuntimeType, "Full"));
1277 #endif
1278 
1279             if (String.IsNullOrEmpty(_projectRootElement.FullPath))
1280             {
1281                 // If this is an un-saved project, this is as far as we can go
1282                 if (String.IsNullOrEmpty(_projectRootElement.DirectoryPath))
1283                 {
1284                     builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectDirectory, startupDirectory));
1285                 }
1286                 else
1287                 {
1288                     // Solution files based on the old OM end up here.  But they do have a location, which is where the solution was loaded from.
1289                     // We need to set this here otherwise we can't locate any projects the solution refers to.
1290                     builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectDirectory, _projectRootElement.DirectoryPath));
1291                 }
1292             }
1293             else
1294             {
1295                 // Add the MSBuildProjectXXXXX properties, but not the MSBuildFileXXXX ones. Those
1296                 // vary according to the file they're evaluated in, so they have to be dealt with
1297                 // specially in the Expander.
1298                 string projectFile = EscapingUtilities.Escape(Path.GetFileName(_projectRootElement.FullPath));
1299                 string projectFileWithoutExtension = EscapingUtilities.Escape(Path.GetFileNameWithoutExtension(_projectRootElement.FullPath));
1300                 string projectExtension = EscapingUtilities.Escape(Path.GetExtension(_projectRootElement.FullPath));
1301                 string projectFullPath = EscapingUtilities.Escape(_projectRootElement.FullPath);
1302                 string projectDirectory = EscapingUtilities.Escape(_projectRootElement.DirectoryPath);
1303 
1304                 int rootLength = Path.GetPathRoot(projectDirectory).Length;
1305                 string projectDirectoryNoRoot = projectDirectory.Substring(rootLength);
1306                 projectDirectoryNoRoot = FileUtilities.EnsureNoTrailingSlash(projectDirectoryNoRoot);
1307                 projectDirectoryNoRoot = EscapingUtilities.Escape(FileUtilities.EnsureNoLeadingSlash(projectDirectoryNoRoot));
1308 
1309                 // ReservedPropertyNames.projectDefaultTargets is already set
1310                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectFile, projectFile));
1311                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectName, projectFileWithoutExtension));
1312                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectExtension, projectExtension));
1313                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectFullPath, projectFullPath));
1314                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectDirectory, projectDirectory));
1315                 builtInProperties.Add(SetBuiltInProperty(ReservedPropertyNames.projectDirectoryNoRoot, projectDirectoryNoRoot));
1316             }
1317 
1318             return builtInProperties;
1319         }
1320 
1321         /// <summary>
1322         /// Pull in all the environment into our property bag
1323         /// </summary>
AddEnvironmentProperties()1324         private ICollection<P> AddEnvironmentProperties()
1325         {
1326             List<P> environmentPropertiesList = new List<P>(_environmentProperties.Count);
1327 
1328             foreach (ProjectPropertyInstance environmentProperty in _environmentProperties)
1329             {
1330                 P property = _data.SetProperty(environmentProperty.Name, ((IProperty)environmentProperty).EvaluatedValueEscaped, false /* NOT global property */, false /* may NOT be a reserved name */);
1331                 environmentPropertiesList.Add(property);
1332             }
1333 
1334             return environmentPropertiesList;
1335         }
1336 
1337         /// <summary>
1338         /// Put all the toolset's properties into our property bag
1339         /// </summary>
AddToolsetProperties()1340         private ICollection<P> AddToolsetProperties()
1341         {
1342             List<P> toolsetProperties = new List<P>(_data.Toolset.Properties.Count);
1343 
1344             foreach (ProjectPropertyInstance toolsetProperty in _data.Toolset.Properties.Values)
1345             {
1346                 P property = _data.SetProperty(toolsetProperty.Name, ((IProperty)toolsetProperty).EvaluatedValueEscaped, false /* NOT global property */, false /* may NOT be a reserved name */);
1347                 toolsetProperties.Add(property);
1348             }
1349 
1350             if (_data.SubToolsetVersion == null)
1351             {
1352                 // In previous versions of MSBuild, there is almost always a subtoolset that adds a VisualStudioVersion property.  Since there
1353                 // is most likely not a subtoolset now, we need to add VisualStudioVersion if its not already a property.
1354                 if (!_data.Properties.Contains(Constants.VisualStudioVersionPropertyName))
1355                 {
1356                     P subToolsetVersionProperty = _data.SetProperty(Constants.VisualStudioVersionPropertyName, MSBuildConstants.CurrentVisualStudioVersion, false /* NOT global property */, false /* may NOT be a reserved name */);
1357                     toolsetProperties.Add(subToolsetVersionProperty);
1358                 }
1359             }
1360             else
1361             {
1362                 SubToolset subToolset = null;
1363 
1364                 // Make the subtoolset version itself available as a property -- but only if it's not already set.
1365                 // Because some people may be depending on this value even if there isn't a matching sub-toolset,
1366                 // set the property even if there is no matching sub-toolset.
1367                 if (!_data.Properties.Contains(Constants.SubToolsetVersionPropertyName))
1368                 {
1369                     P subToolsetVersionProperty = _data.SetProperty(Constants.SubToolsetVersionPropertyName, _data.SubToolsetVersion, false /* NOT global property */, false /* may NOT be a reserved name */);
1370                     toolsetProperties.Add(subToolsetVersionProperty);
1371                 }
1372 
1373                 if (_data.Toolset.SubToolsets.TryGetValue(_data.SubToolsetVersion, out subToolset))
1374                 {
1375                     foreach (ProjectPropertyInstance subToolsetProperty in subToolset.Properties.Values)
1376                     {
1377                         P property = _data.SetProperty(subToolsetProperty.Name, ((IProperty)subToolsetProperty).EvaluatedValueEscaped, false /* NOT global property */, false /* may NOT be a reserved name */);
1378                         toolsetProperties.Add(property);
1379                     }
1380                 }
1381             }
1382 
1383             return toolsetProperties;
1384         }
1385 
1386         /// <summary>
1387         /// Put all the global properties into our property bag
1388         /// </summary>
AddGlobalProperties()1389         private ICollection<P> AddGlobalProperties()
1390         {
1391             if (_data.GlobalPropertiesDictionary == null)
1392             {
1393                 return Array.Empty<P>();
1394             }
1395 
1396             List<P> globalProperties = new List<P>(_data.GlobalPropertiesDictionary.Count);
1397 
1398             foreach (ProjectPropertyInstance globalProperty in _data.GlobalPropertiesDictionary)
1399             {
1400                 P property = _data.SetProperty(globalProperty.Name, ((IProperty)globalProperty).EvaluatedValueEscaped, true /* IS global property */, false /* may NOT be a reserved name */);
1401                 globalProperties.Add(property);
1402             }
1403 
1404             return globalProperties;
1405         }
1406 
1407         /// <summary>
1408         /// Set a built-in property in the supplied bag.
1409         /// NOT to be used for properties originating in XML.
1410         /// NOT to be used for global properties.
1411         /// NOT to be used for environment properties.
1412         /// </summary>
SetBuiltInProperty(string name, string evaluatedValueEscaped)1413         private P SetBuiltInProperty(string name, string evaluatedValueEscaped)
1414         {
1415             P property = _data.SetProperty(name, evaluatedValueEscaped, false /* NOT global property */, true /* OK to be a reserved name */);
1416             return property;
1417         }
1418 
1419         /// <summary>
1420         /// Evaluate a single ProjectPropertyElement and update the data as appropriate
1421         /// </summary>
EvaluatePropertyElement(ProjectPropertyElement propertyElement)1422         private void EvaluatePropertyElement(ProjectPropertyElement propertyElement)
1423         {
1424             using (_evaluationProfiler.TrackElement(propertyElement))
1425             {
1426                 // Global properties cannot be overridden.  We silently ignore them if we try.  Legacy behavior.
1427                 // That is, unless this global property has been explicitly labeled as one that we want to treat as overridable for the duration
1428                 // of this project (or import).
1429                 if (
1430                         ((IDictionary<string, ProjectPropertyInstance>)_data.GlobalPropertiesDictionary).ContainsKey(propertyElement.Name) &&
1431                         !_data.GlobalPropertiesToTreatAsLocal.Contains(propertyElement.Name)
1432                     )
1433                 {
1434                     return;
1435                 }
1436 
1437                 if (!EvaluateConditionCollectingConditionedProperties(propertyElement, ExpanderOptions.ExpandProperties, ParserOptions.AllowProperties))
1438                 {
1439                     return;
1440                 }
1441 
1442                 // Set the name of the property we are currently evaluating so when we are checking to see if we want to add the property to the list of usedUninitialized properties we can not add the property if
1443                 // it is the same as what we are setting the value on. Note: This needs to be set before we expand the property we are currently setting.
1444                 _expander.UsedUninitializedProperties.CurrentlyEvaluatingPropertyElementName = propertyElement.Name;
1445 
1446                 string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(propertyElement.Value, ExpanderOptions.ExpandProperties, propertyElement.Location);
1447 
1448                 // If we are going to set a property to a value other than null or empty we need to check to see if it has been used
1449                 // during evaluation.
1450                 if (evaluatedValue.Length > 0 && _expander.WarnForUninitializedProperties)
1451                 {
1452                     // Is the property we are currently setting in the list of properties which have been used but not initialized
1453                     IElementLocation elementWhichUsedProperty = null;
1454                     bool isPropertyInList = _expander.UsedUninitializedProperties.Properties.TryGetValue(propertyElement.Name, out elementWhichUsedProperty);
1455 
1456                     if (isPropertyInList)
1457                     {
1458                         // Once we are going to warn for a property once, remove it from the list so we do not add it again.
1459                         _expander.UsedUninitializedProperties.Properties.Remove(propertyElement.Name);
1460                         _evaluationLoggingContext.LogWarning(null, new BuildEventFileInfo(propertyElement.Location), "UsedUninitializedProperty", propertyElement.Name, elementWhichUsedProperty.LocationString);
1461                     }
1462                 }
1463 
1464                 _expander.UsedUninitializedProperties.CurrentlyEvaluatingPropertyElementName = null;
1465 
1466                 P predecessor = _data.GetProperty(propertyElement.Name);
1467 
1468                 P property = _data.SetProperty(propertyElement, evaluatedValue, predecessor);
1469 
1470                 if (predecessor != null)
1471                 {
1472                     LogPropertyReassignment(predecessor, property, propertyElement.Location.LocationString);
1473                 }
1474             }
1475         }
1476 
LogPropertyReassignment(P predecessor, P property, string location)1477         private void LogPropertyReassignment(P predecessor, P property, string location)
1478         {
1479             string newValue = property.EvaluatedValue;
1480             string oldValue = predecessor.EvaluatedValue;
1481 
1482             if (newValue != oldValue)
1483             {
1484                 _evaluationLoggingContext.LogComment(
1485                     MessageImportance.Low,
1486                     "PropertyReassignment",
1487                     property.Name,
1488                     newValue,
1489                     oldValue,
1490                     location);
1491             }
1492         }
1493 
EvaluateItemElement(bool itemGroupConditionResult, ProjectItemElement itemElement, LazyItemEvaluator<P, I, M, D> lazyEvaluator)1494         private void EvaluateItemElement(bool itemGroupConditionResult, ProjectItemElement itemElement, LazyItemEvaluator<P, I, M, D> lazyEvaluator)
1495         {
1496             bool itemConditionResult;
1497             if (lazyEvaluator != null)
1498             {
1499                 itemConditionResult = lazyEvaluator.EvaluateConditionWithCurrentState(itemElement, ExpanderOptions.ExpandPropertiesAndItems, ParserOptions.AllowPropertiesAndItemLists);
1500             }
1501             else
1502             {
1503                 itemConditionResult = EvaluateCondition(itemElement, ExpanderOptions.ExpandPropertiesAndItems, ParserOptions.AllowPropertiesAndItemLists);
1504             }
1505 
1506             if (!itemConditionResult && !(_data.ShouldEvaluateForDesignTime && _data.CanEvaluateElementsWithFalseConditions))
1507             {
1508                 return;
1509             }
1510 
1511             if (lazyEvaluator != null)
1512             {
1513                 var conditionResult = itemGroupConditionResult && itemConditionResult;
1514 
1515                 lazyEvaluator.ProcessItemElement(_projectRootElement.DirectoryPath, itemElement, conditionResult);
1516 
1517                 if (conditionResult)
1518                 {
1519                     RecordEvaluatedItemElement(itemElement);
1520                 }
1521 
1522                 return;
1523             }
1524 
1525             // legacy, dead code beyond this point. Runs only if the lazy evaluator is null. Also, the interpretation of Remove is not implemented
1526             if (!string.IsNullOrEmpty(itemElement.Include))
1527             {
1528                 EvaluateItemElementInclude(itemGroupConditionResult, itemConditionResult, itemElement);
1529             }
1530             else if (!string.IsNullOrEmpty(itemElement.Update))
1531             {
1532                 EvaluateItemElementUpdate(itemElement);
1533             }
1534             else
1535             {
1536                 ErrorUtilities.ThrowInternalError("Unexpected item operation");
1537             }
1538         }
1539 
EvaluateItemElementUpdate(ProjectItemElement itemElement)1540         private void EvaluateItemElementUpdate(ProjectItemElement itemElement)
1541         {
1542             RecordEvaluatedItemElement(itemElement);
1543 
1544             var expandedItemSet =
1545                 new HashSet<string>(
1546                     ExpressionShredder.SplitSemiColonSeparatedList
1547                         (
1548                             _expander.ExpandIntoStringLeaveEscaped(itemElement.Update, ExpanderOptions.ExpandPropertiesAndItems, itemElement.Location)
1549                         )
1550                         .SelectMany(i => EngineFileUtilities.GetFileListEscaped(_projectRootElement.DirectoryPath, i))
1551                         .Select(EscapingUtilities.UnescapeAll));
1552 
1553             var itemsToUpdate = _data.GetItems(itemElement.ItemType).Where(i => expandedItemSet.Contains(i.EvaluatedInclude)).ToList();
1554 
1555             DecorateItemsWithMetadataFromProjectItemElement(itemElement, itemsToUpdate);
1556         }
1557 
1558         /// <summary>
1559         /// Evaluate a single ProjectItemElement into zero or more items.
1560         /// If specified, or if the condition on the item itself is false, only gathers the result into the list of items-ignoring-condition,
1561         /// and not into the real list of items.
1562         /// </summary>
EvaluateItemElementInclude(bool itemGroupConditionResult, bool itemConditionResult, ProjectItemElement itemElement)1563         private void EvaluateItemElementInclude(bool itemGroupConditionResult, bool itemConditionResult, ProjectItemElement itemElement)
1564         {
1565             // Paths in items are evaluated relative to the outer project file, rather than relative to any targets file they may be contained in
1566             IList<I> items = CreateItemsFromInclude(_projectRootElement.DirectoryPath, itemElement, _itemFactory, itemElement.Include, _expander);
1567 
1568             // STEP 4: Evaluate, split, expand and subtract any Exclude
1569             if (itemElement.Exclude.Length > 0)
1570             {
1571                 string evaluatedExclude = _expander.ExpandIntoStringLeaveEscaped(itemElement.Exclude, ExpanderOptions.ExpandPropertiesAndItems, itemElement.ExcludeLocation);
1572 
1573                 if (evaluatedExclude.Length > 0)
1574                 {
1575                     var excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedExclude);
1576 
1577                     HashSet<string> excludes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
1578 
1579                     foreach (string excludeSplit in excludeSplits)
1580                     {
1581                         string[] excludeSplitFiles = EngineFileUtilities.GetFileListEscaped(_projectRootElement.DirectoryPath, excludeSplit);
1582 
1583                         foreach (string excludeSplitFile in excludeSplitFiles)
1584                         {
1585                             excludes.Add(EscapingUtilities.UnescapeAll(excludeSplitFile));
1586                         }
1587                     }
1588 
1589                     List<I> remainingItems = new List<I>();
1590 
1591                     for (int i = 0; i < items.Count; i++)
1592                     {
1593                         if (!excludes.Contains(items[i].EvaluatedInclude))
1594                         {
1595                             remainingItems.Add(items[i]);
1596                         }
1597                     }
1598 
1599                     items = remainingItems;
1600                 }
1601             }
1602 
1603             // STEP 5: Evaluate each metadata XML and apply them to each item we have so far
1604             DecorateItemsWithMetadataFromProjectItemElement(itemElement, items);
1605 
1606             // FINALLY: Add the items to the project
1607             if (itemConditionResult && itemGroupConditionResult)
1608             {
1609                 RecordEvaluatedItemElement(itemElement);
1610 
1611                 foreach (I item in items)
1612                 {
1613                     _data.AddItem(item);
1614 
1615                     if (_data.ShouldEvaluateForDesignTime)
1616                     {
1617                         _data.AddToAllEvaluatedItemsList(item);
1618                     }
1619                 }
1620             }
1621 
1622             if (_data.ShouldEvaluateForDesignTime)
1623             {
1624                 foreach (I item in items)
1625                 {
1626                     _data.AddItemIgnoringCondition(item);
1627                 }
1628             }
1629         }
1630 
DecorateItemsWithMetadataFromProjectItemElement(ProjectItemElement itemElement, IList<I> items)1631         private void DecorateItemsWithMetadataFromProjectItemElement(ProjectItemElement itemElement, IList<I> items)
1632         {
1633             if (itemElement.HasMetadata)
1634             {
1635                 ////////////////////////////////////////////////////
1636                 // UNDONE: Implement batching here.
1637                 //
1638                 // We want to allow built-in metadata in metadata values here.
1639                 // For example, so that an Idl file can specify that its Tlb output should be named %(Filename).tlb.
1640                 //
1641                 // In other words, we want batching. However, we won't need to go to the trouble of using the regular batching code!
1642                 // That's because that code is all about grouping into buckets of similar items. In this context, we're not
1643                 // invoking a task, and it's fine to process each item individually, which will always give the correct results.
1644                 //
1645                 // For the CTP, to make the minimal change, we will not do this quite correctly.
1646                 //
1647                 // We will do this:
1648                 // -- check whether any metadata values or their conditions contain any bare built-in metadata expressions,
1649                 //    or whether they contain any custom metadata && the Include involved an @(itemlist) expression.
1650                 // -- if either case is found, we go ahead and evaluate all the metadata separately for each item.
1651                 // -- otherwise we can do the old thing (evaluating all metadata once then applying to all items)
1652                 //
1653                 // This algorithm gives the correct results except when:
1654                 // -- batchable expressions exist on the include, exclude, or condition on the item element itself
1655                 //
1656                 // It means that 99% of cases still go through the old code, which is best for the CTP.
1657                 // When we ultimately implement this correctly, we should make sure we optimize for the case of very many items
1658                 // and little metadata, none of which varies between items.
1659                 List<string> values = new List<string>(itemElement.Count);
1660 
1661                 foreach (ProjectMetadataElement metadatumElement in itemElement.Metadata)
1662                 {
1663                     values.Add(metadatumElement.Value);
1664                     values.Add(metadatumElement.Condition);
1665                 }
1666 
1667                 ItemsAndMetadataPair itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values);
1668 
1669                 bool needToProcessItemsIndividually = false;
1670 
1671                 if (itemsAndMetadataFound.Metadata != null && itemsAndMetadataFound.Metadata.Values.Count > 0)
1672                 {
1673                     // If there is bare metadata of any kind, and the Include involved an item list, we should
1674                     // run items individually, as even non-built-in metadata might differ between items
1675                     List<string> include = new List<string>();
1676                     include.Add(itemElement.Include);
1677                     ItemsAndMetadataPair itemsAndMetadataFromInclude = ExpressionShredder.GetReferencedItemNamesAndMetadata(include);
1678 
1679                     if (itemsAndMetadataFromInclude.Items != null && itemsAndMetadataFromInclude.Items.Count > 0)
1680                     {
1681                         needToProcessItemsIndividually = true;
1682                     }
1683                     else
1684                     {
1685                         // If there is bare built-in metadata, we must always run items individually, as that almost
1686                         // always differs between items.
1687 
1688                         // UNDONE: When batching is implemented for real, we need to make sure that
1689                         // item definition metadata is included in all metadata operations during evaluation
1690                         if (itemsAndMetadataFound.Metadata.Values.Count > 0)
1691                         {
1692                             needToProcessItemsIndividually = true;
1693                         }
1694                     }
1695                 }
1696 
1697                 if (needToProcessItemsIndividually)
1698                 {
1699                     foreach (I item in items)
1700                     {
1701                         _expander.Metadata = item;
1702 
1703                         foreach (ProjectMetadataElement metadatumElement in itemElement.Metadata)
1704                         {
1705                             if (!EvaluateCondition(metadatumElement, ExpanderOptions.ExpandAll, ParserOptions.AllowAll))
1706                             {
1707                                 continue;
1708                             }
1709 
1710                             string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadatumElement.Value, ExpanderOptions.ExpandAll, metadatumElement.Location);
1711 
1712                             item.SetMetadata(metadatumElement, evaluatedValue);
1713                         }
1714                     }
1715 
1716                     // End of legal area for metadata expressions.
1717                     _expander.Metadata = null;
1718                 }
1719 
1720                 // End of pseudo batching
1721                 ////////////////////////////////////////////////////
1722                 // Start of old code
1723                 else
1724                 {
1725                     // Metadata expressions are allowed here.
1726                     // Temporarily gather and expand these in a table so they can reference other metadata elements above.
1727                     EvaluatorMetadataTable metadataTable = new EvaluatorMetadataTable(itemElement.ItemType);
1728                     _expander.Metadata = metadataTable;
1729 
1730                     // Also keep a list of everything so we can get the predecessor objects correct.
1731                     List<Pair<ProjectMetadataElement, string>> metadataList = new List<Pair<ProjectMetadataElement, string>>();
1732 
1733                     foreach (ProjectMetadataElement metadatumElement in itemElement.Metadata)
1734                     {
1735                         // Because of the checking above, it should be safe to expand metadata in conditions; the condition
1736                         // will be true for either all the items or none
1737                         if (!EvaluateCondition(metadatumElement, ExpanderOptions.ExpandAll, ParserOptions.AllowAll))
1738                         {
1739                             continue;
1740                         }
1741 
1742                         string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadatumElement.Value, ExpanderOptions.ExpandAll, metadatumElement.Location);
1743 
1744                         metadataTable.SetValue(metadatumElement, evaluatedValue);
1745                         metadataList.Add(new Pair<ProjectMetadataElement, string>(metadatumElement, evaluatedValue));
1746                     }
1747 
1748                     // Apply those metadata to each item
1749                     // Note that several items could share the same metadata objects
1750 
1751                     // Set all the items at once to make a potential copy-on-write optimization possible.
1752                     // This is valuable in the case where one item element evaluates to
1753                     // many items (either by semicolon or wildcards)
1754                     // and that item also has the same piece/s of metadata for each item.
1755                     _itemFactory.SetMetadata(metadataList, items);
1756 
1757                     // End of legal area for metadata expressions.
1758                     _expander.Metadata = null;
1759                 }
1760             }
1761         }
1762 
1763         /// <summary>
1764         /// Evaluates an itemdefinition element, updating the definitions library.
1765         /// </summary>
EvaluateItemDefinitionElement(ProjectItemDefinitionElement itemDefinitionElement)1766         private void EvaluateItemDefinitionElement(ProjectItemDefinitionElement itemDefinitionElement)
1767         {
1768             // Get matching existing item definition, if any.
1769             IItemDefinition<M> itemDefinition = _data.GetItemDefinition(itemDefinitionElement.ItemType);
1770 
1771             // The expander should use the metadata from this item definition for further expansion, if any.
1772             // Otherwise, use a temporary, empty table.
1773             if (itemDefinition != null)
1774             {
1775                 _expander.Metadata = itemDefinition;
1776             }
1777             else
1778             {
1779                 _expander.Metadata = new EvaluatorMetadataTable(itemDefinitionElement.ItemType);
1780             }
1781 
1782             if (EvaluateCondition(itemDefinitionElement, ExpanderOptions.ExpandPropertiesAndMetadata, ParserOptions.AllowPropertiesAndCustomMetadata))
1783             {
1784                 if (itemDefinition == null)
1785                 {
1786                     itemDefinition = _data.AddItemDefinition(itemDefinitionElement.ItemType);
1787                     _expander.Metadata = itemDefinition;
1788                 }
1789 
1790                 foreach (ProjectMetadataElement metadataElement in itemDefinitionElement.Metadata)
1791                 {
1792                     if (EvaluateCondition(metadataElement, ExpanderOptions.ExpandPropertiesAndMetadata, ParserOptions.AllowPropertiesAndCustomMetadata))
1793                     {
1794                         string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadataElement.Value, ExpanderOptions.ExpandPropertiesAndCustomMetadata, itemDefinitionElement.Location);
1795 
1796                         M predecessor = itemDefinition.GetMetadata(metadataElement.Name);
1797 
1798                         M metadatum = itemDefinition.SetMetadata(metadataElement, evaluatedValue, predecessor);
1799 
1800                         if (_data.ShouldEvaluateForDesignTime)
1801                         {
1802                             _data.AddToAllEvaluatedItemDefinitionMetadataList(metadatum);
1803                         }
1804                     }
1805                 }
1806             }
1807 
1808             // End of valid area for metadata expansion.
1809             _expander.Metadata = null;
1810         }
1811 
1812         /// <summary>
1813         /// Evaluates an import element.
1814         /// If the condition is true, loads the import and continues the pass.
1815         /// </summary>
1816         /// <remarks>
1817         /// UNDONE: Protect against overflowing the stack by having too many nested imports.
1818         /// </remarks>
EvaluateImportElement(string directoryOfImportingFile, ProjectImportElement importElement)1819         private void EvaluateImportElement(string directoryOfImportingFile, ProjectImportElement importElement)
1820         {
1821             using (_evaluationProfiler.TrackElement(importElement))
1822             {
1823                 List<ProjectRootElement> importedProjectRootElements = ExpandAndLoadImports(directoryOfImportingFile, importElement);
1824 
1825                 foreach (ProjectRootElement importedProjectRootElement in importedProjectRootElements)
1826                 {
1827                     _data.RecordImport(importElement, importedProjectRootElement, importedProjectRootElement.Version);
1828 
1829                     PerformDepthFirstPass(importedProjectRootElement);
1830                 }
1831             }
1832         }
1833 
1834         /// <summary>
1835         /// Evaluates an ImportGroup element.
1836         /// If the condition is true, evaluates the contained imports and continues the pass.
1837         /// </summary>
1838         /// <remarks>
1839         /// UNDONE: Protect against overflowing the stack by having too many nested imports.
1840         /// </remarks>
EvaluateImportGroupElement(string directoryOfImportingFile, ProjectImportGroupElement importGroupElement)1841         private void EvaluateImportGroupElement(string directoryOfImportingFile, ProjectImportGroupElement importGroupElement)
1842         {
1843             using (_evaluationProfiler.TrackElement(importGroupElement))
1844             {
1845                 if (EvaluateConditionCollectingConditionedProperties(importGroupElement, ExpanderOptions.ExpandProperties, ParserOptions.AllowProperties, _projectRootElementCache))
1846                 {
1847                     foreach (ProjectImportElement importElement in importGroupElement.Imports)
1848                     {
1849                         EvaluateImportElement(directoryOfImportingFile, importElement);
1850                     }
1851                 }
1852             }
1853         }
1854 
1855         /// <summary>
1856         /// Choose does not accept a condition.
1857         /// </summary>
1858         /// <remarks>
1859         /// We enter here in both the property and item passes, since Chooses can contain both.
1860         /// However, we only evaluate the When conditions on the first pass, so we only pulse
1861         /// those states on that pass. On the other pass, it's as if they're not there.
1862         /// </remarks>
EvaluateChooseElement(ProjectChooseElement chooseElement)1863         private void EvaluateChooseElement(ProjectChooseElement chooseElement)
1864         {
1865             using (_evaluationProfiler.TrackElement(chooseElement))
1866             {
1867                 foreach (ProjectWhenElement whenElement in chooseElement.WhenElements)
1868                 {
1869                     if (EvaluateConditionCollectingConditionedProperties(whenElement, ExpanderOptions.ExpandProperties, ParserOptions.AllowProperties))
1870                     {
1871                         EvaluateWhenOrOtherwiseChildren(whenElement.Children);
1872                         return;
1873                     }
1874                 }
1875 
1876                 // "Otherwise" elements never have a condition
1877                 if (chooseElement.OtherwiseElement != null)
1878                 {
1879                     EvaluateWhenOrOtherwiseChildren(chooseElement.OtherwiseElement.Children);
1880                 }
1881             }
1882         }
1883 
1884         /// <summary>
1885         /// Evaluates the children of a When or Choose.
1886         /// Returns true if the condition was true, so subsequent
1887         /// WhenElements and Otherwise can be skipped.
1888         /// </summary>
EvaluateWhenOrOtherwiseChildren(IEnumerable<ProjectElement> children)1889         private bool EvaluateWhenOrOtherwiseChildren(IEnumerable<ProjectElement> children)
1890         {
1891             foreach (ProjectElement element in children)
1892             {
1893                 using (_evaluationProfiler.TrackElement(element))
1894                 {
1895                     ProjectPropertyGroupElement propertyGroup = element as ProjectPropertyGroupElement;
1896 
1897                     if (propertyGroup != null)
1898                     {
1899                         EvaluatePropertyGroupElement(propertyGroup);
1900                         continue;
1901                     }
1902 
1903                     ProjectItemGroupElement itemGroup = element as ProjectItemGroupElement;
1904 
1905                     if (itemGroup != null)
1906                     {
1907                         _itemGroupElements.Add(itemGroup);
1908                         continue;
1909                     }
1910 
1911                     ProjectChooseElement choose = element as ProjectChooseElement;
1912 
1913                     if (choose != null)
1914                     {
1915                         EvaluateChooseElement(choose);
1916                         continue;
1917                     }
1918 
1919                     ErrorUtilities.ThrowInternalError("Unexpected child type");
1920                 }
1921             }
1922 
1923             return true;
1924         }
1925 
1926         /// <summary>
1927         /// Expands and loads project imports.
1928         /// <remarks>
1929         /// Imports may contain references to "projectImportSearchPaths" defined in the app.config
1930         /// toolset section. If this is the case, this method will search for the imported project
1931         /// in those additional paths if the default fails.
1932         /// </remarks>
1933         /// </summary>
ExpandAndLoadImports(string directoryOfImportingFile, ProjectImportElement importElement)1934         private List<ProjectRootElement> ExpandAndLoadImports(string directoryOfImportingFile, ProjectImportElement importElement)
1935         {
1936             var fallbackSearchPathMatch = _data.Toolset.GetProjectImportSearchPaths(importElement.Project);
1937 
1938             // no reference or we need to lookup only the default path,
1939             // so, use the Import path
1940             if (fallbackSearchPathMatch.Equals(ProjectImportPathMatch.None))
1941             {
1942                 List<ProjectRootElement> projects;
1943                 ExpandAndLoadImportsFromUnescapedImportExpressionConditioned(directoryOfImportingFile, importElement, out projects);
1944                 return projects;
1945             }
1946 
1947             // Note: Any property defined in the <projectImportSearchPaths> section can be replaced, MSBuildExtensionsPath
1948             // is used here as an example of behavior.
1949             // $(MSBuildExtensionsPath*) usually resolves to a single value, single default path
1950             //
1951             //     Eg. <Import Project='$(MSBuildExtensionsPath)\foo\extn.proj' />
1952             //
1953             // But this feature allows that when it is used in an Import element, it will behave as a "search path", meaning
1954             // that the relative project path "foo\extn.proj" will be searched for, in more than one location.
1955             // Essentially, we will try to load that project file by trying multiple values (search paths) for the
1956             // $(MSBuildExtensionsPath*) property.
1957             //
1958             // The various paths tried, in order are:
1959             //
1960             // 1. The value of the MSBuildExtensionsPath* property
1961             //
1962             // 2. Search paths available in the current toolset (via toolset.ImportPropertySearchPathsTable).
1963             //    That may be loaded from app.config with a definition like:
1964             //
1965             //    <toolset .. >
1966             //      <projectImportSearchPaths>
1967             //          <searchPaths os="osx">
1968             //              <property name="MSBuildExtensionsPath" value="/Library/Frameworks/Mono.framework/External/xbuild/;/tmp/foo"/>
1969             //              <property name="MSBuildExtensionsPath32" value="/Library/Frameworks/Mono.framework/External/xbuild/"/>
1970             //              <property name="MSBuildExtensionsPath64" value="/Library/Frameworks/Mono.framework/External/xbuild/"/>
1971             //          </searchPaths>
1972             //      </projectImportSearchPaths>
1973             //    </toolset>
1974             //
1975             // This is available only when used in an Import element and it's Condition. So, the following common pattern
1976             // would work:
1977             //
1978             //      <Import Project="$(MSBuildExtensionsPath)\foo\extn.proj" Condition="'Exists('$(MSBuildExtensionsPath)\foo\extn.proj')'" />
1979             //
1980             // The value of the MSBuildExtensionsPath* property, will always be "visible" with it's default value, example, when read or
1981             // referenced anywhere else. This is a very limited support, so, it doesn't come in to effect if the explicit reference to
1982             // the $(MSBuildExtensionsPath) property is not present in the Project attribute of the Import element. So, the following is
1983             // not supported:
1984             //
1985             //      <PropertyGroup><ProjectPathForImport>$(MSBuildExtensionsPath)\foo\extn.proj</ProjectPathForImport></PropertyGroup>
1986             //      <Import Project='$(ProjectPathForImport)' />
1987             //
1988 
1989             // Adding the value of $(MSBuildExtensionsPath*) property to the list of search paths
1990             var prop = _data.GetProperty(fallbackSearchPathMatch.PropertyName);
1991 
1992             var pathsToSearch = new string[fallbackSearchPathMatch.SearchPaths.Count + 1];
1993             pathsToSearch[0] = prop?.EvaluatedValue;                       // The actual value of the property, with no fallbacks
1994             fallbackSearchPathMatch.SearchPaths.CopyTo(pathsToSearch, 1);  // The list of fallbacks, in order
1995 
1996             string extensionPropertyRefAsString = fallbackSearchPathMatch.MsBuildPropertyFormat;
1997 
1998             _evaluationLoggingContext.LogComment(MessageImportance.Low, "SearchPathsForMSBuildExtensionsPath",
1999                                         extensionPropertyRefAsString,
2000                                         String.Join(";", pathsToSearch));
2001 
2002             bool atleastOneExactFilePathWasLookedAtAndNotFound = false;
2003 
2004             // If there are wildcards in the Import, a list of all the matches from all import search
2005             // paths will be returned (union of all files that match).
2006             var allProjects = new List<ProjectRootElement>();
2007             bool containsWildcards = FileMatcher.HasWildcards(importElement.Project);
2008 
2009             // Try every extension search path, till we get a Hit:
2010             // 1. 1 or more project files loaded
2011             // 2. 1 or more project files *found* but ignored (like circular, self imports)
2012             foreach (var extensionPath in pathsToSearch)
2013             {
2014                 // In the rare case that the property we've enabled for search paths hasn't been defined
2015                 // we will skip it, but continue with other paths in the fallback order.
2016                 if (string.IsNullOrEmpty(extensionPath))
2017                     continue;
2018 
2019                 string extensionPathExpanded = _data.ExpandString(extensionPath);
2020 
2021                 if (!_fallbackSearchPathsCache.DirectoryExists(extensionPathExpanded))
2022                 {
2023                     continue;
2024                 }
2025 
2026                 var newExpandedCondition = importElement.Condition.Replace(extensionPropertyRefAsString, extensionPathExpanded, StringComparison.OrdinalIgnoreCase);
2027                 if (!EvaluateConditionCollectingConditionedProperties(importElement, newExpandedCondition, ExpanderOptions.ExpandProperties, ParserOptions.AllowProperties,
2028                             _projectRootElementCache))
2029                 {
2030                     continue;
2031                 }
2032 
2033                 var newExpandedImportPath = importElement.Project.Replace(extensionPropertyRefAsString, extensionPathExpanded, StringComparison.OrdinalIgnoreCase);
2034                 _evaluationLoggingContext.LogComment(MessageImportance.Low, "TryingExtensionsPath", newExpandedImportPath, extensionPathExpanded);
2035 
2036                 List<ProjectRootElement> projects;
2037                 var result = ExpandAndLoadImportsFromUnescapedImportExpression(directoryOfImportingFile, importElement, newExpandedImportPath, false, out projects);
2038 
2039                 if (result == LoadImportsResult.ProjectsImported)
2040                 {
2041                     // If we don't have a wildcard and we had a match, we're done.
2042                     if (!containsWildcards)
2043                     {
2044                         return projects;
2045                     }
2046 
2047                     allProjects.AddRange(projects);
2048                 }
2049 
2050                 if (result == LoadImportsResult.FoundFilesToImportButIgnored)
2051                 {
2052                     // Circular, Self import cases are usually ignored
2053                     // Since we have a semi-success here, we stop looking at
2054                     // other paths
2055 
2056                     // If we don't have a wildcard and we had a match, we're done.
2057                     if (!containsWildcards)
2058                     {
2059                         return projects;
2060                     }
2061 
2062                     allProjects.AddRange(projects);
2063                 }
2064 
2065                 if (result == LoadImportsResult.TriedToImportButFileNotFound)
2066                 {
2067                     atleastOneExactFilePathWasLookedAtAndNotFound = true;
2068                 }
2069                 // else if (result == LoadImportsResult.ImportExpressionResolvedToNothing) {}
2070             }
2071 
2072             // Found at least one project file for the Import, but no projects were loaded
2073             // atleastOneExactFilePathWasLookedAtAndNotFound would be false, eg, if the expression
2074             // was a wildcard and it resolved to zero files!
2075             if (allProjects.Count == 0 &&
2076                 atleastOneExactFilePathWasLookedAtAndNotFound &&
2077                 (_loadSettings & ProjectLoadSettings.IgnoreMissingImports) == 0)
2078             {
2079                 ThrowForImportedProjectWithSearchPathsNotFound(fallbackSearchPathMatch, importElement);
2080             }
2081 
2082             return allProjects;
2083         }
2084 
2085         /// <summary>
2086         /// Load and parse the specified project import, which may have wildcards,
2087         /// into one or more ProjectRootElements, if it's Condition evaluates to true
2088         /// Caches the parsed import into the provided collection, so future
2089         /// requests can be satisfied without re-parsing it.
2090         /// </summary>
ExpandAndLoadImportsFromUnescapedImportExpressionConditioned(string directoryOfImportingFile, ProjectImportElement importElement, out List<ProjectRootElement> projects, bool throwOnFileNotExistsError = true)2091         private void ExpandAndLoadImportsFromUnescapedImportExpressionConditioned(string directoryOfImportingFile,
2092             ProjectImportElement importElement, out List<ProjectRootElement> projects,
2093             bool throwOnFileNotExistsError = true)
2094         {
2095             if (!EvaluateConditionCollectingConditionedProperties(importElement, ExpanderOptions.ExpandProperties,
2096                 ParserOptions.AllowProperties, _projectRootElementCache))
2097             {
2098                 if (_logProjectImportedEvents)
2099                 {
2100                     // Expand the expression for the Log.  Since we know the condition evaluated to false, leave unexpandable properties in the condition so as not to cause an error
2101                     string expanded = _expander.ExpandIntoStringAndUnescape(importElement.Condition, ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError, importElement.ConditionLocation);
2102 
2103                     ProjectImportedEventArgs eventArgs = new ProjectImportedEventArgs(
2104                         importElement.Location.Line,
2105                         importElement.Location.Column,
2106                         ResourceUtilities.GetResourceString("ProjectImportSkippedFalseCondition"),
2107                         importElement.Project,
2108                         importElement.ContainingProject.FullPath,
2109                         importElement.Location.Line,
2110                         importElement.Location.Column,
2111                         importElement.Condition,
2112                         expanded)
2113                     {
2114                         BuildEventContext = _evaluationLoggingContext.BuildEventContext,
2115                         UnexpandedProject = importElement.Project,
2116                         ProjectFile = importElement.ContainingProject.FullPath
2117                     };
2118 
2119                     _evaluationLoggingContext.LogBuildEvent(eventArgs);
2120                 }
2121                 projects = new List<ProjectRootElement>();
2122                 return;
2123             }
2124 
2125             string project = importElement.Project;
2126 
2127             if (importElement.ParsedSdkReference != null)
2128             {
2129                 // Try to get the path to the solution and project being built. The solution path is not directly known
2130                 // in MSBuild. It is passed in as a property either by the VS project system or by MSBuild's solution
2131                 // metaproject. Microsoft.Common.CurrentVersion.targets sets the value to *Undefined* when not set, and
2132                 // for backward compatibility, we shouldn't change that. But resolvers should be exposed to a string
2133                 // that's null or a full path, so correct that here.
2134                 var solutionPath = _data.GetProperty(SolutionProjectGenerator.SolutionPathPropertyName)?.EvaluatedValue;
2135                 if (solutionPath == "*Undefined*") solutionPath = null;
2136                 var projectPath = _data.GetProperty(ReservedPropertyNames.projectFullPath)?.EvaluatedValue;
2137 
2138                 // Combine SDK path with the "project" relative path
2139                 var sdkRootPath = _sdkResolverService.ResolveSdk(_submissionId, importElement.ParsedSdkReference, _evaluationLoggingContext, importElement.Location, solutionPath, projectPath)?.Path;
2140 
2141                 if (string.IsNullOrEmpty(sdkRootPath))
2142                 {
2143                     if (_loadSettings.HasFlag(ProjectLoadSettings.IgnoreMissingImports))
2144                     {
2145                         ProjectImportedEventArgs eventArgs = new ProjectImportedEventArgs(
2146                             importElement.Location.Line,
2147                             importElement.Location.Column,
2148                             ResourceUtilities.GetResourceString("CouldNotResolveSdk"),
2149                             importElement.ParsedSdkReference.ToString())
2150                         {
2151                             BuildEventContext = _evaluationLoggingContext.BuildEventContext,
2152                             UnexpandedProject = importElement.Project,
2153                             ProjectFile = importElement.ContainingProject.FullPath,
2154                             ImportedProjectFile = null,
2155                             ImportIgnored = true,
2156                         };
2157 
2158                         _evaluationLoggingContext.LogBuildEvent(eventArgs);
2159 
2160                         projects = new List<ProjectRootElement>();
2161 
2162                         return;
2163                     }
2164 
2165                     ProjectErrorUtilities.ThrowInvalidProject(importElement.SdkLocation, "CouldNotResolveSdk", importElement.ParsedSdkReference.ToString());
2166                 }
2167 
2168                 project = Path.Combine(sdkRootPath, project);
2169             }
2170 
2171             ExpandAndLoadImportsFromUnescapedImportExpression(directoryOfImportingFile, importElement, project,
2172                 throwOnFileNotExistsError, out projects);
2173         }
2174 
2175         /// <summary>
2176         /// Load and parse the specified project import, which may have wildcards,
2177         /// into one or more ProjectRootElements.
2178         /// Caches the parsed import into the provided collection, so future
2179         /// requests can be satisfied without re-parsing it.
2180         /// </summary>
ExpandAndLoadImportsFromUnescapedImportExpression(string directoryOfImportingFile, ProjectImportElement importElement, string unescapedExpression, bool throwOnFileNotExistsError, out List<ProjectRootElement> imports)2181         private LoadImportsResult ExpandAndLoadImportsFromUnescapedImportExpression(string directoryOfImportingFile, ProjectImportElement importElement, string unescapedExpression,
2182                                             bool throwOnFileNotExistsError, out List<ProjectRootElement> imports)
2183         {
2184             string importExpressionEscaped = _expander.ExpandIntoStringLeaveEscaped(unescapedExpression, ExpanderOptions.ExpandProperties, importElement.ProjectLocation);
2185             ElementLocation importLocationInProject = importElement.Location;
2186 
2187             if (String.IsNullOrWhiteSpace(importExpressionEscaped))
2188             {
2189                 ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "InvalidAttributeValue", String.Empty, XMakeAttributes.project, XMakeElements.import);
2190             }
2191 
2192             bool atleastOneImportIgnored = false;
2193             bool atleastOneImportEmpty = false;
2194             imports = new List<ProjectRootElement>();
2195 
2196             foreach (string importExpressionEscapedItem in ExpressionShredder.SplitSemiColonSeparatedList(importExpressionEscaped))
2197             {
2198                 string[] importFilesEscaped = null;
2199 
2200                 try
2201                 {
2202                     // Handle the case of an expression expanding to nothing specially;
2203                     // force an exception here to give a nicer message, that doesn't show the project directory in it.
2204                     if (importExpressionEscapedItem.Length == 0 || importExpressionEscapedItem.Trim().Length == 0)
2205                     {
2206                         FileUtilities.NormalizePath(EscapingUtilities.UnescapeAll(importExpressionEscapedItem));
2207                     }
2208 
2209                     // Expand the wildcards and provide an alphabetical order list of import statements.
2210                     importFilesEscaped = EngineFileUtilities.GetFileListEscaped(directoryOfImportingFile, importExpressionEscapedItem, forceEvaluate: true);
2211                 }
2212                 catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
2213                 {
2214                     ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "InvalidAttributeValueWithException", EscapingUtilities.UnescapeAll(importExpressionEscapedItem), XMakeAttributes.project, XMakeElements.import, ex.Message);
2215                 }
2216 
2217                 if (importFilesEscaped.Length == 0)
2218                 {
2219                     // Keep track of any imports that evaluated to empty
2220                     atleastOneImportEmpty = true;
2221 
2222                     if (_logProjectImportedEvents)
2223                     {
2224                         ProjectImportedEventArgs eventArgs = new ProjectImportedEventArgs(
2225                             importElement.Location.Line,
2226                             importElement.Location.Column,
2227                             ResourceUtilities.GetResourceString("ProjectImportSkippedNoMatches"),
2228                             importExpressionEscapedItem,
2229                             importElement.ContainingProject.FullPath,
2230                             importElement.Location.Line,
2231                             importElement.Location.Column)
2232                         {
2233                             BuildEventContext = _evaluationLoggingContext.BuildEventContext,
2234                             UnexpandedProject = importElement.Project,
2235                             ProjectFile = importElement.ContainingProject.FullPath,
2236                         };
2237 
2238                         _evaluationLoggingContext.LogBuildEvent(eventArgs);
2239                     }
2240                 }
2241 
2242                 foreach (string importFileEscaped in importFilesEscaped)
2243                 {
2244                     string importFileUnescaped = EscapingUtilities.UnescapeAll(importFileEscaped);
2245 
2246                     // GetFileListEscaped may not return a rooted path, we need to root it. Also if there are no wild cards we still need to get the full path on the filespec.
2247                     try
2248                     {
2249                         if (directoryOfImportingFile != null && !Path.IsPathRooted(importFileUnescaped))
2250                         {
2251                             importFileUnescaped = Path.Combine(directoryOfImportingFile, importFileUnescaped);
2252                         }
2253 
2254                         // Canonicalize to eg., eliminate "\..\"
2255                         importFileUnescaped = FileUtilities.NormalizePath(importFileUnescaped);
2256                     }
2257                     catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
2258                     {
2259                         ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "InvalidAttributeValueWithException", importFileUnescaped, XMakeAttributes.project, XMakeElements.import, ex.Message);
2260                     }
2261 
2262                     // If a file is included twice, or there is a cycle of imports, we ignore all but the first import
2263                     // and issue a warning to that effect.
2264                     if (String.Equals(_projectRootElement.FullPath, importFileUnescaped, StringComparison.OrdinalIgnoreCase) /* We are trying to import ourselves */)
2265                     {
2266                         _evaluationLoggingContext.LogWarning(null, new BuildEventFileInfo(importLocationInProject), "SelfImport", importFileUnescaped);
2267                         atleastOneImportIgnored = true;
2268 
2269                         continue;
2270                     }
2271 
2272                     // Circular dependencies (e.g. t0.targets imports t1.targets, t1.targets imports t2.targets and t2.targets imports t0.targets) will be
2273                     // caught by the check for duplicate imports which is done later in the method. However, if the project load setting requires throwing
2274                     // on circular imports or recording duplicate-but-not-circular imports, then we need to do exclusive check for circular imports here.
2275                     if ((_loadSettings & ProjectLoadSettings.RejectCircularImports) != 0 || (_loadSettings & ProjectLoadSettings.RecordDuplicateButNotCircularImports) != 0)
2276                     {
2277                         // Check if this import introduces circularity.
2278                         if (IntroducesCircularity(importFileUnescaped, importElement))
2279                         {
2280                             // Get the full path of the MSBuild file that has this import.
2281                             string importedBy = importElement.ContainingProject.FullPath ?? String.Empty;
2282 
2283                             _evaluationLoggingContext.LogWarning(null, new BuildEventFileInfo(importLocationInProject), "ImportIntroducesCircularity", importFileUnescaped, importedBy);
2284 
2285                             // Throw exception if the project load settings requires us to stop the evaluation of a project when circular imports are detected.
2286                             if ((_loadSettings & ProjectLoadSettings.RejectCircularImports) != 0)
2287                             {
2288                                 ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "ImportIntroducesCircularity", importFileUnescaped, importedBy);
2289                             }
2290 
2291                             // Ignore this import and no more further processing on it.
2292                             atleastOneImportIgnored = true;
2293                             continue;
2294                         }
2295                     }
2296 
2297                     ProjectImportElement previouslyImportedAt;
2298                     bool duplicateImport = false;
2299 
2300                     if (_importsSeen.TryGetValue(importFileUnescaped, out previouslyImportedAt))
2301                     {
2302                         string parenthesizedProjectLocation = String.Empty;
2303 
2304                         // If neither file involved is the project itself, append its path in square brackets
2305                         if (previouslyImportedAt.ContainingProject != _projectRootElement && importElement.ContainingProject != _projectRootElement)
2306                         {
2307                             parenthesizedProjectLocation = "[" + _projectRootElement.FullPath + "]";
2308                         }
2309                         // TODO: Detect if the duplicate import came from an SDK attribute
2310                         _evaluationLoggingContext.LogWarning(null, new BuildEventFileInfo(importLocationInProject), "DuplicateImport", importFileUnescaped, previouslyImportedAt.Location.LocationString, parenthesizedProjectLocation);
2311                         duplicateImport = true;
2312                     }
2313 
2314                     ProjectRootElement importedProjectElement;
2315 
2316                     try
2317                     {
2318                         // We take the explicit loaded flag from the project ultimately being evaluated.  The goal being that
2319                         // if a project system loaded a user's project, all imports (which would include property sheets and .user file)
2320                         // may impact evaluation and should be included in the weak cache without ever being cleared out to avoid
2321                         // the project system being exposed to multiple PRE instances for the same file.  We only want to consider
2322                         // clearing the weak cache (and therefore setting explicitload=false) for projects the project system never
2323                         // was directly interested in (i.e. the ones that were reached for purposes of building a P2P.)
2324                         bool explicitlyLoaded = importElement.ContainingProject.IsExplicitlyLoaded;
2325                         importedProjectElement = _projectRootElementCache.Get(
2326                             importFileUnescaped,
2327                             (p, c) =>
2328                             {
2329                                 return ProjectRootElement.OpenProjectOrSolution(
2330                                     importFileUnescaped,
2331                                     new ReadOnlyConvertingDictionary<string, ProjectPropertyInstance, string>(
2332                                         _data.GlobalPropertiesDictionary,
2333                                         instance => ((IProperty)instance).EvaluatedValueEscaped),
2334                                     _data.ExplicitToolsVersion,
2335                                     _projectRootElementCache,
2336                                     explicitlyLoaded);
2337                             },
2338                             explicitlyLoaded,
2339                             // don't care about formatting, reuse whatever is there
2340                             preserveFormatting: null);
2341 
2342                         if (duplicateImport)
2343                         {
2344                             // Only record the data if we want to record duplicate imports
2345                             if ((_loadSettings & ProjectLoadSettings.RecordDuplicateButNotCircularImports) != 0)
2346                             {
2347                                 _data.RecordImportWithDuplicates(importElement, importedProjectElement,
2348                                     importedProjectElement.Version);
2349                             }
2350 
2351                             // Since we have already seen this we need to not continue on in the processing.
2352                             atleastOneImportIgnored = true;
2353                             continue;
2354                         }
2355                         else
2356                         {
2357                             imports.Add(importedProjectElement);
2358 
2359                             if (_logProjectImportedEvents)
2360                             {
2361                                 ProjectImportedEventArgs eventArgs = new ProjectImportedEventArgs(
2362                                     importElement.Location.Line,
2363                                     importElement.Location.Column,
2364                                     ResourceUtilities.GetResourceString("ProjectImported"),
2365                                     importedProjectElement.FullPath,
2366                                     importElement.ContainingProject.FullPath,
2367                                     importElement.Location.Line,
2368                                     importElement.Location.Column)
2369                                 {
2370                                     BuildEventContext = _evaluationLoggingContext.BuildEventContext,
2371                                     ImportedProjectFile = importedProjectElement.FullPath,
2372                                     UnexpandedProject = importElement.Project,
2373                                     ProjectFile = importElement.ContainingProject.FullPath
2374                                 };
2375 
2376                                 _evaluationLoggingContext.LogBuildEvent(eventArgs);
2377                             }
2378                         }
2379                     }
2380                     catch (InvalidProjectFileException ex)
2381                     {
2382                         // The import couldn't be read from disk, or something similar. In that case,
2383                         // the error message would be more useful if it pointed to the location in the importing project file instead.
2384                         // Perhaps the import tag has a typo in, for example.
2385 
2386                         // There's a specific message for file not existing
2387                         if (!File.Exists(importFileUnescaped))
2388                         {
2389                             bool ignoreMissingImportsFlagSet = (_loadSettings & ProjectLoadSettings.IgnoreMissingImports) != 0;
2390                             if (!throwOnFileNotExistsError || ignoreMissingImportsFlagSet)
2391                             {
2392                                 if (ignoreMissingImportsFlagSet)
2393                                 {
2394                                     // Log message for import skipped
2395                                     ProjectImportedEventArgs eventArgs = new ProjectImportedEventArgs(
2396                                         importElement.Location.Line,
2397                                         importElement.Location.Column,
2398                                         ResourceUtilities.GetResourceString("ProjectImportSkippedMissingFile"),
2399                                         importFileUnescaped,
2400                                         importElement.ContainingProject.FullPath,
2401                                         importElement.Location.Line,
2402                                         importElement.Location.Column)
2403                                     {
2404                                         BuildEventContext = _evaluationLoggingContext.BuildEventContext,
2405                                         UnexpandedProject = importElement.Project,
2406                                         ProjectFile = importElement.ContainingProject.FullPath,
2407                                         ImportedProjectFile = importFileUnescaped,
2408                                         ImportIgnored = true,
2409                                     };
2410 
2411                                     _evaluationLoggingContext.LogBuildEvent(eventArgs);
2412                                 }
2413 
2414 
2415                                 continue;
2416                             }
2417 
2418                             ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "ImportedProjectNotFound",
2419                                 importFileUnescaped);
2420                         }
2421                         else
2422                         {
2423                             bool ignoreImport = false;
2424                             string ignoreImportResource = null;
2425 
2426                             if (((_loadSettings & ProjectLoadSettings.IgnoreEmptyImports) != 0 || Traits.Instance.EscapeHatches.IgnoreEmptyImports) && ProjectRootElement.IsEmptyXmlFile(importFileUnescaped))
2427                             {
2428                                 // If IgnoreEmptyImports is enabled, check if the file is considered empty
2429                                 //
2430                                 ignoreImport = true;
2431                                 ignoreImportResource = "ProjectImportSkippedEmptyFile";
2432                             }
2433                             else if ((_loadSettings & ProjectLoadSettings.IgnoreInvalidImports) != 0)
2434                             {
2435                                 // If IgnoreInvalidImports is enabled, log all other non-handled exceptions and continue
2436                                 //
2437                                 ignoreImport = true;
2438                                 ignoreImportResource = "ProjectImportSkippedInvalidFile";
2439                             }
2440 
2441                             if (ignoreImport)
2442                             {
2443                                 atleastOneImportIgnored = true;
2444 
2445                                 // Log message for import skipped
2446                                 ProjectImportedEventArgs eventArgs = new ProjectImportedEventArgs(
2447                                     importElement.Location.Line,
2448                                     importElement.Location.Column,
2449                                     ResourceUtilities.GetResourceString(ignoreImportResource),
2450                                     importFileUnescaped,
2451                                     importElement.ContainingProject.FullPath,
2452                                     importElement.Location.Line,
2453                                     importElement.Location.Column)
2454                                 {
2455                                     BuildEventContext = _evaluationLoggingContext.BuildEventContext,
2456                                     UnexpandedProject = importElement.Project,
2457                                     ProjectFile = importElement.ContainingProject.FullPath,
2458                                     ImportedProjectFile = importFileUnescaped,
2459                                     ImportIgnored = true,
2460                                 };
2461 
2462                                 _evaluationLoggingContext.LogBuildEvent(eventArgs);
2463 
2464                                 continue;
2465                             }
2466 
2467                             // If this exception is a wrapped exception (like IOException or XmlException) then wrap it as an invalid import instead
2468                             if (ex.InnerException != null)
2469                             {
2470                                 // Otherwise a more generic message, still pointing to the location of the import tag
2471                                 ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "InvalidImportedProjectFile",
2472                                     importFileUnescaped, ex.InnerException.Message);
2473                             }
2474 
2475                             // Throw the original InvalidProjectFileException because it has no InnerException and was not wrapping something else
2476                             throw;
2477                         }
2478                     }
2479 
2480                     // Because these expressions will never be expanded again, we
2481                     // can store the unescaped value. The only purpose of escaping is to
2482                     // avoid undesired splitting or expansion.
2483                     _importsSeen.Add(importFileUnescaped, importElement);
2484                 }
2485             }
2486 
2487             if (imports.Count > 0)
2488             {
2489                 return LoadImportsResult.ProjectsImported;
2490             }
2491 
2492             if (atleastOneImportIgnored)
2493             {
2494                 return LoadImportsResult.FoundFilesToImportButIgnored;
2495             }
2496 
2497             if (atleastOneImportEmpty)
2498             {
2499                 // One or more expression resolved to "", eg. a wildcard
2500                 return LoadImportsResult.ImportExpressionResolvedToNothing;
2501             }
2502 
2503             // No projects were imported, none were ignored but we did have atleast
2504             // one file to process, which means that we did try to load a file but
2505             // failed w/o an exception escaping from here.
2506             // We ignore only the file not existing error, so, that is the case here
2507             // (if @throwOnFileNotExistsError==true, then it would have thrown
2508             //  and we wouldn't be here)
2509             return LoadImportsResult.TriedToImportButFileNotFound;
2510         }
2511 
2512         /// <summary>
2513         /// Checks if an import matches with another import in its ancestor line of imports.
2514         /// </summary>
2515         /// <param name="importFileUnescaped"> The import that is being added. </param>
2516         /// <param name="importElement"> The importing element for this import. </param>
2517         /// <returns> True, if and only if this import introduces a circularity. </returns>
IntroducesCircularity(string importFileUnescaped, ProjectImportElement importElement)2518         private bool IntroducesCircularity(string importFileUnescaped, ProjectImportElement importElement)
2519         {
2520             bool foundMatchingAncestor = false;
2521 
2522             // While we haven't found a matching ancestor haven't reach the project node,
2523             // keep climbing the import chain and checking for matches.
2524             while (importElement != null)
2525             {
2526                 // Get the full path of the MSBuild file that imports this file.
2527                 string importedBy = importElement.ContainingProject.FullPath;
2528 
2529                 if (String.Equals(importFileUnescaped, importedBy, StringComparison.OrdinalIgnoreCase))
2530                 {
2531                     // Circular dependency found!
2532                     foundMatchingAncestor = true;
2533                     break;
2534                 }
2535 
2536                 if (!String.IsNullOrEmpty(importedBy)) // The full path of a project loaded from memory can be null.
2537                 {
2538                     // Set the "counter" to the importing project.
2539                     _importsSeen.TryGetValue(importedBy, out importElement);
2540                 }
2541                 else
2542                 {
2543                     importElement = null;
2544                 }
2545             }
2546 
2547             return foundMatchingAncestor;
2548         }
2549 
2550         /// <summary>
2551         /// Evaluate a given condition
2552         /// </summary>
EvaluateCondition(ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions)2553         private bool EvaluateCondition(ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions)
2554         {
2555             return EvaluateCondition(element, element.Condition, expanderOptions, parserOptions);
2556         }
2557 
EvaluateCondition(ProjectElement element, string condition, ExpanderOptions expanderOptions, ParserOptions parserOptions)2558         private bool EvaluateCondition(ProjectElement element, string condition, ExpanderOptions expanderOptions, ParserOptions parserOptions)
2559         {
2560             if (condition.Length == 0)
2561             {
2562                 return true;
2563             }
2564 
2565             using (_evaluationProfiler.TrackCondition(element.ConditionLocation, condition))
2566             {
2567                 bool result = ConditionEvaluator.EvaluateCondition
2568                     (
2569                     condition,
2570                     parserOptions,
2571                     _expander,
2572                     expanderOptions,
2573                     GetCurrentDirectoryForConditionEvaluation(element),
2574                     element.ConditionLocation,
2575                     _evaluationLoggingContext.LoggingService,
2576                     _evaluationLoggingContext.BuildEventContext
2577                     );
2578 
2579                 return result;
2580             }
2581         }
2582 
EvaluateConditionCollectingConditionedProperties(ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions, ProjectRootElementCache projectRootElementCache = null)2583         private bool EvaluateConditionCollectingConditionedProperties(ProjectElement element, ExpanderOptions expanderOptions, ParserOptions parserOptions, ProjectRootElementCache projectRootElementCache = null)
2584         {
2585             return EvaluateConditionCollectingConditionedProperties(element, element.Condition, expanderOptions, parserOptions, projectRootElementCache);
2586         }
2587 
2588         /// <summary>
2589         /// Evaluate a given condition, collecting conditioned properties.
2590         /// </summary>
EvaluateConditionCollectingConditionedProperties(ProjectElement element, string condition, ExpanderOptions expanderOptions, ParserOptions parserOptions, ProjectRootElementCache projectRootElementCache = null)2591         private bool EvaluateConditionCollectingConditionedProperties(ProjectElement element, string condition, ExpanderOptions expanderOptions, ParserOptions parserOptions, ProjectRootElementCache projectRootElementCache = null)
2592         {
2593             if (condition.Length == 0)
2594             {
2595                 return true;
2596             }
2597 
2598             if (!_data.ShouldEvaluateForDesignTime)
2599             {
2600                 return EvaluateCondition(element, condition, expanderOptions, parserOptions);
2601             }
2602 
2603             using (_evaluationProfiler.TrackCondition(element.ConditionLocation, condition))
2604             {
2605                 bool result = ConditionEvaluator.EvaluateConditionCollectingConditionedProperties
2606                     (
2607                     condition,
2608                     parserOptions,
2609                     _expander,
2610                     expanderOptions,
2611                     _data.ConditionedProperties,
2612                     GetCurrentDirectoryForConditionEvaluation(element),
2613                     element.ConditionLocation,
2614                     _evaluationLoggingContext.LoggingService,
2615                     _evaluationLoggingContext.BuildEventContext,
2616                     projectRootElementCache
2617                     );
2618 
2619                 return result;
2620             }
2621         }
2622 
2623         /// <summary>
2624         /// COMPAT: Whidbey used the "current project file/targets" directory for evaluating Import and PropertyGroup conditions
2625         /// Orcas broke this by using the current root project file for all conditions
2626         /// For Dev10+, we'll fix this, and use the current project file/targets directory for Import, ImportGroup and PropertyGroup
2627         /// but the root project file for the rest. Inside of targets will use the root project file as always.
2628         /// </summary>
GetCurrentDirectoryForConditionEvaluation(ProjectElement element)2629         private string GetCurrentDirectoryForConditionEvaluation(ProjectElement element)
2630         {
2631             if (element is ProjectPropertyGroupElement || element is ProjectImportElement || element is ProjectImportGroupElement)
2632             {
2633                 return element.ContainingProject.DirectoryPath;
2634             }
2635             else
2636             {
2637                 return _data.Directory;
2638             }
2639         }
2640 
RecordEvaluatedItemElement(ProjectItemElement itemElement)2641         private void RecordEvaluatedItemElement(ProjectItemElement itemElement)
2642         {
2643             if (_loadSettings.HasFlag(ProjectLoadSettings.RecordEvaluatedItemElements))
2644             {
2645                 _data.EvaluatedItemElements.Add(itemElement);
2646             }
2647         }
2648 
2649         /// <summary>
2650         /// Throws InvalidProjectException because we failed to import a project which contained a ProjectImportSearchPath fall-back.
2651         /// <param name="searchPathMatch">MSBuildExtensionsPath reference kind found in the Project attribute of the Import element</param>
2652         /// <param name="importElement">The importing element for this import</param>
2653         /// </summary>
ThrowForImportedProjectWithSearchPathsNotFound(ProjectImportPathMatch searchPathMatch, ProjectImportElement importElement)2654         private void ThrowForImportedProjectWithSearchPathsNotFound(ProjectImportPathMatch searchPathMatch, ProjectImportElement importElement)
2655         {
2656             var extensionsPathProp = _data.GetProperty(searchPathMatch.PropertyName);
2657             string importExpandedWithDefaultPath;
2658             string relativeProjectPath;
2659 
2660             if (extensionsPathProp != null)
2661             {
2662                 string extensionsPathPropValue = extensionsPathProp.EvaluatedValue;
2663                 importExpandedWithDefaultPath =
2664                     _expander.ExpandIntoStringLeaveEscaped(
2665                         importElement.Project.Replace(searchPathMatch.MsBuildPropertyFormat, extensionsPathPropValue),
2666                         ExpanderOptions.ExpandProperties, importElement.ProjectLocation);
2667 
2668                 relativeProjectPath = FileUtilities.MakeRelative(extensionsPathPropValue, importExpandedWithDefaultPath);
2669             }
2670             else
2671             {
2672                 // If we can't get the original property, just use the actual text from the project file in the error message.
2673                 // This should be a very rare case where the toolset is out of sync with the fallback. This will resolve
2674                 // a null ref calling EvaluatedValue on the property.
2675                 importExpandedWithDefaultPath = importElement.Project;
2676                 relativeProjectPath = importElement.Project;
2677             }
2678 
2679             var onlyFallbackSearchPaths = searchPathMatch.SearchPaths.Select(s => _data.ExpandString(s)).ToList();
2680 
2681             string stringifiedListOfSearchPaths = StringifyList(onlyFallbackSearchPaths);
2682 
2683 #if FEATURE_SYSTEM_CONFIGURATION
2684             string configLocation = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
2685 
2686             ProjectErrorUtilities.ThrowInvalidProject(importElement.ProjectLocation,
2687                 "ImportedProjectFromExtensionsPathNotFoundFromAppConfig",
2688                 importExpandedWithDefaultPath,
2689                 relativeProjectPath,
2690                 searchPathMatch.MsBuildPropertyFormat,
2691                 stringifiedListOfSearchPaths,
2692                 configLocation);
2693 #else
2694             ProjectErrorUtilities.ThrowInvalidProject(importElement.ProjectLocation, "ImportedProjectFromExtensionsPathNotFound",
2695                                                         importExpandedWithDefaultPath,
2696                                                         relativeProjectPath,
2697                                                         searchPathMatch.MsBuildPropertyFormat,
2698                                                         stringifiedListOfSearchPaths);
2699 #endif
2700         }
2701 
2702         /// <summary>
2703         /// Stringify a list of strings, like {"abc, "def", "foo"} to "abc, def and foo"
2704         /// or {"abc"} to "abc"
2705         /// <param name="strings">List of strings to stringify</param>
2706         /// <returns>Stringified list</returns>
2707         /// </summary>
StringifyList(IList<string> strings)2708         private static string StringifyList(IList<string> strings)
2709         {
2710             var sb = new StringBuilder();
2711             for (int i = 0; i < strings.Count - 1; i++)
2712             {
2713                 if (i > 0)
2714                 {
2715                     sb.Append(", ");
2716                 }
2717 
2718                 sb.Append($"\"{strings[i]}\"");
2719             }
2720 
2721             if (strings.Count > 1)
2722             {
2723                 sb.Append(" and ");
2724             }
2725 
2726             sb.Append($"\"{strings[strings.Count - 1]}\"");
2727 
2728             return sb.ToString();
2729         }
2730     }
2731 
2732     /// <summary>
2733     /// Represents result of attempting to load imports (ExpandAndLoadImportsFromUnescapedImportExpression*)
2734     /// </summary>
2735     internal enum LoadImportsResult
2736     {
2737         ProjectsImported,
2738         FoundFilesToImportButIgnored,
2739         TriedToImportButFileNotFound,
2740         ImportExpressionResolvedToNothing,
2741         ConditionWasFalse
2742     }
2743 }
2744