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>An object containing properties of a toolset.</summary> 6 //----------------------------------------------------------------------- 7 8 using System; 9 using System.Collections.Generic; 10 using System.Diagnostics; 11 using System.Diagnostics.CodeAnalysis; 12 using System.IO; 13 using System.Linq; 14 using System.Xml; 15 16 using Microsoft.Build.BackEnd; 17 using Microsoft.Build.Collections; 18 using Microsoft.Build.Construction; 19 using Microsoft.Build.Execution; 20 using Microsoft.Build.Framework; 21 using Microsoft.Build.Internal; 22 using Microsoft.Build.Shared; 23 using Microsoft.Win32; 24 using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; 25 using ObjectModel = System.Collections.ObjectModel; 26 using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; 27 28 namespace Microsoft.Build.Evaluation 29 { 30 /// <summary> 31 /// Delegate for loading an Xml file, for unit testing. 32 /// </summary> 33 /// <param name="path">The path to load.</param> 34 /// <returns>An Xml document.</returns> LoadXmlFromPath(string path)35 internal delegate XmlDocumentWithLocation LoadXmlFromPath(string path); 36 37 /// <summary> 38 /// Aggregation of a toolset version (eg. "2.0"), tools path, and optional set of associated properties. 39 /// Toolset is immutable. 40 /// </summary> 41 /// <remarks> 42 /// UNDONE: Review immutability. If this is not immutable, add a mechanism to notify the project collection/s owning it to increment their toolsetVersion. 43 /// </remarks> 44 [DebuggerDisplay("ToolsVersion={ToolsVersion} ToolsPath={ToolsPath} #Properties={_properties.Count}")] 45 public class Toolset : INodePacketTranslatable 46 { 47 /// <summary> 48 /// these files list all default tasks and task assemblies that do not need to be explicitly declared by projects 49 /// </summary> 50 private const string DefaultTasksFilePattern = "*.tasks"; 51 52 /// <summary> 53 /// these files list all Override tasks and task assemblies that do not need to be explicitly declared by projects 54 /// </summary> 55 private const string OverrideTasksFilePattern = "*.overridetasks"; 56 57 /// <summary> 58 /// Regkey that we check to see whether Dev10 is installed. This should exist if any SKU of Dev10 is installed, 59 /// but is not removed even when the last version of Dev10 is uninstalled, due to 10.0\bsln sticking around. 60 /// </summary> 61 private const string Dev10OverallInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0"; 62 63 /// <summary> 64 /// Regkey that we check to see whether Dev10 Ultimate is installed. This will exist if it is installed, and be 65 /// properly removed after it has been uninstalled. 66 /// </summary> 67 private const string Dev10UltimateInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\vstscore"; 68 69 /// <summary> 70 /// Regkey that we check to see whether Dev10 Premium is installed. This will exist if it is installed, and be 71 /// properly removed after it has been uninstalled. 72 /// </summary> 73 private const string Dev10PremiumInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\vstdcore"; 74 75 /// <summary> 76 /// Regkey that we check to see whether Dev10 Professional is installed. This will exist if it is installed, and be 77 /// properly removed after it has been uninstalled. 78 /// </summary> 79 private const string Dev10ProfessionalInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\procore"; 80 81 /// <summary> 82 /// Regkey that we check to see whether C# Express 2010 is installed. This will exist if it is installed, and be 83 /// properly removed after it has been uninstalled. 84 /// </summary> 85 private const string Dev10VCSExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vcs\Servicing\10.0\xcor"; 86 87 /// <summary> 88 /// Regkey that we check to see whether VB Express 2010 is installed. This will exist if it is installed, and be 89 /// properly removed after it has been uninstalled. 90 /// </summary> 91 private const string Dev10VBExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vb\Servicing\10.0\xcor"; 92 93 /// <summary> 94 /// Regkey that we check to see whether VC Express 2010 is installed. This will exist if it is installed, and be 95 /// properly removed after it has been uninstalled. 96 /// </summary> 97 private const string Dev10VCExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vc\Servicing\10.0\xcor"; 98 99 /// <summary> 100 /// Regkey that we check to see whether VWD Express 2010 is installed. This will exist if it is installed, and be 101 /// properly removed after it has been uninstalled. 102 /// </summary> 103 private const string Dev10VWDExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vns\Servicing\10.0\xcor"; 104 105 /// <summary> 106 /// Regkey that we check to see whether LightSwitch 2010 is installed. This will exist if it is installed, and be 107 /// properly removed after it has been uninstalled. 108 /// </summary> 109 private const string Dev10LightSwitchInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\vslscore"; 110 111 #if FEATURE_WIN32_REGISTRY 112 /// <summary> 113 /// Null if it hasn't been figured out yet; true if (some variation of) Visual Studio 2010 is installed on 114 /// the current machine, false otherwise. 115 /// </summary> 116 private static bool? s_dev10IsInstalled = null; 117 #endif 118 119 /// <summary> 120 /// Name of the tools version 121 /// </summary> 122 private string _toolsVersion; 123 124 /// <summary> 125 /// The MSBuildBinPath (and ToolsPath) for this tools version 126 /// </summary> 127 private string _toolsPath; 128 129 /// <summary> 130 /// The properties defined by the toolset. 131 /// </summary> 132 private PropertyDictionary<ProjectPropertyInstance> _properties; 133 134 /// <summary> 135 /// Path to look for msbuild override task files. 136 /// </summary> 137 private string _overrideTasksPath; 138 139 /// <summary> 140 /// ToolsVersion to use as the default ToolsVersion for this version of MSBuild 141 /// </summary> 142 private string _defaultOverrideToolsVersion; 143 144 /// <summary> 145 /// The environment properties 146 /// </summary> 147 private PropertyDictionary<ProjectPropertyInstance> _environmentProperties; 148 149 /// <summary> 150 /// The build-global properties 151 /// </summary> 152 private PropertyDictionary<ProjectPropertyInstance> _globalProperties; 153 154 /// <summary> 155 /// indicates if the default tasks file has already been scanned 156 /// </summary> 157 private bool _defaultTasksRegistrationAttempted; 158 159 /// <summary> 160 /// indicates if the override tasks file has already been scanned 161 /// </summary> 162 private bool _overrideTasksRegistrationAttempted; 163 164 /// <summary> 165 /// holds all the default tasks we know about and the assemblies they exist in 166 /// </summary> 167 private TaskRegistry _defaultTaskRegistry; 168 169 /// <summary> 170 /// holds all the override tasks we know about and the assemblies they exist in 171 /// </summary> 172 private TaskRegistry _overrideTaskRegistry; 173 174 /// <summary> 175 /// Delegate to retrieving files. For unit testing only. 176 /// </summary> 177 private DirectoryGetFiles _getFiles; 178 179 /// <summary> 180 /// Delegate to check to see if a directory exists 181 /// </summary> 182 private DirectoryExists _directoryExists = null; 183 184 /// <summary> 185 /// Delegate for loading Xml. For unit testing only. 186 /// </summary> 187 private LoadXmlFromPath _loadXmlFromPath; 188 189 /// <summary> 190 /// Expander to expand the properties and items in the using tasks files 191 /// </summary> 192 private Expander<ProjectPropertyInstance, ProjectItemInstance> _expander; 193 194 /// <summary> 195 /// Bag of properties for the expander to expand the properties and items in the using tasks files 196 /// </summary> 197 private PropertyDictionary<ProjectPropertyInstance> _propertyBag; 198 199 /// <summary> 200 /// SubToolsets that map to this toolset. 201 /// </summary> 202 private Dictionary<string, SubToolset> _subToolsets; 203 204 /// <summary> 205 /// If no sub-toolset is specified, this is the default sub-toolset version. Null == no default 206 /// sub-toolset, just use the base toolset. 207 /// </summary> 208 private string _defaultSubToolsetVersion; 209 210 /// <summary> 211 /// Map of project import properties to their list of fall-back search paths 212 /// </summary> 213 private Dictionary<string, ProjectImportPathMatch> _propertySearchPathsTable; 214 215 /// <summary> 216 /// Constructor taking only tools version and a matching tools path 217 /// </summary> 218 /// <param name="toolsVersion">Name of the toolset</param> 219 /// <param name="toolsPath">Path to this toolset's tasks and targets</param> 220 /// <param name="projectCollection">The project collection from which to obtain the properties.</param> 221 /// <param name="msbuildOverrideTasksPath">The path to search for msbuild overridetasks files.</param> Toolset(string toolsVersion, string toolsPath, ProjectCollection projectCollection, string msbuildOverrideTasksPath)222 public Toolset(string toolsVersion, string toolsPath, ProjectCollection projectCollection, string msbuildOverrideTasksPath) 223 : this(toolsVersion, toolsPath, null, projectCollection, msbuildOverrideTasksPath) 224 { 225 } 226 227 /// <summary> 228 /// Constructor that also associates a set of properties with the tools version 229 /// </summary> 230 /// <param name="toolsVersion">Name of the toolset</param> 231 /// <param name="toolsPath">Path to this toolset's tasks and targets</param> 232 /// <param name="buildProperties"> 233 /// Properties that should be associated with the Toolset. 234 /// May be null, in which case an empty property group will be used. 235 /// </param> 236 /// <param name="projectCollection">The project collection that this toolset should inherit from</param> 237 /// <param name="msbuildOverrideTasksPath">The override tasks path.</param> Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, string msbuildOverrideTasksPath)238 public Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, string msbuildOverrideTasksPath) 239 : this(toolsVersion, toolsPath, buildProperties, projectCollection, null, msbuildOverrideTasksPath) 240 { 241 } 242 243 /// <summary> 244 /// Constructor that also associates a set of properties with the tools version 245 /// </summary> 246 /// <param name="toolsVersion">Name of the toolset</param> 247 /// <param name="toolsPath">Path to this toolset's tasks and targets</param> 248 /// <param name="buildProperties"> 249 /// Properties that should be associated with the Toolset. 250 /// May be null, in which case an empty property group will be used. 251 /// </param> 252 /// <param name="projectCollection">The project collection that this toolset should inherit from</param> 253 /// <param name="subToolsets">The set of sub-toolsets to add to this toolset</param> 254 /// <param name="msbuildOverrideTasksPath">The override tasks path.</param> Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, IDictionary<string, SubToolset> subToolsets, string msbuildOverrideTasksPath)255 public Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, IDictionary<string, SubToolset> subToolsets, string msbuildOverrideTasksPath) 256 : this(toolsVersion, toolsPath, null, projectCollection.EnvironmentProperties, projectCollection.GlobalPropertiesCollection, subToolsets, msbuildOverrideTasksPath, defaultOverrideToolsVersion: null) 257 { 258 _properties = new PropertyDictionary<ProjectPropertyInstance>(); 259 if (null != buildProperties) 260 { 261 foreach (KeyValuePair<string, string> keyValuePair in buildProperties) 262 { 263 _properties.Set(ProjectPropertyInstance.Create(keyValuePair.Key, keyValuePair.Value, true)); 264 } 265 } 266 } 267 268 /// <summary> 269 /// Constructor taking only tools version and a matching tools path 270 /// </summary> 271 /// <param name="toolsVersion">Name of the toolset</param> 272 /// <param name="toolsPath">Path to this toolset's tasks and targets</param> 273 /// <param name="environmentProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the environment properties.</param> 274 /// <param name="globalProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the global properties.</param> 275 /// <param name="msbuildOverrideTasksPath">The override tasks path.</param> 276 /// <param name="defaultOverrideToolsVersion">ToolsVersion to use as the default ToolsVersion for this version of MSBuild.</param> Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, string msbuildOverrideTasksPath, string defaultOverrideToolsVersion)277 internal Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, string msbuildOverrideTasksPath, string defaultOverrideToolsVersion) 278 { 279 ErrorUtilities.VerifyThrowArgumentLength(toolsVersion, "toolsVersion"); 280 ErrorUtilities.VerifyThrowArgumentLength(toolsPath, "toolsPath"); 281 ErrorUtilities.VerifyThrowArgumentNull(environmentProperties, "environmentProperties"); 282 ErrorUtilities.VerifyThrowArgumentNull(globalProperties, "globalProperties"); 283 284 _toolsVersion = toolsVersion; 285 this.ToolsPath = toolsPath; 286 _globalProperties = globalProperties; 287 _environmentProperties = environmentProperties; 288 _overrideTasksPath = msbuildOverrideTasksPath; 289 _defaultOverrideToolsVersion = defaultOverrideToolsVersion; 290 291 } 292 293 /// <summary> 294 /// Constructor that also associates a set of properties with the tools version 295 /// </summary> 296 /// <param name="toolsVersion">Name of the toolset</param> 297 /// <param name="toolsPath">Path to this toolset's tasks and targets</param> 298 /// <param name="buildProperties"> 299 /// Properties that should be associated with the Toolset. 300 /// May be null, in which case an empty property group will be used. 301 /// </param> 302 /// <param name="environmentProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the environment properties.</param> 303 /// <param name="globalProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the global properties.</param> 304 /// <param name="subToolsets">A list of <see cref="SubToolset"/> to use.</param> 305 /// <param name="msbuildOverrideTasksPath">The override tasks path.</param> 306 /// <param name="defaultOverrideToolsVersion">ToolsVersion to use as the default ToolsVersion for this version of MSBuild.</param> 307 /// <param name="importSearchPathsTable">Map of parameter name to property search paths for use during Import.</param> Toolset( string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> buildProperties, PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, IDictionary<string, SubToolset> subToolsets, string msbuildOverrideTasksPath, string defaultOverrideToolsVersion, Dictionary<string, ProjectImportPathMatch> importSearchPathsTable = null)308 internal Toolset( 309 string toolsVersion, 310 string toolsPath, 311 PropertyDictionary<ProjectPropertyInstance> buildProperties, 312 PropertyDictionary<ProjectPropertyInstance> environmentProperties, 313 PropertyDictionary<ProjectPropertyInstance> globalProperties, 314 IDictionary<string, SubToolset> subToolsets, 315 string msbuildOverrideTasksPath, 316 string defaultOverrideToolsVersion, 317 Dictionary<string, ProjectImportPathMatch> importSearchPathsTable = null) 318 : this(toolsVersion, toolsPath, environmentProperties, globalProperties, msbuildOverrideTasksPath, defaultOverrideToolsVersion) 319 { 320 if (_properties == null) 321 { 322 _properties = buildProperties != null 323 ? new PropertyDictionary<ProjectPropertyInstance>(buildProperties) 324 : new PropertyDictionary<ProjectPropertyInstance>(); 325 } 326 327 if (subToolsets != null) 328 { 329 Dictionary<string, SubToolset> subToolsetsAsDictionary = subToolsets as Dictionary<string, SubToolset>; 330 _subToolsets = subToolsetsAsDictionary ?? new Dictionary<string, SubToolset>(subToolsets); 331 } 332 333 if (importSearchPathsTable != null) 334 { 335 _propertySearchPathsTable = importSearchPathsTable; 336 } 337 } 338 339 /// <summary> 340 /// Additional constructor to make unit testing the TaskRegistry support easier 341 /// </summary> 342 /// <remarks> 343 /// Internal for unit test purposes only. 344 /// </remarks> 345 /// <param name="toolsVersion">Name of the toolset</param> 346 /// <param name="toolsPath">Path to this toolset's tasks and targets</param> 347 /// <param name="buildProperties"> 348 /// Properties that should be associated with the Toolset. 349 /// May be null, in which case an empty property group will be used. 350 /// </param> 351 /// <param name="projectCollection">The project collection.</param> 352 /// <param name="getFiles">A delegate to intercept GetFiles calls. For unit testing.</param> 353 /// <param name="loadXmlFromPath">A delegate to intercept Xml load calls. For unit testing.</param> 354 /// <param name="msbuildOverrideTasksPath">The override tasks path.</param> 355 /// <param name="directoryExists"></param> Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> buildProperties, ProjectCollection projectCollection, DirectoryGetFiles getFiles, LoadXmlFromPath loadXmlFromPath, string msbuildOverrideTasksPath, DirectoryExists directoryExists)356 internal Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> buildProperties, ProjectCollection projectCollection, DirectoryGetFiles getFiles, LoadXmlFromPath loadXmlFromPath, string msbuildOverrideTasksPath, DirectoryExists directoryExists) 357 : this(toolsVersion, toolsPath, buildProperties, projectCollection.EnvironmentProperties, projectCollection.GlobalPropertiesCollection, null, msbuildOverrideTasksPath, null) 358 { 359 ErrorUtilities.VerifyThrowInternalNull(getFiles, "getFiles"); 360 ErrorUtilities.VerifyThrowInternalNull(loadXmlFromPath, "loadXmlFromPath"); 361 362 _directoryExists = directoryExists; 363 _getFiles = getFiles; 364 _loadXmlFromPath = loadXmlFromPath; 365 } 366 367 /// <summary> 368 /// Private constructor for serialization. 369 /// </summary> Toolset(INodePacketTranslator translator)370 private Toolset(INodePacketTranslator translator) 371 { 372 ((INodePacketTranslatable)this).Translate(translator); 373 } 374 375 /// <summary> 376 /// Returns a ProjectImportPathMatch struct for the first property found in the expression for which 377 /// project import search paths is enabled. 378 /// <param name="expression">Expression to search for properties in (first level only, not recursive)</param> 379 /// <returns>List of search paths or ProjectImportPathMatch.None if empty</returns> 380 /// </summary> GetProjectImportSearchPaths(string expression)381 internal ProjectImportPathMatch GetProjectImportSearchPaths(string expression) 382 { 383 if (string.IsNullOrEmpty(expression) || ImportPropertySearchPathsTable == null) 384 { 385 return ProjectImportPathMatch.None; 386 } 387 388 foreach (var searchPath in _propertySearchPathsTable.Values) 389 { 390 if (expression.IndexOf(searchPath.MsBuildPropertyFormat, StringComparison.OrdinalIgnoreCase) >= 0) 391 { 392 return searchPath; 393 } 394 } 395 396 return ProjectImportPathMatch.None; 397 } 398 399 /// <summary> 400 /// Name of this toolset 401 /// </summary> 402 public string ToolsVersion => _toolsVersion; 403 404 /// <summary> 405 /// Path to this toolset's tasks and targets. Corresponds to $(MSBuildToolsPath) in a project or targets file. 406 /// </summary> 407 public string ToolsPath 408 { 409 get 410 { 411 return _toolsPath; 412 } 413 414 private set 415 { 416 // Strip the trailing backslash if it exists. This way, when somebody 417 // concatenates does something like "$(MSBuildToolsPath)\CSharp.targets", 418 // they don't end up with a double-backslash in the middle. (It doesn't 419 // technically hurt anything, but it doesn't look nice.) 420 string toolsPathToUse = value; 421 422 if (FileUtilities.EndsWithSlash(toolsPathToUse)) 423 { 424 string rootPath = Path.GetPathRoot(Path.GetFullPath(toolsPathToUse)); 425 426 // Only if $(MSBuildBinPath) is *NOT* the root of a drive should we strip trailing slashes 427 if (!String.Equals(rootPath, toolsPathToUse, StringComparison.OrdinalIgnoreCase)) 428 { 429 // Trim off one trailing slash 430 toolsPathToUse = toolsPathToUse.Substring(0, toolsPathToUse.Length - 1); 431 } 432 } 433 434 _toolsPath = toolsPathToUse; 435 } 436 } 437 438 /// <summary> 439 /// Properties associated with the toolset 440 /// </summary> 441 public IDictionary<string, ProjectPropertyInstance> Properties 442 { 443 get 444 { 445 if (_properties == null) 446 { 447 return ReadOnlyEmptyDictionary<string, ProjectPropertyInstance>.Instance; 448 } 449 450 return new ObjectModel.ReadOnlyDictionary<string, ProjectPropertyInstance>(_properties); 451 } 452 } 453 454 /// <summary> 455 /// The set of sub-toolsets associated with this toolset. 456 /// </summary> 457 public IDictionary<string, SubToolset> SubToolsets 458 { 459 get 460 { 461 if (_subToolsets == null) 462 { 463 return ReadOnlyEmptyDictionary<string, SubToolset>.Instance; 464 } 465 466 return new ObjectModel.ReadOnlyDictionary<string, SubToolset>(_subToolsets); 467 } 468 } 469 470 /// <summary> 471 /// Returns the default sub-toolset version for this sub-toolset. Heuristic used is: 472 /// 1) If Visual Studio 2010 is installed and our ToolsVersion is "4.0", use the base toolset, and return 473 /// a sub-toolset version of "10.0", to be set as a publicly visible property so that e.g. targets can 474 /// consume it. This is to handle the fact that Visual Studio 2010 did not have any concept of sub-toolsets. 475 /// 2) Otherwise, use the highest-versioned sub-toolset found. Sub-toolsets with numbered versions will 476 /// be ordered numerically; any additional sub-toolsets will be prepended to the beginning of the list in 477 /// the order found. We use the highest-versioned sub-toolset because, in the absence of any other information, 478 /// we assume that higher-versioned tools will be more likely to be able to generate something more correct. 479 /// 480 /// Will return null if there is no sub-toolset available (and Dev10 is not installed). 481 /// </summary> 482 public string DefaultSubToolsetVersion 483 { 484 get 485 { 486 if (_defaultSubToolsetVersion == null) 487 { 488 // 1) Workaround for ToolsVersion 4.0 + VS 2010 489 if (String.Equals(ToolsVersion, "4.0", StringComparison.OrdinalIgnoreCase) && Dev10IsInstalled) 490 { 491 return Constants.Dev10SubToolsetValue; 492 } 493 494 // 2) Otherwise, just pick the highest available. 495 SortedDictionary<Version, string> subToolsetsWithVersion = new SortedDictionary<Version, string>(); 496 List<string> additionalSubToolsetNames = new List<string>(); 497 498 foreach (string subToolsetName in SubToolsets.Keys) 499 { 500 Version subToolsetVersion = VersionUtilities.ConvertToVersion(subToolsetName); 501 502 if (subToolsetVersion != null) 503 { 504 subToolsetsWithVersion.Add(subToolsetVersion, subToolsetName); 505 } 506 else 507 { 508 // if it doesn't parse to an actual version number, shrug and just add it to the end. 509 additionalSubToolsetNames.Add(subToolsetName); 510 } 511 } 512 513 List<string> orderedSubToolsetList = new List<string>(additionalSubToolsetNames); 514 orderedSubToolsetList.AddRange(subToolsetsWithVersion.Values); 515 516 if (orderedSubToolsetList.Count > 0) 517 { 518 _defaultSubToolsetVersion = orderedSubToolsetList[orderedSubToolsetList.Count - 1]; 519 } 520 } 521 522 return _defaultSubToolsetVersion; 523 } 524 } 525 526 /// <summary> 527 /// Null if it hasn't been figured out yet; true if (some variation of) Visual Studio 2010 is installed on 528 /// the current machine, false otherwise. 529 /// </summary> 530 /// <comments> 531 /// Internal so that unit tests can use it too. 532 /// </comments> 533 internal static bool Dev10IsInstalled 534 { 535 get 536 { 537 #if FEATURE_WIN32_REGISTRY 538 if (!NativeMethodsShared.IsWindows) 539 { 540 return false; 541 } 542 543 if (s_dev10IsInstalled == null) 544 { 545 try 546 { 547 // Figure out whether Dev10 is currently installed using the following heuristic: 548 // - Check whether the overall key (installed if any version of Dev10 is installed) is there. 549 // - If it's not, no version of Dev10 exists or has ever existed on this machine, so return 'false'. 550 // - If it is, we know that some version of Dev10 has been installed at some point, but we don't know 551 // for sure whether it's still there or not. Check the inndividual keys for {Pro, Premium, Ultimate, 552 // C# Express, VB Express, C++ Express, VWD Express, LightSwitch} 2010 553 // - If even one of them exists, return 'true'. 554 // - Otherwise, return 'false. 555 if (!RegistryKeyWrapper.KeyExists(Dev10OverallInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32)) 556 { 557 s_dev10IsInstalled = false; 558 } 559 else if ( 560 RegistryKeyWrapper.KeyExists(Dev10UltimateInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) || 561 RegistryKeyWrapper.KeyExists(Dev10PremiumInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) || 562 RegistryKeyWrapper.KeyExists(Dev10ProfessionalInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) || 563 RegistryKeyWrapper.KeyExists(Dev10VCSExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) || 564 RegistryKeyWrapper.KeyExists(Dev10VBExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) || 565 RegistryKeyWrapper.KeyExists(Dev10VCExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) || 566 RegistryKeyWrapper.KeyExists(Dev10VWDExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) || 567 RegistryKeyWrapper.KeyExists(Dev10LightSwitchInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) 568 ) 569 { 570 s_dev10IsInstalled = true; 571 } 572 else 573 { 574 s_dev10IsInstalled = false; 575 } 576 } 577 catch (Exception e) 578 { 579 if (ExceptionHandling.NotExpectedRegistryException(e)) 580 { 581 throw; 582 } 583 584 // if it's a registry exception, just shrug, eat it, and move on with life on the assumption that whatever 585 // went wrong, it's pretty clear that Dev10 probably isn't installed. 586 s_dev10IsInstalled = false; 587 } 588 } 589 590 return s_dev10IsInstalled.Value; 591 #else 592 return false; 593 #endif 594 } 595 } 596 597 /// <summary> 598 /// Path to look for msbuild override task files. 599 /// </summary> 600 internal string OverrideTasksPath => _overrideTasksPath; 601 602 /// <summary> 603 /// ToolsVersion to use as the default ToolsVersion for this version of MSBuild 604 /// </summary> 605 internal string DefaultOverrideToolsVersion => _defaultOverrideToolsVersion; 606 607 /// <summary> 608 /// Map of properties to their list of fall-back search paths 609 /// </summary> 610 internal Dictionary<string, ProjectImportPathMatch> ImportPropertySearchPathsTable => _propertySearchPathsTable; 611 612 /// <summary> 613 /// Map of MSBuildExtensionsPath properties to their list of fallback search paths 614 /// </summary> 615 internal Dictionary<MSBuildExtensionsPathReferenceKind, IList<string>> MSBuildExtensionsPathSearchPathsTable 616 { 617 get; set; 618 } 619 620 /// <summary> 621 /// Function for serialization. 622 /// </summary> INodePacketTranslatable.Translate(INodePacketTranslator translator)623 void INodePacketTranslatable.Translate(INodePacketTranslator translator) 624 { 625 translator.Translate(ref _toolsVersion); 626 translator.Translate(ref _toolsPath); 627 translator.TranslateProjectPropertyInstanceDictionary(ref _properties); 628 translator.TranslateProjectPropertyInstanceDictionary(ref _environmentProperties); 629 translator.TranslateProjectPropertyInstanceDictionary(ref _globalProperties); 630 translator.TranslateDictionary(ref _subToolsets, StringComparer.OrdinalIgnoreCase, SubToolset.FactoryForDeserialization); 631 translator.Translate(ref _overrideTasksPath); 632 translator.Translate(ref _defaultOverrideToolsVersion); 633 translator.TranslateDictionary(ref _propertySearchPathsTable, StringComparer.OrdinalIgnoreCase, ProjectImportPathMatch.FactoryForDeserialization); 634 } 635 636 /// <summary> 637 /// Generates the sub-toolset version to be used with this toolset. Sub-toolset version is based on: 638 /// 1. If "VisualStudioVersion" is set as a property on the toolset itself (global or environment), 639 /// use that. 640 /// 2. Otherwise, use the default sub-toolset version for this toolset. 641 /// 642 /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used, 643 /// just the base toolset on its own. The sub-toolset version returned may not map to an existing 644 /// sub-toolset. 645 /// </summary> GenerateSubToolsetVersion()646 public string GenerateSubToolsetVersion() 647 { 648 string subToolsetVersion = GenerateSubToolsetVersion(0 /* user doesn't care about solution version */); 649 return subToolsetVersion; 650 } 651 652 /// <summary> 653 /// Generates the sub-toolset version to be used with this toolset. Sub-toolset version is based on: 654 /// 1. If the "VisualStudioVersion" global property exists in the set of properties passed to us, use it. 655 /// 2. Otherwise, if "VisualStudioVersion" is set as a property on the toolset itself (global or environment), 656 /// use that. 657 /// 3. Otherwise, use Visual Studio version from solution file if it maps to an existing sub-toolset. 658 /// 4. Otherwise, use the default sub-toolset version for this toolset. 659 /// 660 /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used, 661 /// just the base toolset on its own. The sub-toolset version returned may not map to an existing 662 /// sub-toolset. 663 /// 664 /// The global properties dictionary may be null. 665 /// </summary> 666 [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "solutionVersion-1", Justification = "Method called in restricted places. Checks done by the callee and inside the method.")] GenerateSubToolsetVersion(IDictionary<string, string> overrideGlobalProperties, int solutionVersion)667 public string GenerateSubToolsetVersion(IDictionary<string, string> overrideGlobalProperties, int solutionVersion) 668 { 669 return GenerateSubToolsetVersionUsingVisualStudioVersion(overrideGlobalProperties, solutionVersion - 1); 670 } 671 672 /// <summary> 673 /// Given a property name and a sub-toolset version, searches for that property first in the 674 /// sub-toolset, then falls back to the base toolset if necessary, and returns the property 675 /// if it was found. 676 /// </summary> GetProperty(string propertyName, string subToolsetVersion)677 public ProjectPropertyInstance GetProperty(string propertyName, string subToolsetVersion) 678 { 679 SubToolset subToolset; 680 ProjectPropertyInstance property = null; 681 682 if (SubToolsets.TryGetValue(subToolsetVersion, out subToolset)) 683 { 684 property = subToolset.Properties[propertyName]; 685 } 686 687 return property ?? (Properties[propertyName]); 688 } 689 690 /// <summary> 691 /// Factory for deserialization. 692 /// </summary> FactoryForDeserialization(INodePacketTranslator translator)693 internal static Toolset FactoryForDeserialization(INodePacketTranslator translator) 694 { 695 Toolset toolset = new Toolset(translator); 696 return toolset; 697 } 698 699 /// <summary> 700 /// Given a search path and a task pattern get a list of task or override task files. 701 /// </summary> GetTaskFiles(DirectoryGetFiles getFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string taskPattern, string searchPath, string taskFileWarning)702 internal static string[] GetTaskFiles(DirectoryGetFiles getFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string taskPattern, string searchPath, string taskFileWarning) 703 { 704 string[] defaultTasksFiles = null; 705 706 try 707 { 708 if (null != getFiles) 709 { 710 defaultTasksFiles = getFiles(searchPath, taskPattern); 711 } 712 else 713 { 714 // The order of the returned file names is not guaranteed per msdn 715 defaultTasksFiles = Directory.GetFiles(searchPath, taskPattern); 716 } 717 718 if (defaultTasksFiles.Length == 0) 719 { 720 loggingServices.LogWarning 721 ( 722 buildEventContext, 723 null, 724 new BuildEventFileInfo(/* this warning truly does not involve any file */ String.Empty), 725 taskFileWarning, 726 taskPattern, 727 searchPath, 728 String.Empty 729 ); 730 } 731 } 732 catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) 733 { 734 loggingServices.LogWarning 735 ( 736 buildEventContext, 737 null, 738 new BuildEventFileInfo(/* this warning truly does not involve any file */ String.Empty), 739 taskFileWarning, 740 taskPattern, 741 searchPath, 742 e.Message 743 ); 744 } 745 746 // Sort the file names to give a deterministic order 747 if (defaultTasksFiles != null) 748 { 749 Array.Sort<string>(defaultTasksFiles, StringComparer.OrdinalIgnoreCase); 750 return defaultTasksFiles; 751 } 752 return Array.Empty<string>(); 753 } 754 755 /// <summary> 756 /// Generates the sub-toolset version to be used with this toolset. Sub-toolset version is based on: 757 /// 1. If the "VisualStudioVersion" global property exists in the set of properties passed to us, use it. 758 /// 2. Otherwise, if "VisualStudioVersion" is set as a property on the toolset itself (global or environment), 759 /// use that. 760 /// 3. Otherwise, use Visual Studio version from solution file if it maps to an existing sub-toolset. 761 /// 4. Otherwise, use the default sub-toolset version for this toolset. 762 /// 763 /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used, 764 /// just the base toolset on its own. The sub-toolset version returned may not map to an existing 765 /// sub-toolset. 766 /// 767 /// The global properties dictionary may be null. 768 /// </summary> GenerateSubToolsetVersion(PropertyDictionary<ProjectPropertyInstance> overrideGlobalProperties)769 internal string GenerateSubToolsetVersion(PropertyDictionary<ProjectPropertyInstance> overrideGlobalProperties) 770 { 771 ProjectPropertyInstance subToolsetProperty = null; 772 string visualStudioVersion = null; 773 if (overrideGlobalProperties != null) 774 { 775 subToolsetProperty = overrideGlobalProperties[Constants.SubToolsetVersionPropertyName]; 776 777 if (subToolsetProperty != null) 778 { 779 visualStudioVersion = subToolsetProperty.EvaluatedValue; 780 return visualStudioVersion; 781 } 782 } 783 784 visualStudioVersion = GenerateSubToolsetVersion(0 /* don't care about solution version */); 785 return visualStudioVersion; 786 } 787 788 /// <summary> 789 /// Generates the sub-toolset version to be used with this toolset. Sub-toolset version is based on: 790 /// 1. If the "VisualStudioVersion" global property exists in the set of properties passed to us, use it. 791 /// 2. Otherwise, if "VisualStudioVersion" is set as a property on the toolset itself (global or environment), 792 /// use that. 793 /// 3. Otherwise, use Visual Studio version from solution file if it maps to an existing sub-toolset. 794 /// 4. Otherwise, use the default sub-toolset version for this toolset. 795 /// 796 /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used, 797 /// just the base toolset on its own. The sub-toolset version returned may not map to an existing 798 /// sub-toolset. 799 /// 800 /// The global properties dictionary may be null. 801 /// </summary> GenerateSubToolsetVersion(int visualStudioVersionFromSolution)802 internal string GenerateSubToolsetVersion(int visualStudioVersionFromSolution) 803 { 804 // Next, try the toolset global properties (before environment properties because if there's a clash between the 805 // two, global should win) 806 if (_globalProperties != null) 807 { 808 ProjectPropertyInstance visualStudioVersionProperty = _globalProperties[Constants.SubToolsetVersionPropertyName]; 809 810 if (visualStudioVersionProperty != null) 811 { 812 return visualStudioVersionProperty.EvaluatedValue; 813 } 814 } 815 816 // Next, try the toolset environment properties 817 if (_environmentProperties != null) 818 { 819 ProjectPropertyInstance visualStudioVersionProperty = _environmentProperties[Constants.SubToolsetVersionPropertyName]; 820 821 if (visualStudioVersionProperty != null) 822 { 823 return visualStudioVersionProperty.EvaluatedValue; 824 } 825 } 826 827 // The VisualStudioVersion derived from parsing the solution version in the solution file 828 string subToolsetVersion = null; 829 if (visualStudioVersionFromSolution > 0) 830 { 831 Version visualStudioVersionFromSolutionAsVersion = new Version(visualStudioVersionFromSolution, 0); 832 subToolsetVersion = SubToolsets.Keys.FirstOrDefault(version => visualStudioVersionFromSolutionAsVersion.Equals(VersionUtilities.ConvertToVersion(version))); 833 } 834 835 // Solution version also didn't work out, so fall back to default. 836 // If subToolsetVersion is null, there simply wasn't a matching solution version. 837 return subToolsetVersion ?? (DefaultSubToolsetVersion); 838 } 839 840 /// <summary> 841 /// Return a task registry stub for the tasks in the *.tasks file for this toolset 842 /// </summary> 843 /// <param name="loggingServices">The logging services used to log during task registration.</param> 844 /// <param name="buildEventContext">The build event context used to log during task registration.</param> 845 /// <param name="projectRootElementCache">The <see cref="ProjectRootElementCache"/> to use.</param> 846 /// <returns>The task registry</returns> GetTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)847 internal TaskRegistry GetTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache) 848 { 849 RegisterDefaultTasks(loggingServices, buildEventContext, projectRootElementCache); 850 return _defaultTaskRegistry; 851 } 852 853 /// <summary> 854 /// Get SubToolset version using Visual Studio version from Dev 12 solution file 855 /// </summary> GenerateSubToolsetVersionUsingVisualStudioVersion(IDictionary<string, string> overrideGlobalProperties, int visualStudioVersionFromSolution)856 internal string GenerateSubToolsetVersionUsingVisualStudioVersion(IDictionary<string, string> overrideGlobalProperties, int visualStudioVersionFromSolution) 857 { 858 string visualStudioVersion = null; 859 if (overrideGlobalProperties != null && overrideGlobalProperties.TryGetValue(Constants.SubToolsetVersionPropertyName, out visualStudioVersion)) 860 { 861 return visualStudioVersion; 862 } 863 864 visualStudioVersion = GenerateSubToolsetVersion(visualStudioVersionFromSolution); 865 return visualStudioVersion; 866 } 867 868 /// <summary> 869 /// Return a task registry for the override tasks in the *.overridetasks file for this toolset 870 /// </summary> 871 /// <param name="loggingServices">The logging services used to log during task registration.</param> 872 /// <param name="buildEventContext">The build event context used to log during task registration.</param> 873 /// <param name="projectRootElementCache">The <see cref="ProjectRootElementCache"/> to use.</param> 874 /// <returns>The task registry</returns> GetOverrideTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)875 internal TaskRegistry GetOverrideTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache) 876 { 877 RegisterOverrideTasks(loggingServices, buildEventContext, projectRootElementCache); 878 return _overrideTaskRegistry; 879 } 880 881 /// <summary> 882 /// Used to load information about default MSBuild tasks i.e. tasks that do not need to be explicitly declared in projects 883 /// with the <UsingTask> element. Default task information is read from special files, which are located in the same 884 /// directory as the MSBuild binaries. 885 /// </summary> 886 /// <remarks> 887 /// 1) a default tasks file needs the <Project> root tag in order to be well-formed 888 /// 2) the XML declaration tag <?xml ...> is ignored 889 /// 3) comment tags are always ignored regardless of their placement 890 /// 4) the rest of the tags are expected to be <UsingTask> tags 891 /// </remarks> 892 /// <param name="loggingServices">The logging services to use to log during this registration.</param> 893 /// <param name="buildEventContext">The build event context to use to log during this registration.</param> 894 /// <param name="projectRootElementCache">The <see cref="ProjectRootElementCache"/> to use.</param> RegisterDefaultTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)895 private void RegisterDefaultTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache) 896 { 897 if (!_defaultTasksRegistrationAttempted) 898 { 899 try 900 { 901 _defaultTaskRegistry = new TaskRegistry(projectRootElementCache); 902 903 InitializeProperties(loggingServices, buildEventContext); 904 905 string[] defaultTasksFiles = GetTaskFiles(_getFiles, loggingServices, buildEventContext, DefaultTasksFilePattern, ToolsPath, "DefaultTasksFileLoadFailureWarning"); 906 LoadAndRegisterFromTasksFile(ToolsPath, defaultTasksFiles, loggingServices, buildEventContext, DefaultTasksFilePattern, "DefaultTasksFileFailure", projectRootElementCache, _defaultTaskRegistry); 907 } 908 finally 909 { 910 _defaultTasksRegistrationAttempted = true; 911 } 912 } 913 } 914 915 /// <summary> 916 /// Initialize the properties which are used to evaluate the tasks files. 917 /// </summary> InitializeProperties(ILoggingService loggingServices, BuildEventContext buildEventContext)918 private void InitializeProperties(ILoggingService loggingServices, BuildEventContext buildEventContext) 919 { 920 try 921 { 922 if (_propertyBag == null) 923 { 924 List<ProjectPropertyInstance> reservedProperties = new List<ProjectPropertyInstance>(); 925 926 reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.binPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true)); 927 reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsVersion, ToolsVersion, mayBeReserved: true)); 928 929 reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true)); 930 reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion, mayBeReserved: true)); 931 reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.version, MSBuildAssemblyFileVersion.Instance.MajorMinorBuild, mayBeReserved: true)); 932 933 // Add one for the subtoolset version property -- it may or may not be set depending on whether it has already been set by the 934 // environment or global properties, but it's better to create a dictionary that's one too big than one that's one too small. 935 int count = _environmentProperties.Count + reservedProperties.Count + Properties.Values.Count + _globalProperties.Count + 1; 936 937 // GenerateSubToolsetVersion checks the environment and global properties, so it's safe to go ahead and gather the 938 // subtoolset properties here without fearing that we'll have somehow come up with the wrong subtoolset version. 939 string subToolsetVersion = this.GenerateSubToolsetVersion(); 940 SubToolset subToolset; 941 ICollection<ProjectPropertyInstance> subToolsetProperties = null; 942 943 if (subToolsetVersion != null) 944 { 945 if (SubToolsets.TryGetValue(subToolsetVersion, out subToolset)) 946 { 947 subToolsetProperties = subToolset.Properties.Values; 948 count += subToolsetProperties.Count; 949 } 950 } 951 952 _propertyBag = new PropertyDictionary<ProjectPropertyInstance>(count); 953 954 // Should be imported in the same order as in the evaluator: 955 // - Environment 956 // - Toolset 957 // - Subtoolset (if any) 958 // - Global 959 _propertyBag.ImportProperties(_environmentProperties); 960 961 _propertyBag.ImportProperties(reservedProperties); 962 963 _propertyBag.ImportProperties(Properties.Values); 964 965 if (subToolsetVersion != null) 966 { 967 _propertyBag.Set(ProjectPropertyInstance.Create(Constants.SubToolsetVersionPropertyName, subToolsetVersion)); 968 } 969 970 if (subToolsetProperties != null) 971 { 972 _propertyBag.ImportProperties(subToolsetProperties); 973 } 974 975 _propertyBag.ImportProperties(_globalProperties); 976 } 977 978 if (_expander == null) 979 { 980 _expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(_propertyBag); 981 } 982 } 983 catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) 984 { 985 loggingServices.LogError(buildEventContext, new BuildEventFileInfo(/* this warning truly does not involve any file it is just gathering properties */String.Empty), "TasksPropertyBagError", e.Message); 986 } 987 } 988 989 /// <summary> 990 /// Used to load information about MSBuild override tasks i.e. tasks that override tasks declared in tasks or project files. 991 /// </summary> RegisterOverrideTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)992 private void RegisterOverrideTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache) 993 { 994 if (!_overrideTasksRegistrationAttempted) 995 { 996 try 997 { 998 _overrideTaskRegistry = new TaskRegistry(projectRootElementCache); 999 bool overrideDirectoryExists = false; 1000 1001 try 1002 { 1003 // Make sure the override directory exists and is not empty before trying to find the files 1004 if (!String.IsNullOrEmpty(_overrideTasksPath)) 1005 { 1006 if (Path.IsPathRooted(_overrideTasksPath)) 1007 { 1008 if (null != _directoryExists) 1009 { 1010 overrideDirectoryExists = _directoryExists(_overrideTasksPath); 1011 } 1012 else 1013 { 1014 overrideDirectoryExists = Directory.Exists(_overrideTasksPath); 1015 } 1016 } 1017 1018 if (!overrideDirectoryExists) 1019 { 1020 string rootedPathMessage = ResourceUtilities.FormatResourceString("OverrideTaskNotRootedPath", _overrideTasksPath); 1021 loggingServices.LogWarning(buildEventContext, null, new BuildEventFileInfo(String.Empty /* this warning truly does not involve any file*/), "OverrideTasksFileFailure", rootedPathMessage); 1022 } 1023 } 1024 } 1025 catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) 1026 { 1027 string rootedPathMessage = ResourceUtilities.FormatResourceString("OverrideTaskProblemWithPath", _overrideTasksPath, e.Message); 1028 loggingServices.LogWarning(buildEventContext, null, new BuildEventFileInfo(String.Empty /* this warning truly does not involve any file*/), "OverrideTasksFileFailure", rootedPathMessage); 1029 } 1030 1031 if (overrideDirectoryExists) 1032 { 1033 InitializeProperties(loggingServices, buildEventContext); 1034 string[] overrideTasksFiles = GetTaskFiles(_getFiles, loggingServices, buildEventContext, OverrideTasksFilePattern, _overrideTasksPath, "OverrideTasksFileLoadFailureWarning"); 1035 1036 // Load and register any override tasks 1037 LoadAndRegisterFromTasksFile(_overrideTasksPath, overrideTasksFiles, loggingServices, buildEventContext, OverrideTasksFilePattern, "OverrideTasksFileFailure", projectRootElementCache, _overrideTaskRegistry); 1038 } 1039 } 1040 finally 1041 { 1042 _overrideTasksRegistrationAttempted = true; 1043 } 1044 } 1045 } 1046 1047 /// <summary> 1048 /// Do the actual loading of the tasks or override tasks file and register the tasks in the task registry 1049 /// </summary> LoadAndRegisterFromTasksFile(string searchPath, string[] defaultTaskFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string defaultTasksFilePattern, string taskFileError, ProjectRootElementCache projectRootElementCache, TaskRegistry registry)1050 private void LoadAndRegisterFromTasksFile(string searchPath, string[] defaultTaskFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string defaultTasksFilePattern, string taskFileError, ProjectRootElementCache projectRootElementCache, TaskRegistry registry) 1051 { 1052 foreach (string defaultTasksFile in defaultTaskFiles) 1053 { 1054 try 1055 { 1056 // Important to keep the following line since unit tests use the delegate. 1057 ProjectRootElement projectRootElement; 1058 if (_loadXmlFromPath != null) 1059 { 1060 XmlDocumentWithLocation defaultTasks = _loadXmlFromPath(defaultTasksFile); 1061 projectRootElement = ProjectRootElement.Open(defaultTasks); 1062 } 1063 else 1064 { 1065 projectRootElement = ProjectRootElement.Open(defaultTasksFile, projectRootElementCache, 1066 false /*The tasks file is not a explicitly loaded file*/, 1067 preserveFormatting: false); 1068 } 1069 1070 foreach (ProjectElement elementXml in projectRootElement.Children) 1071 { 1072 ProjectUsingTaskElement usingTask = elementXml as ProjectUsingTaskElement; 1073 1074 if (null == usingTask) 1075 { 1076 ProjectErrorUtilities.ThrowInvalidProject 1077 ( 1078 elementXml.Location, 1079 "UnrecognizedElement", 1080 elementXml.XmlElement.Name 1081 ); 1082 } 1083 1084 TaskRegistry.RegisterTasksFromUsingTaskElement<ProjectPropertyInstance, ProjectItemInstance> 1085 ( 1086 loggingServices, 1087 buildEventContext, 1088 Path.GetDirectoryName(defaultTasksFile), 1089 usingTask, 1090 registry, 1091 _expander, 1092 ExpanderOptions.ExpandProperties 1093 ); 1094 } 1095 } 1096 catch (XmlException e) 1097 { 1098 // handle XML errors in the default tasks file 1099 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(defaultTasksFile, e), taskFileError, e.Message); 1100 } 1101 catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) 1102 { 1103 loggingServices.LogError(buildEventContext, new BuildEventFileInfo(defaultTasksFile), taskFileError, e.Message); 1104 break; 1105 } 1106 } 1107 } 1108 } 1109 } 1110