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