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>Converts a solution file to a set of project instances which can be built.</summary> 6 //----------------------------------------------------------------------- 7 8 using System; 9 using System.Collections; 10 using System.Collections.Generic; 11 using System.Collections.Immutable; 12 using System.Collections.ObjectModel; 13 using System.Globalization; 14 using System.IO; 15 using System.Linq; 16 using System.Text; 17 using System.Xml; 18 19 using Microsoft.Build.BackEnd; 20 using Microsoft.Build.BackEnd.SdkResolution; 21 using Microsoft.Build.Framework; 22 using Microsoft.Build.Shared; 23 using Microsoft.Build.Collections; 24 25 using Project = Microsoft.Build.Evaluation.Project; 26 using ProjectCollection = Microsoft.Build.Evaluation.ProjectCollection; 27 using ProjectItem = Microsoft.Build.Evaluation.ProjectItem; 28 using IProperty = Microsoft.Build.Evaluation.IProperty; 29 30 using Constants = Microsoft.Build.Internal.Constants; 31 using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; 32 33 using FrameworkName = System.Runtime.Versioning.FrameworkName; 34 using Microsoft.Build.Execution; 35 36 namespace Microsoft.Build.Construction 37 { 38 /// <summary> 39 /// This class is used to generate an MSBuild wrapper project for a solution file. 40 /// </summary> 41 internal class SolutionProjectGenerator 42 { 43 #region Private Fields 44 45 /// <summary> 46 /// Name of the property used to store the path to the solution being built. 47 /// </summary> 48 internal const string SolutionPathPropertyName = "SolutionPath"; 49 50 /// <summary> 51 /// The path node to add in when the output directory for a website is overridden. 52 /// </summary> 53 private const string WebProjectOverrideFolder = "_PublishedWebsites"; 54 55 /// <summary> 56 /// The set of properties all projects in the solution should be built with 57 /// </summary> 58 private const string SolutionProperties = "BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"; 59 60 /// <summary> 61 /// The set of properties which identify the configuration and platform to build a project with 62 /// </summary> 63 private const string SolutionConfigurationAndPlatformProperties = "Configuration=$(Configuration); Platform=$(Platform)"; 64 65 /// <summary> 66 /// A known list of target names to create. This is for backwards compatibility. 67 /// </summary> 68 internal static readonly ISet<string> _defaultTargetNames = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase, 69 "Build", 70 "Clean", 71 "Rebuild", 72 "Publish" 73 ); 74 75 /// <summary> 76 /// Version 2.0 77 /// </summary> 78 private readonly Version _version20 = new Version(2, 0); 79 80 /// <summary> 81 /// Version 4.0 82 /// </summary> 83 private readonly Version _version40 = new Version(4, 0); 84 85 /// <summary> 86 /// The list of global properties we set on each metaproject and which get passed to each project when building. 87 /// </summary> 88 private Tuple<string, string>[] _metaprojectGlobalProperties = new Tuple<string, string>[] 89 { 90 new Tuple<string, string>("Configuration", null), // This is the solution configuration in a metaproject, and project configuration on an actual project 91 new Tuple<string, string>("Platform", null), // This is the solution platform in a metaproject, and project platform on an actual project 92 new Tuple<string, string>("BuildingSolutionFile", "true"), 93 new Tuple<string, string>("CurrentSolutionConfigurationContents", null), 94 new Tuple<string, string>("SolutionDir", null), 95 new Tuple<string, string>("SolutionExt", null), 96 new Tuple<string, string>("SolutionFileName", null), 97 new Tuple<string, string>("SolutionName", null), 98 new Tuple<string, string>(SolutionPathPropertyName, null) 99 }; 100 101 /// <summary> 102 /// The SolutionFile containing information about the solution we're generating a wrapper for. 103 /// </summary> 104 private SolutionFile _solutionFile; 105 106 /// <summary> 107 /// The global properties passed under which the project should be opened. 108 /// </summary> 109 private IDictionary<string, string> _globalProperties; 110 111 /// <summary> 112 /// The ToolsVersion passed on the commandline, if any. May be null. 113 /// </summary> 114 private string _toolsVersionOverride; 115 116 /// <summary> 117 /// The context of this build (used for logging purposes). 118 /// </summary> 119 private BuildEventContext _projectBuildEventContext; 120 121 /// <summary> 122 /// The LoggingService used to log messages. 123 /// </summary> 124 private ILoggingService _loggingService; 125 126 /// <summary> 127 /// The list of targets specified to use. 128 /// </summary> 129 private readonly IReadOnlyCollection<string> _targetNames = new Collection<string>(); 130 131 /// <summary> 132 /// The solution configuration selected for this build. 133 /// </summary> 134 private string _selectedSolutionConfiguration; 135 136 /// <summary> 137 /// The <see cref="ISdkResolverService"/> to use. 138 /// </summary> 139 private ISdkResolverService _sdkResolverService; 140 141 /// <summary> 142 /// The current build submission ID. 143 /// </summary> 144 private int _submissionId; 145 146 #endregion // Private Fields 147 148 #region Constructors 149 150 /// <summary> 151 /// Constructor. 152 /// </summary> SolutionProjectGenerator(SolutionFile solution, IDictionary<string, string> globalProperties, string toolsVersionOverride, BuildEventContext projectBuildEventContext, ILoggingService loggingService, IReadOnlyCollection<string> targetNames, ISdkResolverService sdkResolverService, int submissionId)153 private SolutionProjectGenerator 154 (SolutionFile solution, 155 IDictionary<string, string> globalProperties, 156 string toolsVersionOverride, 157 BuildEventContext projectBuildEventContext, 158 ILoggingService loggingService, 159 IReadOnlyCollection<string> targetNames, 160 ISdkResolverService sdkResolverService, 161 int submissionId) 162 { 163 _solutionFile = solution; 164 _globalProperties = globalProperties ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); 165 _toolsVersionOverride = toolsVersionOverride; 166 _projectBuildEventContext = projectBuildEventContext; 167 _loggingService = loggingService; 168 _sdkResolverService = sdkResolverService ?? SdkResolverService.Instance; 169 _submissionId = submissionId; 170 171 if (targetNames != null) 172 { 173 _targetNames = targetNames.Select(i => i.Split(new[] {':'}, 2, StringSplitOptions.RemoveEmptyEntries).Last()).ToList(); 174 } 175 } 176 177 #endregion // Constructors 178 179 #region Methods 180 181 /// <summary> 182 /// This method generates an MSBuild project file from the list of projects and project dependencies 183 /// that have been collected from the solution file. 184 /// </summary> 185 /// <param name="solution">The parser which contains the solution file.</param> 186 /// <param name="globalProperties">The global properties.</param> 187 /// <param name="toolsVersionOverride">Tools Version override (may be null). This should be any tools version explicitly passed to the command-line or from an MSBuild ToolsVersion parameter.</param> 188 /// <param name="projectBuildEventContext">The logging context for this project.</param> 189 /// <param name="loggingService">The logging service.</param> 190 /// <param name="targetNames">A collection of target names the user requested to be built.</param> 191 /// <param name="sdkResolverService">An <see cref="ISdkResolverService"/> to use.</param> 192 /// <param name="submissionId">The current build submission ID.</param> 193 /// <returns>An array of ProjectInstances. The first instance is the traversal project, the remaining are the metaprojects for each project referenced in the solution.</returns> Generate( SolutionFile solution, IDictionary<string, string> globalProperties, string toolsVersionOverride, BuildEventContext projectBuildEventContext, ILoggingService loggingService, IReadOnlyCollection<string> targetNames = default(IReadOnlyCollection<string>), ISdkResolverService sdkResolverService = null, int submissionId = BuildEventContext.InvalidSubmissionId)194 internal static ProjectInstance[] Generate 195 ( 196 SolutionFile solution, 197 IDictionary<string, string> globalProperties, 198 string toolsVersionOverride, 199 BuildEventContext projectBuildEventContext, 200 ILoggingService loggingService, 201 IReadOnlyCollection<string> targetNames = default(IReadOnlyCollection<string>), 202 ISdkResolverService sdkResolverService = null, 203 int submissionId = BuildEventContext.InvalidSubmissionId) 204 { 205 SolutionProjectGenerator projectGenerator = new SolutionProjectGenerator 206 ( 207 solution, 208 globalProperties, 209 toolsVersionOverride, 210 projectBuildEventContext, 211 loggingService, 212 targetNames, 213 sdkResolverService, 214 submissionId 215 ); 216 217 return projectGenerator.Generate(); 218 } 219 220 /// <summary> 221 /// Adds a new property group with contents of the given solution configuration to the project 222 /// Internal for unit-testing. 223 /// </summary> AddPropertyGroupForSolutionConfiguration(ProjectRootElement msbuildProject, SolutionFile solutionFile, SolutionConfigurationInSolution solutionConfiguration)224 internal static void AddPropertyGroupForSolutionConfiguration(ProjectRootElement msbuildProject, SolutionFile solutionFile, SolutionConfigurationInSolution solutionConfiguration) 225 { 226 ProjectPropertyGroupElement solutionConfigurationProperties = msbuildProject.CreatePropertyGroupElement(); 227 msbuildProject.AppendChild(solutionConfigurationProperties); 228 solutionConfigurationProperties.Condition = GetConditionStringForConfiguration(solutionConfiguration); 229 230 StringBuilder solutionConfigurationContents = new StringBuilder(1024); 231 XmlWriterSettings settings = new XmlWriterSettings(); 232 settings.Indent = true; 233 settings.OmitXmlDeclaration = true; 234 using (XmlWriter xw = XmlWriter.Create(solutionConfigurationContents, settings)) 235 { 236 xw.WriteStartElement("SolutionConfiguration"); 237 238 // add a project configuration entry for each project in the solution 239 foreach (ProjectInSolution project in solutionFile.ProjectsInOrder) 240 { 241 ProjectConfigurationInSolution projectConfiguration = null; 242 243 if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out projectConfiguration)) 244 { 245 xw.WriteStartElement("ProjectConfiguration"); 246 xw.WriteAttributeString("Project", project.ProjectGuid); 247 xw.WriteAttributeString("AbsolutePath", project.AbsolutePath); 248 xw.WriteAttributeString("BuildProjectInSolution", projectConfiguration.IncludeInBuild.ToString()); 249 xw.WriteString(projectConfiguration.FullName); 250 251 foreach (string dependencyProjectGuid in project.Dependencies) 252 { 253 // This is a project that the current project depends *ON* (ie., it must build first) 254 ProjectInSolution dependencyProject; 255 if (!solutionFile.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out dependencyProject)) 256 { 257 // If it's not itself part of the solution, that's an invalid solution 258 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(dependencyProject != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solutionFile.FullPath), "SolutionParseProjectDepNotFoundError", project.ProjectGuid, dependencyProjectGuid); 259 } 260 261 // Add it to the list of dependencies, but only if it should build in this solution configuration 262 // (If a project is not selected for build in the solution configuration, it won't build even if it's depended on by something that IS selected for build) 263 // .. and only if it's known to be MSBuild format, as projects can't use the information otherwise 264 if (dependencyProject.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) 265 { 266 ProjectConfigurationInSolution dependencyProjectConfiguration = null; 267 if (dependencyProject.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out dependencyProjectConfiguration) && 268 WouldProjectBuild(solutionFile, solutionConfiguration.FullName, dependencyProject, dependencyProjectConfiguration)) 269 { 270 xw.WriteStartElement("ProjectDependency"); 271 xw.WriteAttributeString("Project", dependencyProjectGuid); 272 xw.WriteEndElement(); 273 } 274 } 275 } 276 277 xw.WriteEndElement(); // </ProjectConfiguration> 278 } 279 } 280 281 xw.WriteEndElement(); // </SolutionConfiguration> 282 } 283 284 var escapedSolutionConfigurationContents = EscapingUtilities.Escape(solutionConfigurationContents.ToString()); 285 286 solutionConfigurationProperties.AddProperty("CurrentSolutionConfigurationContents", escapedSolutionConfigurationContents); 287 288 msbuildProject.AddItem( 289 "SolutionConfiguration", 290 solutionConfiguration.FullName, 291 new Dictionary<string, string> 292 { 293 { "Configuration", solutionConfiguration.ConfigurationName }, 294 { "Platform", solutionConfiguration.PlatformName }, 295 { "Content", escapedSolutionConfigurationContents }, 296 }); 297 } 298 299 /// <summary> 300 /// Add a new error/warning/message tag into the given target 301 /// </summary> AddErrorWarningMessageElement( ProjectTargetElement target, string elementType, bool treatAsLiteral, string textResourceName, params object[] args )302 internal static ProjectTaskElement AddErrorWarningMessageElement 303 ( 304 ProjectTargetElement target, 305 string elementType, 306 bool treatAsLiteral, 307 string textResourceName, 308 params object[] args 309 ) 310 { 311 string code = null; 312 string helpKeyword = null; 313 string text = ResourceUtilities.FormatResourceString(out code, out helpKeyword, textResourceName, args); 314 315 if (treatAsLiteral) 316 { 317 text = EscapingUtilities.Escape(text); 318 } 319 320 ProjectTaskElement task = target.AddTask(elementType); 321 task.SetParameter("Text", text); 322 323 if ((elementType != XMakeElements.message) && (code != null)) 324 { 325 task.SetParameter("Code", EscapingUtilities.Escape(code)); 326 } 327 328 if ((elementType != XMakeElements.message) && (helpKeyword != null)) 329 { 330 task.SetParameter("HelpKeyword", EscapingUtilities.Escape(helpKeyword)); 331 } 332 333 return task; 334 } 335 336 /// <summary> 337 /// Normally the active solution configuration/platform is determined when we build the solution 338 /// wrapper project, not when we create it. However, we need to know them to scan project references 339 /// for the right project configuration/platform. It's unlikely that references would be conditional, 340 /// but still possible and we want to get that case right. 341 /// </summary> PredictActiveSolutionConfigurationName(SolutionFile solutionFile, IDictionary<string, string> globalProperties)342 internal static string PredictActiveSolutionConfigurationName(SolutionFile solutionFile, IDictionary<string, string> globalProperties) 343 { 344 string candidateFullSolutionConfigurationName = DetermineLikelyActiveSolutionConfiguration(solutionFile, globalProperties); 345 346 // Now check if this solution configuration actually exists 347 string fullSolutionConfigurationName = null; 348 349 foreach (SolutionConfigurationInSolution solutionConfiguration in solutionFile.SolutionConfigurations) 350 { 351 if (String.Equals(solutionConfiguration.FullName, candidateFullSolutionConfigurationName, StringComparison.OrdinalIgnoreCase)) 352 { 353 fullSolutionConfigurationName = solutionConfiguration.FullName; 354 break; 355 } 356 } 357 358 return fullSolutionConfigurationName; 359 } 360 361 /// <summary> 362 /// Returns the name of the metaproject for an actual project. 363 /// </summary> 364 /// <param name="fullPathToProject">The full path to the actual project</param> 365 /// <returns>The metaproject path name</returns> GetMetaprojectName(string fullPathToProject)366 private static string GetMetaprojectName(string fullPathToProject) 367 { 368 return EscapingUtilities.Escape(fullPathToProject + ".metaproj"); 369 } 370 371 /// <summary> 372 /// Figure out what tools version to build the solution wrapper project with. If a /tv 373 /// switch was passed in, use that; otherwise fall back to the default (12.0). 374 /// </summary> DetermineWrapperProjectToolsVersion(string toolsVersionOverride, out bool explicitToolsVersionSpecified)375 private static string DetermineWrapperProjectToolsVersion(string toolsVersionOverride, out bool explicitToolsVersionSpecified) 376 { 377 string wrapperProjectToolsVersion = toolsVersionOverride; 378 379 if (wrapperProjectToolsVersion == null) 380 { 381 explicitToolsVersionSpecified = false; 382 wrapperProjectToolsVersion = Constants.defaultSolutionWrapperProjectToolsVersion; 383 } 384 else 385 { 386 explicitToolsVersionSpecified = true; 387 } 388 389 return wrapperProjectToolsVersion; 390 } 391 392 /// <summary> 393 /// Add a call to the ResolveAssemblyReference task to crack the pre-resolved referenced 394 /// assemblies for the complete list of dependencies, PDBs, satellites, etc. The invoke 395 /// the Copy task to copy all these files (or at least the ones that RAR determined should 396 /// be copied local) into the web project's bin directory. 397 /// </summary> AddTasksToCopyAllDependenciesIntoBinDir( ProjectTargetInstance target, ProjectInSolution project, string referenceItemName, string conditionDescribingValidConfigurations )398 private static void AddTasksToCopyAllDependenciesIntoBinDir 399 ( 400 ProjectTargetInstance target, 401 ProjectInSolution project, 402 string referenceItemName, 403 string conditionDescribingValidConfigurations 404 ) 405 { 406 string copyLocalFilesItemName = referenceItemName + "_CopyLocalFiles"; 407 string targetFrameworkDirectoriesName = GenerateSafePropertyName(project, "_TargetFrameworkDirectories"); 408 string fullFrameworkRefAssyPathName = GenerateSafePropertyName(project, "_FullFrameworkReferenceAssemblyPaths"); 409 string destinationFolder = String.Format(CultureInfo.InvariantCulture, @"$({0})\Bin\", GenerateSafePropertyName(project, "AspNetPhysicalPath")); 410 411 // This is a bit of a hack. We're actually calling the "Copy" task on all of 412 // the *non-existent* files. Why? Because we want to emit a warning in the 413 // log for each non-existent file, and the Copy task does that nicely for us. 414 // I would have used the <Warning> task except for the fact that we are in 415 // string-resource lockdown. 416 ProjectTaskInstance copyNonExistentReferencesTask = target.AddTask("Copy", String.Format(CultureInfo.InvariantCulture, "!Exists('%({0}.Identity)')", referenceItemName), "true"); 417 copyNonExistentReferencesTask.SetParameter("SourceFiles", "@(" + referenceItemName + "->'%(FullPath)')"); 418 copyNonExistentReferencesTask.SetParameter("DestinationFolder", destinationFolder); 419 420 // We need to determine the appropriate TargetFrameworkMoniker to pass to GetReferenceAssemblyPaths, 421 // so that we will pass the appropriate target framework directories to RAR. 422 ProjectTaskInstance getRefAssembliesTask = target.AddTask("GetReferenceAssemblyPaths", null, null); 423 getRefAssembliesTask.SetParameter("TargetFrameworkMoniker", project.TargetFrameworkMoniker); 424 getRefAssembliesTask.SetParameter("RootPath", "$(TargetFrameworkRootPath)"); 425 getRefAssembliesTask.AddOutputProperty("ReferenceAssemblyPaths", targetFrameworkDirectoriesName, null); 426 getRefAssembliesTask.AddOutputProperty("FullFrameworkReferenceAssemblyPaths", fullFrameworkRefAssyPathName, null); 427 428 // Call ResolveAssemblyReference on each of the .DLL files that were found on 429 // disk from the .REFRESH files as well as the P2P references. RAR will crack 430 // the dependencies, find PDBs, satellite assemblies, etc., and determine which 431 // files need to be copy-localed. 432 ProjectTaskInstance rarTask = target.AddTask("ResolveAssemblyReference", String.Format(CultureInfo.InvariantCulture, "Exists('%({0}.Identity)')", referenceItemName), null); 433 rarTask.SetParameter("Assemblies", "@(" + referenceItemName + "->'%(FullPath)')"); 434 rarTask.SetParameter("TargetFrameworkDirectories", "$(" + targetFrameworkDirectoriesName + ")"); 435 rarTask.SetParameter("FullFrameworkFolders", "$(" + fullFrameworkRefAssyPathName + ")"); 436 rarTask.SetParameter("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}"); 437 rarTask.SetParameter("FindDependencies", "true"); 438 rarTask.SetParameter("FindSatellites", "true"); 439 rarTask.SetParameter("FindSerializationAssemblies", "true"); 440 rarTask.SetParameter("FindRelatedFiles", "true"); 441 rarTask.SetParameter("TargetFrameworkMoniker", project.TargetFrameworkMoniker); 442 rarTask.AddOutputItem("CopyLocalFiles", copyLocalFilesItemName, null); 443 444 // Copy all the copy-local files (reported by RAR) to the web project's "bin" 445 // directory. 446 ProjectTaskInstance copyTask = target.AddTask("Copy", conditionDescribingValidConfigurations, null); 447 copyTask.SetParameter("SourceFiles", "@(" + copyLocalFilesItemName + ")"); 448 copyTask.SetParameter 449 ( 450 "DestinationFiles", 451 String.Format(CultureInfo.InvariantCulture, @"@({0}->'{1}%(DestinationSubDirectory)%(Filename)%(Extension)')", copyLocalFilesItemName, destinationFolder) 452 ); 453 } 454 455 /// <summary> 456 /// This code handles the *.REFRESH files that are in the "bin" subdirectory of 457 /// a web project. These .REFRESH files are just text files that contain absolute or 458 /// relative paths to the referenced assemblies. The goal of these tasks is to 459 /// search all *.REFRESH files and extract fully-qualified absolute paths for 460 /// each of the references. 461 /// </summary> AddTasksToResolveAutoRefreshFileReferences( ProjectTargetInstance target, ProjectInSolution project, string referenceItemName )462 private static void AddTasksToResolveAutoRefreshFileReferences 463 ( 464 ProjectTargetInstance target, 465 ProjectInSolution project, 466 string referenceItemName 467 ) 468 { 469 string webRoot = "$(" + GenerateSafePropertyName(project, "AspNetPhysicalPath") + ")"; 470 471 // Create an item list containing each of the .REFRESH files. 472 ProjectTaskInstance createItemTask = target.AddTask("CreateItem", null, null); 473 createItemTask.SetParameter("Include", webRoot + @"\Bin\*.refresh"); 474 createItemTask.AddOutputItem("Include", referenceItemName + "_RefreshFile", null); 475 476 // Read the lines out of each .REFRESH file; they should be paths to .DLLs. Put these paths 477 // into an item list. 478 ProjectTaskInstance readLinesTask = target.AddTask("ReadLinesFromFile", String.Format(CultureInfo.InvariantCulture, @" '%({0}_RefreshFile.Identity)' != '' ", referenceItemName), null); 479 readLinesTask.SetParameter("File", String.Format(CultureInfo.InvariantCulture, @"%({0}_RefreshFile.Identity)", referenceItemName)); 480 readLinesTask.AddOutputItem("Lines", referenceItemName + "_ReferenceRelPath", null); 481 482 // Take those paths and combine them with the root of the web project to form either 483 // an absolute path or a path relative to the .SLN file. These paths can be passed 484 // directly to RAR later. 485 ProjectTaskInstance combinePathTask = target.AddTask("CombinePath", null, null); 486 combinePathTask.SetParameter("BasePath", webRoot); 487 combinePathTask.SetParameter("Paths", String.Format(CultureInfo.InvariantCulture, @"@({0}_ReferenceRelPath)", referenceItemName)); 488 combinePathTask.AddOutputItem("CombinedPaths", referenceItemName, null); 489 } 490 491 /// <summary> 492 /// Adds an MSBuild task to the specified target 493 /// </summary> AddMSBuildTaskInstance( ProjectTargetInstance target, string projectPath, string msbuildTargetName, string configurationName, string platformName, bool specifyProjectToolsVersion )494 private static ProjectTaskInstance AddMSBuildTaskInstance 495 ( 496 ProjectTargetInstance target, 497 string projectPath, 498 string msbuildTargetName, 499 string configurationName, 500 string platformName, 501 bool specifyProjectToolsVersion 502 ) 503 { 504 ProjectTaskInstance msbuildTask = target.AddTask("MSBuild", null, null); 505 msbuildTask.SetParameter("Projects", EscapingUtilities.Escape(projectPath)); 506 507 if (msbuildTargetName != null && msbuildTargetName.Length > 0) 508 { 509 msbuildTask.SetParameter("Targets", msbuildTargetName); 510 } 511 512 string additionalProperties = string.Format( 513 CultureInfo.InvariantCulture, 514 "Configuration={0}; Platform={1}; BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)", 515 EscapingUtilities.Escape(configurationName), 516 EscapingUtilities.Escape(platformName) 517 ); 518 519 msbuildTask.SetParameter("Properties", additionalProperties); 520 if (specifyProjectToolsVersion) 521 { 522 msbuildTask.SetParameter("ToolsVersion", "$(ProjectToolsVersion)"); 523 } 524 525 return msbuildTask; 526 } 527 528 /// <summary> 529 /// Takes a project in the solution and a base property name, and creates a new property name 530 /// that can safely be used as an XML element name, and is also unique to that project (by 531 /// embedding the project's GUID into the property name. 532 /// </summary> GenerateSafePropertyName( ProjectInSolution proj, string propertyName )533 private static string GenerateSafePropertyName 534 ( 535 ProjectInSolution proj, 536 string propertyName 537 ) 538 { 539 // XML element names cannot contain curly braces, so get rid of them from the project guid. 540 string projectGuid = proj.ProjectGuid.Substring(1, proj.ProjectGuid.Length - 2); 541 return "Project_" + projectGuid + "_" + propertyName; 542 } 543 544 /// <summary> 545 /// Makes a legal item name from a given string by replacing invalid characters with '_' 546 /// </summary> MakeIntoSafeItemName(string name)547 private static string MakeIntoSafeItemName(string name) 548 { 549 StringBuilder builder = new StringBuilder(name); 550 551 if (name.Length > 0) 552 { 553 if (!XmlUtilities.IsValidInitialElementNameCharacter(name[0])) 554 { 555 builder[0] = '_'; 556 } 557 } 558 559 for (int i = 1; i < builder.Length; i++) 560 { 561 if (!XmlUtilities.IsValidSubsequentElementNameCharacter(builder[i])) 562 { 563 builder[i] = '_'; 564 } 565 } 566 567 return builder.ToString(); 568 } 569 570 /// <summary> 571 /// Add a new error/warning/message tag into the given target 572 /// </summary> AddErrorWarningMessageInstance( ProjectTargetInstance target, string condition, string elementType, bool treatAsLiteral, string textResourceName, params object[] args )573 private static ProjectTaskInstance AddErrorWarningMessageInstance 574 ( 575 ProjectTargetInstance target, 576 string condition, 577 string elementType, 578 bool treatAsLiteral, 579 string textResourceName, 580 params object[] args 581 ) 582 { 583 string code = null; 584 string helpKeyword = null; 585 string text = ResourceUtilities.FormatResourceString(out code, out helpKeyword, textResourceName, args); 586 587 if (treatAsLiteral) 588 { 589 text = EscapingUtilities.Escape(text); 590 } 591 592 ProjectTaskInstance task = target.AddTask(elementType, condition, null); 593 task.SetParameter("Text", text); 594 595 if ((elementType != XMakeElements.message) && (code != null)) 596 { 597 task.SetParameter("Code", EscapingUtilities.Escape(code)); 598 } 599 600 if ((elementType != XMakeElements.message) && (helpKeyword != null)) 601 { 602 task.SetParameter("HelpKeyword", EscapingUtilities.Escape(helpKeyword)); 603 } 604 605 return task; 606 } 607 608 /// <summary> 609 /// A helper method for constructing conditions for a solution configuration 610 /// </summary> 611 /// <remarks> 612 /// Sample configuration condition: 613 /// '$(Configuration)' == 'Release' and '$(Platform)' == 'Any CPU' 614 /// </remarks> GetConditionStringForConfiguration(SolutionConfigurationInSolution configuration)615 private static string GetConditionStringForConfiguration(SolutionConfigurationInSolution configuration) 616 { 617 return string.Format 618 ( 619 CultureInfo.InvariantCulture, 620 " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", 621 EscapingUtilities.Escape(configuration.ConfigurationName), 622 EscapingUtilities.Escape(configuration.PlatformName) 623 ); 624 } 625 626 /// <summary> 627 /// Figure out what solution configuration we are going to build, whether or not it actually exists in the solution 628 /// file. 629 /// </summary> DetermineLikelyActiveSolutionConfiguration(SolutionFile solutionFile, IDictionary<string, string> globalProperties)630 private static string DetermineLikelyActiveSolutionConfiguration(SolutionFile solutionFile, IDictionary<string, string> globalProperties) 631 { 632 string activeSolutionConfiguration = null; 633 string activeSolutionPlatform = null; 634 635 globalProperties.TryGetValue("Configuration", out activeSolutionConfiguration); 636 globalProperties.TryGetValue("Platform", out activeSolutionPlatform); 637 638 if (String.IsNullOrEmpty(activeSolutionConfiguration)) 639 { 640 activeSolutionConfiguration = solutionFile.GetDefaultConfigurationName(); 641 } 642 643 if (String.IsNullOrEmpty(activeSolutionPlatform)) 644 { 645 activeSolutionPlatform = solutionFile.GetDefaultPlatformName(); 646 } 647 648 SolutionConfigurationInSolution configurationInSolution = new SolutionConfigurationInSolution(activeSolutionConfiguration, activeSolutionPlatform); 649 650 return configurationInSolution.FullName; 651 } 652 653 /// <summary> 654 /// Returns true if the specified project will build in the currently selected solution configuration. 655 /// </summary> WouldProjectBuild(SolutionFile solutionFile, string selectedSolutionConfiguration, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration)656 private static bool WouldProjectBuild(SolutionFile solutionFile, string selectedSolutionConfiguration, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration) 657 { 658 if (projectConfiguration == null) 659 { 660 if (project.ProjectType == SolutionProjectType.WebProject) 661 { 662 // Sometimes web projects won't have the configuration we need (Release typically.) But they should still build if there is 663 // a solution configuration for it 664 foreach (SolutionConfigurationInSolution configuration in solutionFile.SolutionConfigurations) 665 { 666 if (String.Equals(configuration.FullName, selectedSolutionConfiguration, StringComparison.OrdinalIgnoreCase)) 667 { 668 return true; 669 } 670 } 671 } 672 673 // No configuration, so it can't build. 674 return false; 675 } 676 677 if (!projectConfiguration.IncludeInBuild) 678 { 679 // Not included in the build. 680 return false; 681 } 682 683 return true; 684 } 685 686 /// <summary> 687 /// Private method: generates an MSBuild wrapper project for the solution passed in; the MSBuild wrapper 688 /// project to be generated is the private variable "msbuildProject" and the SolutionFile containing information 689 /// about the solution is the private variable "solutionFile" 690 /// </summary> Generate()691 private ProjectInstance[] Generate() 692 { 693 // Validate against our minimum for upgradable projects 694 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile 695 ( 696 (_solutionFile.Version >= SolutionFile.slnFileMinVersion), 697 "SubCategoryForSolutionParsingErrors", 698 new BuildEventFileInfo(_solutionFile.FullPath), 699 "SolutionParseUpgradeNeeded" 700 ); 701 702 // This is needed in order to make decisions about tools versions such as whether to put a 703 // ToolsVersion parameter on <MSBuild> task tags and what MSBuildToolsPath to use when 704 // scanning child projects for dependency information. 705 // The knowledge of whether it was explicitly specified is required because otherwise we 706 // don't know whether we need to pass the ToolsVersion on to the child projects or not. 707 bool explicitToolsVersionSpecified = false; 708 string wrapperProjectToolsVersion = DetermineWrapperProjectToolsVersion(_toolsVersionOverride, out explicitToolsVersionSpecified); 709 710 return CreateSolutionProject(wrapperProjectToolsVersion, explicitToolsVersionSpecified); 711 } 712 713 /// <summary> 714 /// Given a parsed solution, generate a top level traversal project and the metaprojects representing the dependencies for each real project 715 /// referenced in the solution. 716 /// </summary> CreateSolutionProject(string wrapperProjectToolsVersion, bool explicitToolsVersionSpecified)717 private ProjectInstance[] CreateSolutionProject(string wrapperProjectToolsVersion, bool explicitToolsVersionSpecified) 718 { 719 AddFakeReleaseSolutionConfigurationIfNecessary(); 720 721 if (_solutionFile.ContainsWebDeploymentProjects) 722 { 723 // If there are Web Deployment projects, we need to scan those project files 724 // and specify the references explicitly. 725 // Other references are either ProjectReferences (taken care of by MSBuild) or 726 // explicit manual references in the solution file -- which get parsed out by 727 // the SolutionParser. 728 string childProjectToolsVersion = DetermineChildProjectToolsVersion(wrapperProjectToolsVersion); 729 string fullSolutionConfigurationName = PredictActiveSolutionConfigurationName(); 730 731 ScanProjectDependencies(childProjectToolsVersion, fullSolutionConfigurationName); 732 } 733 734 // Get a list of all actual projects in the solution 735 List<ProjectInSolution> projectsInOrder = new List<ProjectInSolution>(_solutionFile.ProjectsInOrder.Count); 736 foreach (ProjectInSolution project in _solutionFile.ProjectsInOrder) 737 { 738 if (SolutionFile.IsBuildableProject(project)) 739 { 740 projectsInOrder.Add(project); 741 } 742 } 743 744 // Create the list of our generated projects. 745 List<ProjectInstance> projectInstances = new List<ProjectInstance>(projectsInOrder.Count + 1); 746 747 // Create the project instance for the traversal project. 748 ProjectInstance traversalInstance = CreateTraversalInstance(wrapperProjectToolsVersion, explicitToolsVersionSpecified, projectsInOrder); 749 750 // Compute the solution configuration which will be used for this build. We will use it later. 751 _selectedSolutionConfiguration = String.Format(CultureInfo.InvariantCulture, "{0}|{1}", traversalInstance.GetProperty("Configuration").EvaluatedValue, traversalInstance.GetProperty("Platform").EvaluatedValue); 752 projectInstances.Add(traversalInstance); 753 754 // Now evaluate all of the projects in the solution and handle them appropriately. 755 EvaluateAndAddProjects(projectsInOrder, projectInstances, traversalInstance, _selectedSolutionConfiguration); 756 757 // Special environment variable to allow people to see the in-memory MSBuild project generated 758 // to represent the SLN. 759 if (Environment.GetEnvironmentVariable("MSBuildEmitSolution") != null) 760 { 761 foreach (ProjectInstance instance in projectInstances) 762 { 763 instance.ToProjectRootElement().Save(instance.FullPath); 764 } 765 } 766 767 return projectInstances.ToArray(); 768 } 769 770 /// <summary> 771 /// Examine each project in the solution, add references and targets for it, and create metaprojects if necessary. 772 /// </summary> EvaluateAndAddProjects(List<ProjectInSolution> projectsInOrder, List<ProjectInstance> projectInstances, ProjectInstance traversalInstance, string selectedSolutionConfiguration)773 private void EvaluateAndAddProjects(List<ProjectInSolution> projectsInOrder, List<ProjectInstance> projectInstances, ProjectInstance traversalInstance, string selectedSolutionConfiguration) 774 { 775 // Now add all of the per-project items, targets and metaprojects. 776 foreach (ProjectInSolution project in projectsInOrder) 777 { 778 ProjectConfigurationInSolution projectConfiguration; 779 project.ProjectConfigurations.TryGetValue(selectedSolutionConfiguration, out projectConfiguration); 780 if (!WouldProjectBuild(_solutionFile, selectedSolutionConfiguration, project, projectConfiguration)) 781 { 782 // Project wouldn't build, so omit it from further processing. 783 continue; 784 } 785 786 bool canBuildDirectly = CanBuildDirectly(traversalInstance, project, projectConfiguration); 787 788 // Add an entry to @(ProjectReference) for the project. This will be either a reference directly to the project, or to the 789 // metaproject, as appropriate. 790 AddProjectReference(traversalInstance, traversalInstance, project, projectConfiguration, canBuildDirectly); 791 792 // Add the targets to the traversal project for each standard target. These will either invoke the project directly or invoke the 793 // metaproject, as appropriate 794 AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, null, "BuildOutput", canBuildDirectly); 795 AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Clean", null, canBuildDirectly); 796 AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Rebuild", "BuildOutput", canBuildDirectly); 797 AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Publish", null, canBuildDirectly); 798 799 800 // Add any other targets specified by the user that were not already added 801 foreach (string targetName in _targetNames.Where(i => !traversalInstance.Targets.ContainsKey(i))) 802 { 803 AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, targetName, null, canBuildDirectly); 804 } 805 806 // If we cannot build the project directly, then we need to generate a metaproject for it. 807 if (!canBuildDirectly) 808 { 809 ProjectInstance metaProject = CreateMetaproject(traversalInstance, project, projectConfiguration); 810 projectInstances.Add(metaProject); 811 } 812 } 813 814 // Add any other targets specified by the user that were not already added 815 foreach (string targetName in _targetNames.Where(i => !traversalInstance.Targets.ContainsKey(i))) 816 { 817 AddTraversalReferencesTarget(traversalInstance, targetName, null); 818 } 819 } 820 821 /// <summary> 822 /// Adds the standard targets to the traversal project. 823 /// </summary> AddStandardTraversalTargets(ProjectInstance traversalInstance, List<ProjectInSolution> projectsInOrder)824 private void AddStandardTraversalTargets(ProjectInstance traversalInstance, List<ProjectInSolution> projectsInOrder) 825 { 826 // Add the initial target with some solution configuration validation/information 827 AddInitialTargets(traversalInstance, projectsInOrder); 828 829 // Add the targets to traverse the metaprojects. 830 AddTraversalReferencesTarget(traversalInstance, null, "CollectedBuildOutput"); 831 AddTraversalReferencesTarget(traversalInstance, "Clean", null); 832 AddTraversalReferencesTarget(traversalInstance, "Rebuild", "CollectedBuildOutput"); 833 AddTraversalReferencesTarget(traversalInstance, "Publish", null); 834 } 835 836 /// <summary> 837 /// Creates the traversal project instance. This has all of the properties against which we can perform evaluations for the remainder of the process. 838 /// </summary> CreateTraversalInstance(string wrapperProjectToolsVersion, bool explicitToolsVersionSpecified, List<ProjectInSolution> projectsInOrder)839 private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersion, bool explicitToolsVersionSpecified, List<ProjectInSolution> projectsInOrder) 840 { 841 // Create the traversal project's root element. We will later instantiate this, and use it for evaluation of conditions on 842 // the metaprojects. 843 ProjectRootElement traversalProject = ProjectRootElement.Create(); 844 traversalProject.ToolsVersion = wrapperProjectToolsVersion; 845 traversalProject.DefaultTargets = "Build"; 846 traversalProject.InitialTargets = "ValidateSolutionConfiguration;ValidateToolsVersions;ValidateProjects"; 847 traversalProject.FullPath = _solutionFile.FullPath + ".metaproj"; 848 849 // We don't use dependency levels any more - however this will find circular dependencies and throw for us. 850 Dictionary<int, List<ProjectInSolution>> projectsByDependencyLevel = new Dictionary<int, List<ProjectInSolution>>(); 851 852 // Add default solution configuration/platform names in case the user doesn't specify them on the command line 853 AddConfigurationPlatformDefaults(traversalProject); 854 855 // Add default Venus configuration names (for more details, see comments for this method) 856 AddVenusConfigurationDefaults(traversalProject); 857 858 // Add solution related macros 859 AddGlobalProperties(traversalProject); 860 861 // Add a property group for each solution configuration, each with one XML property containing the 862 // project configurations in this solution configuration. 863 foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations) 864 { 865 AddPropertyGroupForSolutionConfiguration(traversalProject, solutionConfiguration); 866 } 867 868 // Add our global extensibility points to the project representing the solution: 869 // Imported at the top: $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore\* 870 // Imported at the bottom: $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\* 871 ProjectImportElement importBefore = traversalProject.CreateImportElement(@"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore\*"); 872 importBefore.Condition = @"'$(ImportByWildcardBeforeSolution)' != 'false' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore')"; // Avoids wildcard perf problem 873 874 ProjectImportElement importAfter = traversalProject.CreateImportElement(@"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\*"); 875 importAfter.Condition = @"'$(ImportByWildcardBeforeSolution)' != 'false' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter')"; // Avoids wildcard perf problem 876 877 // Add our local extensibility points to the project representing the solution 878 // Imported at the top: before.mysolution.sln.targets 879 // Imported at the bottom: after.mysolution.sln.targets 880 string escapedSolutionFile = EscapingUtilities.Escape(Path.GetFileName(_solutionFile.FullPath)); 881 string escapedSolutionDirectory = EscapingUtilities.Escape(_solutionFile.SolutionFileDirectory); 882 string localFile = Path.Combine(escapedSolutionDirectory, "before." + escapedSolutionFile + ".targets"); 883 ProjectImportElement importBeforeLocal = traversalProject.CreateImportElement(localFile); 884 importBeforeLocal.Condition = @"exists('" + localFile + "')"; 885 886 localFile = Path.Combine(escapedSolutionDirectory, "after." + escapedSolutionFile + ".targets"); 887 ProjectImportElement importAfterLocal = traversalProject.CreateImportElement(localFile); 888 importAfterLocal.Condition = @"exists('" + localFile + "')"; 889 890 // Put locals second so they can override globals if they want 891 traversalProject.PrependChild(importBeforeLocal); 892 traversalProject.PrependChild(importBefore); 893 traversalProject.AppendChild(importAfter); 894 traversalProject.AppendChild(importAfterLocal); 895 896 // These are just dummies necessary to make the evaluation into a project instance succeed when 897 // any custom imported targets have declarations like BeforeTargets="Build" 898 // They'll be replaced momentarily with the real ones. 899 foreach (string targetName in _defaultTargetNames) 900 { 901 traversalProject.AddTarget(targetName); 902 } 903 904 // For debugging purposes: some information is lost when evaluating into a project instance, 905 // so make it possible to see what we have at this point. 906 if (Environment.GetEnvironmentVariable("MSBUILDEMITSOLUTION") != null) 907 { 908 string path = traversalProject.FullPath; 909 traversalProject.Save(_solutionFile.FullPath + ".metaproj.tmp"); 910 traversalProject.FullPath = path; 911 } 912 913 // Create the instance. From this point forward we can evaluate conditions against the traversal project directly. 914 ProjectInstance traversalInstance = new ProjectInstance 915 ( 916 traversalProject, 917 _globalProperties, 918 explicitToolsVersionSpecified ? wrapperProjectToolsVersion : null, 919 _solutionFile.VisualStudioVersion, 920 new ProjectCollection(), 921 _sdkResolverService, 922 _submissionId 923 ); 924 925 // Make way for the real ones 926 foreach (string targetName in _defaultTargetNames) 927 { 928 traversalInstance.RemoveTarget(targetName); 929 } 930 931 AddStandardTraversalTargets(traversalInstance, projectsInOrder); 932 933 return traversalInstance; 934 } 935 936 /// <summary> 937 /// This method adds a new ProjectReference item to the specified instance. The reference will either be to its metaproject (if the project 938 /// is a web project or has reference of its own) or to the project itself (if it has no references and is a normal MSBuildable project.) 939 /// </summary> AddProjectReference(ProjectInstance traversalProject, ProjectInstance projectInstance, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration, bool direct)940 private void AddProjectReference(ProjectInstance traversalProject, ProjectInstance projectInstance, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration, bool direct) 941 { 942 ProjectItemInstance item; 943 944 if (direct) 945 { 946 // We can build this project directly, so add its reference. 947 item = projectInstance.AddItem("ProjectReference", EscapingUtilities.Escape(projectToAdd.AbsolutePath), null); 948 item.SetMetadata("ToolsVersion", GetToolsVersionMetadataForDirectMSBuildTask(traversalProject)); 949 item.SetMetadata("SkipNonexistentProjects", "False"); // Skip if it doesn't exist on disk. 950 item.SetMetadata("AdditionalProperties", GetPropertiesMetadataForProjectReference(traversalProject, GetConfigurationAndPlatformPropertiesString(projectConfiguration))); 951 } 952 else 953 { 954 // We cannot build directly, add the metaproject reference instead. 955 item = projectInstance.AddItem("ProjectReference", GetMetaprojectName(projectToAdd), null); 956 item.SetMetadata("ToolsVersion", traversalProject.ToolsVersion); 957 item.SetMetadata("SkipNonexistentProjects", "Build"); // Instruct the MSBuild task to try to build even though the file doesn't exist on disk. 958 item.SetMetadata("AdditionalProperties", GetPropertiesMetadataForProjectReference(traversalProject, SolutionConfigurationAndPlatformProperties)); 959 } 960 961 // Set raw config and platform for custom build steps to use if they wish 962 // Configuration is null for web projects 963 if (projectConfiguration != null) 964 { 965 item.SetMetadata("Configuration", projectConfiguration.ConfigurationName); 966 item.SetMetadata("Platform", projectConfiguration.PlatformName); 967 } 968 } 969 970 /// <summary> 971 /// The value to be passed to the ToolsVersion attribute of the MSBuild task used to directly build a project. 972 /// </summary> GetToolsVersionMetadataForDirectMSBuildTask(ProjectInstance traversalProject)973 private string GetToolsVersionMetadataForDirectMSBuildTask(ProjectInstance traversalProject) 974 { 975 string directProjectToolsVersion = traversalProject.GetPropertyValue("ProjectToolsVersion"); 976 return directProjectToolsVersion; 977 } 978 979 /// <summary> 980 /// The value to be passed to the ToolsVersion attribute of the MSBuild task used to directly build a project. 981 /// </summary> GetToolsVersionAttributeForDirectMSBuildTask(ProjectInstance traversalProject)982 private string GetToolsVersionAttributeForDirectMSBuildTask(ProjectInstance traversalProject) 983 { 984 return "$(ProjectToolsVersion)"; 985 } 986 987 /// <summary> 988 /// The value to be assigned to the metadata for a particular project reference. Contains only configuration and platform specified in the project configuration, evaluated. 989 /// </summary> GetPropertiesMetadataForProjectReference(ProjectInstance traversalProject, string configurationAndPlatformProperties)990 private string GetPropertiesMetadataForProjectReference(ProjectInstance traversalProject, string configurationAndPlatformProperties) 991 { 992 string directProjectProperties = traversalProject.ExpandString(configurationAndPlatformProperties); 993 994 if (traversalProject.SubToolsetVersion != null) 995 { 996 // Note: it is enough below to compare traversalProject.SubToolsetVersion with 4.0 as a means to verify if 997 // traversalProject.SubToolsetVersion < 12.0 since this path isn't followed for traversalProject.SubToolsetVersion values of 2.0 and 3.5 998 if (traversalProject.SubToolsetVersion.Equals("4.0", StringComparison.OrdinalIgnoreCase)) 999 { 1000 directProjectProperties = String.Format(CultureInfo.InvariantCulture, "{0}; {1}={2}", directProjectProperties, Constants.SubToolsetVersionPropertyName, traversalProject.SubToolsetVersion); 1001 } 1002 } 1003 1004 return directProjectProperties; 1005 } 1006 1007 /// <summary> 1008 /// Gets the project configuration and platform values as an attribute string for an MSBuild task used to build the project. 1009 /// </summary> GetConfigurationAndPlatformPropertiesString(ProjectConfigurationInSolution projectConfiguration)1010 private string GetConfigurationAndPlatformPropertiesString(ProjectConfigurationInSolution projectConfiguration) 1011 { 1012 string directProjectProperties = String.Format(CultureInfo.InvariantCulture, "Configuration={0}; Platform={1}", projectConfiguration.ConfigurationName, projectConfiguration.PlatformName); 1013 return directProjectProperties; 1014 } 1015 1016 /// <summary> 1017 /// The value to be passed to the Properties attribute of the MSBuild task to build a specific project. Contains reference to project configuration and 1018 /// platform as well as the solution configuration bits. 1019 /// </summary> GetPropertiesAttributeForDirectMSBuildTask(ProjectConfigurationInSolution projectConfiguration)1020 private string GetPropertiesAttributeForDirectMSBuildTask(ProjectConfigurationInSolution projectConfiguration) 1021 { 1022 string directProjectProperties = OpportunisticIntern.InternStringIfPossible(String.Join(";", GetConfigurationAndPlatformPropertiesString(projectConfiguration), SolutionProperties)); 1023 return directProjectProperties; 1024 } 1025 1026 /// <summary> 1027 /// Returns true if the specified project can be built directly, without using a metaproject. 1028 /// </summary> CanBuildDirectly(ProjectInstance traversalProject, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration)1029 private bool CanBuildDirectly(ProjectInstance traversalProject, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration) 1030 { 1031 // Can we build this project directly, without a metaproject? We can if it's MSBuild-able and has no references building in this configuration. 1032 bool canBuildDirectly = false; 1033 string unknownProjectTypeErrorMessage; 1034 if ((projectToAdd.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) || 1035 (projectToAdd.CanBeMSBuildProjectFile(out unknownProjectTypeErrorMessage))) 1036 { 1037 canBuildDirectly = true; 1038 foreach (string dependencyProjectGuid in projectToAdd.Dependencies) 1039 { 1040 ProjectInSolution dependencyProject; 1041 if (!_solutionFile.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out dependencyProject)) 1042 { 1043 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile 1044 ( 1045 false, 1046 "SubCategoryForSolutionParsingErrors", 1047 new BuildEventFileInfo(traversalProject.FullPath), 1048 "SolutionParseProjectDepNotFoundError", 1049 projectToAdd.ProjectGuid, 1050 dependencyProjectGuid 1051 ); 1052 } 1053 1054 if (WouldProjectBuild(_solutionFile, _selectedSolutionConfiguration, dependencyProject, projectConfiguration)) 1055 { 1056 // This is a reference we would have to build, so we can't build the project directly. 1057 canBuildDirectly = false; 1058 break; 1059 } 1060 } 1061 } 1062 1063 return canBuildDirectly; 1064 } 1065 1066 /// <summary> 1067 /// Produces a set of targets which allows the MSBuild scheduler to schedule projects in the order automatically by 1068 /// following their dependencies without enforcing build levels. 1069 /// </summary> 1070 /// <remarks> 1071 /// We want MSBuild to be able to parallelize the builds of these projects where possible and still honor references. 1072 /// Since the project files referenced by the solution do not (necessarily) themselves contain actual project references 1073 /// to the projects they depend on, we need to synthesize this relationship ourselves. This is done by creating a target 1074 /// which first invokes the project's dependencies, then invokes the actual project itself. However, invoking the 1075 /// dependencies must also invoke their dependencies and so on down the line. 1076 /// 1077 /// Additionally, we do not wish to create a separate MSBuild project to contain this target yet we want to parallelize 1078 /// calls to these targets. The way to do this is to pass in different global properties to the same project in the same 1079 /// MSBuild call. MSBuild easily allows this using the AdditionalProperties metadata which can be specified on an Item. 1080 /// 1081 /// Assuming the solution project we are generating is called "foo.proj", we can accomplish this parallelism as follows: 1082 /// <ItemGroup> 1083 /// <ProjectReference Include="Project0"/> 1084 /// <ProjectReference Include="Project1"/> 1085 /// <ProjectReference Include="Project2"/> 1086 /// </ItemGroup> 1087 /// 1088 /// We now have expressed the top level reference to all projects as @(SolutionReference) and each project's 1089 /// set of references as @(PROJECTNAMEReference). We construct our target as: 1090 /// 1091 /// <Target Name="Build"> 1092 /// <MSBuild Projects="@(ProjectReference)" Targets="Build" /> 1093 /// <MSBuild Projects="actualProjectName" Targets="Build" /> 1094 /// </Target> 1095 /// 1096 /// The first MSBuild call re-invokes the solution project instructing it to build the reference projects for the 1097 /// current project. The second MSBuild call invokes the actual project itself. Because all reference projects have 1098 /// the same additional properties, MSBuild will only build the first one it comes across and the rest will be 1099 /// satisfied from the cache. 1100 /// </remarks> CreateMetaproject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration)1101 private ProjectInstance CreateMetaproject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration) 1102 { 1103 // Create a new project instance with global properties and tools version from the existing project 1104 ProjectInstance metaprojectInstance = new ProjectInstance(EscapingUtilities.UnescapeAll(GetMetaprojectName(project)), traversalProject, GetMetaprojectGlobalProperties(traversalProject)); 1105 1106 // Add the project references which must build before this one. 1107 AddMetaprojectReferenceItems(traversalProject, metaprojectInstance, project); 1108 1109 // This string holds the error message generated when we try to determine if a project is an MSBuild format 1110 // project but it is not. 1111 string unknownProjectTypeErrorMessage; 1112 1113 if (project.ProjectType == SolutionProjectType.WebProject) 1114 { 1115 AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, null); 1116 AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Clean"); 1117 AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Rebuild"); 1118 AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Publish"); 1119 1120 foreach (string targetName in _targetNames.Where(i => !metaprojectInstance.Targets.ContainsKey(i))) 1121 { 1122 AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, targetName); 1123 } 1124 } 1125 else if ((project.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) || 1126 (project.CanBeMSBuildProjectFile(out unknownProjectTypeErrorMessage))) 1127 { 1128 string safeItemNameFromProjectName = MakeIntoSafeItemName(project.ProjectName); 1129 string targetOutputItemName = string.Format(CultureInfo.InvariantCulture, "{0}BuildOutput", safeItemNameFromProjectName); 1130 1131 AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Clean", null); 1132 AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, null, targetOutputItemName); 1133 AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Rebuild", targetOutputItemName); 1134 AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Publish", null); 1135 1136 foreach (string targetName in _targetNames.Where(i => !metaprojectInstance.Targets.ContainsKey(i))) 1137 { 1138 AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, targetName, null); 1139 } 1140 } 1141 else 1142 { 1143 AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, null, unknownProjectTypeErrorMessage); 1144 AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Clean", unknownProjectTypeErrorMessage); 1145 AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Rebuild", unknownProjectTypeErrorMessage); 1146 AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Publish", unknownProjectTypeErrorMessage); 1147 1148 foreach (string targetName in _targetNames.Where(i => !metaprojectInstance.Targets.ContainsKey(i))) 1149 { 1150 AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, targetName, unknownProjectTypeErrorMessage); 1151 } 1152 } 1153 1154 return metaprojectInstance; 1155 } 1156 1157 /// <summary> 1158 /// Returns the metaproject name for a given project. 1159 /// </summary> GetMetaprojectName(ProjectInSolution project)1160 private string GetMetaprojectName(ProjectInSolution project) 1161 { 1162 string baseName; 1163 if (project.ProjectType == SolutionProjectType.WebProject) 1164 { 1165 baseName = Path.Combine(_solutionFile.SolutionFileDirectory, MakeIntoSafeItemName(project.GetUniqueProjectName())); 1166 } 1167 else 1168 { 1169 baseName = project.AbsolutePath; 1170 } 1171 1172 if (String.IsNullOrEmpty(baseName)) 1173 { 1174 baseName = project.ProjectName; 1175 } 1176 1177 baseName = FileUtilities.EnsureNoTrailingSlash(baseName); 1178 1179 return SolutionProjectGenerator.GetMetaprojectName(baseName); 1180 } 1181 1182 /// <summary> 1183 /// Adds a set of items which describe the references for this project. 1184 /// </summary> AddMetaprojectReferenceItems(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project)1185 private void AddMetaprojectReferenceItems(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project) 1186 { 1187 foreach (string dependencyProjectGuid in project.Dependencies) 1188 { 1189 ProjectInSolution dependencyProject; 1190 if (!_solutionFile.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out dependencyProject)) 1191 { 1192 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile 1193 ( 1194 false, 1195 "SubCategoryForSolutionParsingErrors", 1196 new BuildEventFileInfo(traversalProject.FullPath), 1197 "SolutionParseProjectDepNotFoundError", 1198 project.ProjectGuid, 1199 dependencyProjectGuid 1200 ); 1201 } 1202 else 1203 { 1204 ProjectConfigurationInSolution dependencyProjectConfiguration = null; 1205 if (dependencyProject.ProjectConfigurations.TryGetValue(_selectedSolutionConfiguration, out dependencyProjectConfiguration) && 1206 WouldProjectBuild(_solutionFile, _selectedSolutionConfiguration, dependencyProject, dependencyProjectConfiguration)) 1207 { 1208 bool canBuildDirectly = CanBuildDirectly(traversalProject, dependencyProject, dependencyProjectConfiguration); 1209 AddProjectReference(traversalProject, metaprojectInstance, dependencyProject, dependencyProjectConfiguration, canBuildDirectly); 1210 } 1211 } 1212 } 1213 } 1214 1215 /// <summary> 1216 /// Adds the targets which build the dependencies and actual project for a metaproject. 1217 /// </summary> AddMetaprojectTargetForManagedProject(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetName, string outputItem)1218 private void AddMetaprojectTargetForManagedProject(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetName, string outputItem) 1219 { 1220 string outputItemAsItem = null; 1221 if (!String.IsNullOrEmpty(outputItem)) 1222 { 1223 outputItemAsItem = "@(" + outputItem + ")"; 1224 } 1225 1226 ProjectTargetInstance target = metaprojectInstance.AddTarget(targetName ?? "Build", String.Empty, String.Empty, outputItemAsItem, null, String.Empty, String.Empty, false /* legacy target returns behaviour */); 1227 1228 AddReferencesBuildTask(metaprojectInstance, target, targetName, null /* No need to capture output */); 1229 1230 // Add the task to build the actual project. 1231 AddProjectBuildTask(traversalProject, project, projectConfiguration, target, targetName, EscapingUtilities.Escape(project.AbsolutePath), String.Empty, outputItem); 1232 } 1233 1234 /// <summary> 1235 /// Adds an MSBuild task to a real project. 1236 /// </summary> AddProjectBuildTask(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, ProjectTargetInstance target, string targetToBuild, string sourceItems, string condition, string outputItem)1237 private void AddProjectBuildTask(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, ProjectTargetInstance target, string targetToBuild, string sourceItems, string condition, string outputItem) 1238 { 1239 ProjectTaskInstance task = target.AddTask("MSBuild", condition, String.Empty); 1240 task.SetParameter("Projects", sourceItems); 1241 if (targetToBuild != null) 1242 { 1243 task.SetParameter("Targets", targetToBuild); 1244 } 1245 1246 task.SetParameter("BuildInParallel", "True"); 1247 1248 task.SetParameter("ToolsVersion", GetToolsVersionAttributeForDirectMSBuildTask(traversalProject)); 1249 task.SetParameter("Properties", GetPropertiesAttributeForDirectMSBuildTask(projectConfiguration)); 1250 1251 if (outputItem != null) 1252 { 1253 task.AddOutputItem("TargetOutputs", outputItem, String.Empty); 1254 } 1255 } 1256 1257 /// <summary> 1258 /// Adds an MSBuild task to a single metaproject. This is used in the traversal project. 1259 /// </summary> AddMetaprojectBuildTask(ProjectInstance traversalProject, ProjectInSolution project, ProjectTargetInstance target, string targetToBuild, string outputItem)1260 private void AddMetaprojectBuildTask(ProjectInstance traversalProject, ProjectInSolution project, ProjectTargetInstance target, string targetToBuild, string outputItem) 1261 { 1262 ProjectTaskInstance task = target.AddTask("MSBuild", OpportunisticIntern.InternStringIfPossible("'%(ProjectReference.Identity)' == '" + GetMetaprojectName(project) + "'"), String.Empty); 1263 task.SetParameter("Projects", "@(ProjectReference)"); 1264 1265 if (targetToBuild != null) 1266 { 1267 task.SetParameter("Targets", targetToBuild); 1268 } 1269 1270 task.SetParameter("BuildInParallel", "True"); 1271 task.SetParameter("ToolsVersion", MSBuildConstants.CurrentToolsVersion); 1272 task.SetParameter("Properties", SolutionProperties); 1273 task.SetParameter("SkipNonexistentProjects", "%(ProjectReference.SkipNonexistentProjects)"); 1274 1275 if (outputItem != null) 1276 { 1277 task.AddOutputItem("TargetOutputs", outputItem, String.Empty); 1278 } 1279 } 1280 1281 /// <summary> 1282 /// Add a target for a Venus project into the XML doc that's being generated. This 1283 /// target will call the AspNetCompiler task. 1284 /// </summary> AddMetaprojectTargetForWebProject(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string targetName)1285 private void AddMetaprojectTargetForWebProject(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string targetName) 1286 { 1287 // Add a supporting target called "GetFrameworkPathAndRedistList". 1288 AddTargetForGetFrameworkPathAndRedistList(metaprojectInstance); 1289 1290 ProjectTargetInstance newTarget = metaprojectInstance.AddTarget(targetName ?? "Build", ComputeTargetConditionForWebProject(project), null, null, null, null, "GetFrameworkPathAndRedistList", false /* legacy target returns behaviour */); 1291 1292 // Build the references 1293 AddReferencesBuildTask(metaprojectInstance, newTarget, targetName, null /* No need to capture output */); 1294 1295 if (targetName == "Clean") 1296 { 1297 // Well, hmmm. The AspNetCompiler task doesn't support any kind of 1298 // a "Clean" operation. The best we can really do is offer up a 1299 // message saying so. 1300 AddErrorWarningMessageInstance(newTarget, null, XMakeElements.message, true, "SolutionVenusProjectNoClean"); 1301 } 1302 else if (targetName == "Publish") 1303 { 1304 // Well, hmmm. The AspNetCompiler task doesn't support any kind of 1305 // a "Publish" operation. The best we can really do is offer up a 1306 // message saying so. 1307 AddErrorWarningMessageInstance(newTarget, null, XMakeElements.message, true, "SolutionVenusProjectNoPublish"); 1308 } 1309 else 1310 { 1311 // For normal build and "Rebuild", just call the AspNetCompiler task with the 1312 // correct parameters. But before calling the AspNetCompiler task, we need to 1313 // do a bunch of prep work regarding references. 1314 1315 // We're going to build up an MSBuild condition string that represents the valid Configurations. 1316 // We do this by OR'ing together individual conditions, each of which compares $(Configuration) 1317 // with a valid configuration name. We init our condition string to "false", so we can easily 1318 // OR together more stuff as we go, and also easily take the negation of the condition by putting 1319 // a ! around the whole thing. 1320 StringBuilder conditionDescribingValidConfigurations = new StringBuilder("(false)"); 1321 1322 // Loop through all the valid configurations and add a PropertyGroup for each one. 1323 foreach (DictionaryEntry aspNetConfiguration in project.AspNetConfigurations) 1324 { 1325 string configurationName = (string)aspNetConfiguration.Key; 1326 AspNetCompilerParameters aspNetCompilerParameters = (AspNetCompilerParameters)aspNetConfiguration.Value; 1327 1328 // We only add the PropertyGroup once per Venus project. Without the following "if", we would add 1329 // the same identical PropertyGroup twice, once when AddTargetForWebProject is called with 1330 // subTargetName=null and once when subTargetName="Rebuild". 1331 if (targetName == null) 1332 { 1333 AddPropertyGroupForAspNetConfiguration(traversalProject, metaprojectInstance, project, configurationName, aspNetCompilerParameters, _solutionFile.FullPath); 1334 } 1335 1336 // Update our big condition string to include this configuration. 1337 conditionDescribingValidConfigurations.Append(" or "); 1338 conditionDescribingValidConfigurations.Append(String.Format(CultureInfo.InvariantCulture, "('$(AspNetConfiguration)' == '{0}')", EscapingUtilities.Escape(configurationName))); 1339 } 1340 1341 StringBuilder referenceItemName = new StringBuilder(GenerateSafePropertyName(project, "References")); 1342 if (!string.IsNullOrEmpty(targetName)) 1343 { 1344 referenceItemName.Append('_'); 1345 referenceItemName.Append(targetName); 1346 } 1347 1348 // Add tasks to resolve project references of this web project, if any 1349 if (project.ProjectReferences.Count > 0) 1350 { 1351 // This is a bit tricky. Even though web projects don't use solution configurations, 1352 // we want to use the current solution configuration to build the proper configurations 1353 // of referenced projects. 1354 foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations) 1355 { 1356 string referenceProjectGuids = null; 1357 1358 AddResolveProjectReferenceTasks 1359 ( 1360 traversalProject, 1361 newTarget, 1362 project, 1363 solutionConfiguration, 1364 referenceItemName.ToString(), 1365 null /* don't care about native references */, 1366 out referenceProjectGuids 1367 ); 1368 } 1369 } 1370 1371 // Add tasks to capture the auto-refreshed file references (those .REFRESH files). 1372 AddTasksToResolveAutoRefreshFileReferences(newTarget, project, referenceItemName.ToString()); 1373 1374 // Add a call to RAR (ResolveAssemblyReference) and the Copy task to put the referenced 1375 // project outputs in the right place 1376 AddTasksToCopyAllDependenciesIntoBinDir(newTarget, project, referenceItemName.ToString(), conditionDescribingValidConfigurations.ToString()); 1377 1378 // Add a call to the AspNetCompiler task, conditioned on having a valid Configuration. 1379 AddTaskForAspNetCompiler(newTarget, project, conditionDescribingValidConfigurations.ToString()); 1380 1381 // Add a call to the <Message> task, conditioned on having an *invalid* Configuration. The 1382 // message says that we're skipping the Venus project because it's either not enabled 1383 // for precompilation, or doesn't support the given configuration. 1384 ProjectTaskInstance skippingVenusProjectMessageTask = AddErrorWarningMessageInstance 1385 ( 1386 newTarget, 1387 "!(" + conditionDescribingValidConfigurations.ToString() + ")", 1388 XMakeElements.message, 1389 false, 1390 "SolutionVenusProjectSkipped" 1391 ); 1392 } 1393 } 1394 1395 /// <summary> 1396 /// Helper method to add a call to the AspNetCompiler task into the given target. 1397 /// </summary> AddTaskForAspNetCompiler( ProjectTargetInstance target, ProjectInSolution project, string conditionDescribingValidConfigurations )1398 private void AddTaskForAspNetCompiler 1399 ( 1400 ProjectTargetInstance target, 1401 ProjectInSolution project, 1402 string conditionDescribingValidConfigurations 1403 ) 1404 { 1405 // Add a call to the AspNetCompiler task, conditioned on having a valid Configuration. 1406 ProjectTaskInstance newTask = target.AddTask("AspNetCompiler", conditionDescribingValidConfigurations, null); 1407 newTask.SetParameter("VirtualPath", "$(" + GenerateSafePropertyName(project, "AspNetVirtualPath") + ")"); 1408 newTask.SetParameter("PhysicalPath", "$(" + GenerateSafePropertyName(project, "AspNetPhysicalPath") + ")"); 1409 newTask.SetParameter("TargetPath", "$(" + GenerateSafePropertyName(project, "AspNetTargetPath") + ")"); 1410 newTask.SetParameter("Force", "$(" + GenerateSafePropertyName(project, "AspNetForce") + ")"); 1411 newTask.SetParameter("Updateable", "$(" + GenerateSafePropertyName(project, "AspNetUpdateable") + ")"); 1412 newTask.SetParameter("Debug", "$(" + GenerateSafePropertyName(project, "AspNetDebug") + ")"); 1413 newTask.SetParameter("KeyFile", "$(" + GenerateSafePropertyName(project, "AspNetKeyFile") + ")"); 1414 newTask.SetParameter("KeyContainer", "$(" + GenerateSafePropertyName(project, "AspNetKeyContainer") + ")"); 1415 newTask.SetParameter("DelaySign", "$(" + GenerateSafePropertyName(project, "AspNetDelaySign") + ")"); 1416 newTask.SetParameter("AllowPartiallyTrustedCallers", "$(" + GenerateSafePropertyName(project, "AspNetAPTCA") + ")"); 1417 newTask.SetParameter("FixedNames", "$(" + GenerateSafePropertyName(project, "AspNetFixedNames") + ")"); 1418 1419 bool isDotNetFramework = false; 1420 1421 // generate the target .NET Framework version based on the passed in TargetFrameworkMoniker. 1422 try 1423 { 1424 FrameworkName targetFramework = new FrameworkName(project.TargetFrameworkMoniker); 1425 1426 if (String.Equals(targetFramework.Identifier, ".NETFramework", StringComparison.OrdinalIgnoreCase)) 1427 { 1428 isDotNetFramework = true; 1429 1430 // As of .NET Framework 4.0, there are only two versions of aspnet_compiler.exe: 2.0 and 4.0. If 1431 // the TargetFrameworkVersion is less than 4.0, use the 2.0 version. Otherwise, just use the 4.0 1432 // version of the executable, so that if say FV 4.1 is passed in, we don't throw an error. 1433 if (targetFramework.Version.Major >= 4) 1434 { 1435 newTask.SetParameter 1436 ( 1437 "ToolPath", 1438 FrameworkLocationHelper.GetPathToDotNetFramework(_version40) 1439 ); 1440 1441 if (targetFramework.Version > _version40) 1442 { 1443 _loggingService.LogComment(_projectBuildEventContext, MessageImportance.Low, 1444 "AspNetCompiler.TargetingHigherFrameworksDefaultsTo40", project.ProjectName, 1445 targetFramework.Version.ToString()); 1446 } 1447 } 1448 else 1449 { 1450 string pathTo20 = FrameworkLocationHelper.GetPathToDotNetFramework(_version20); 1451 1452 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(pathTo20 != null, 1453 "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(_solutionFile.FullPath), 1454 "AspNetCompiler.20NotInstalled"); 1455 1456 newTask.SetParameter 1457 ( 1458 "ToolPath", 1459 pathTo20 1460 ); 1461 } 1462 } 1463 } 1464 catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) 1465 { 1466 ProjectFileErrorUtilities.ThrowInvalidProjectFile 1467 ( 1468 new BuildEventFileInfo(_solutionFile.FullPath), 1469 e, 1470 "AspNetCompiler.InvalidTargetFrameworkMonikerFromException", 1471 project.ProjectName, 1472 project.TargetFrameworkMoniker, 1473 e.Message 1474 ); 1475 } 1476 1477 if (!isDotNetFramework) 1478 { 1479 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile 1480 ( 1481 false, 1482 "SubCategoryForSolutionParsingErrors", 1483 new BuildEventFileInfo(_solutionFile.FullPath), 1484 "AspNetCompiler.InvalidTargetFrameworkMonikerNotDotNET", 1485 project.ProjectName, 1486 project.TargetFrameworkMoniker 1487 ); 1488 } 1489 } 1490 1491 /// <summary> 1492 /// Adds MSBuild tasks to a project target to pre-resolve its project references 1493 /// </summary> AddResolveProjectReferenceTasks( ProjectInstance traversalProject, ProjectTargetInstance target, ProjectInSolution project, SolutionConfigurationInSolution solutionConfiguration, string outputReferenceItemName, string outputImportLibraryItemName, out string addedReferenceGuids )1494 private void AddResolveProjectReferenceTasks 1495 ( 1496 ProjectInstance traversalProject, 1497 ProjectTargetInstance target, 1498 ProjectInSolution project, 1499 SolutionConfigurationInSolution solutionConfiguration, 1500 string outputReferenceItemName, 1501 string outputImportLibraryItemName, 1502 out string addedReferenceGuids 1503 ) 1504 { 1505 StringBuilder referenceGuids = new StringBuilder(); 1506 1507 string message = null; 1508 1509 // Suffix for the reference item name. Since we need to attach additional (different) metadata to every 1510 // reference item, we need to have helper item lists each with only one item 1511 int outputReferenceItemNameSuffix = 0; 1512 1513 // Pre-resolve the MSBuild project references 1514 foreach (string projectReferenceGuid in project.ProjectReferences) 1515 { 1516 ProjectInSolution referencedProject = (ProjectInSolution)_solutionFile.ProjectsByGuid[projectReferenceGuid]; 1517 ProjectConfigurationInSolution referencedProjectConfiguration = null; 1518 1519 if ((referencedProject != null) && 1520 (referencedProject.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out referencedProjectConfiguration)) && 1521 (referencedProjectConfiguration != null)) 1522 { 1523 string outputReferenceItemNameWithSuffix = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", outputReferenceItemName, outputReferenceItemNameSuffix); 1524 1525 if ((referencedProject.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) || 1526 ((referencedProject.ProjectType == SolutionProjectType.Unknown) && (referencedProject.CanBeMSBuildProjectFile(out message)))) 1527 { 1528 string condition = GetConditionStringForConfiguration(solutionConfiguration); 1529 if (traversalProject.EvaluateCondition(condition)) 1530 { 1531 bool specifyProjectToolsVersion = 1532 String.Equals(traversalProject.ToolsVersion, "2.0", StringComparison.OrdinalIgnoreCase) ? false : true; 1533 1534 ProjectTaskInstance msbuildTask = AddMSBuildTaskInstance 1535 ( 1536 target, 1537 referencedProject.RelativePath, 1538 "GetTargetPath", 1539 referencedProjectConfiguration.ConfigurationName, 1540 referencedProjectConfiguration.PlatformName, 1541 specifyProjectToolsVersion 1542 ); 1543 msbuildTask.AddOutputItem("TargetOutputs", outputReferenceItemNameWithSuffix, null); 1544 } 1545 1546 if (referenceGuids.Length > 0) 1547 { 1548 referenceGuids.Append(';'); 1549 } 1550 1551 referenceGuids.Append(projectReferenceGuid); 1552 1553 // This merges the one-item item list into the main list, adding the appropriate guid metadata 1554 ProjectTaskInstance createItemTask = target.AddTask("CreateItem", null, null); 1555 createItemTask.SetParameter("Include", "@(" + outputReferenceItemNameWithSuffix + ")"); 1556 createItemTask.SetParameter("AdditionalMetadata", "Guid=" + projectReferenceGuid); 1557 createItemTask.AddOutputItem("Include", outputReferenceItemName, null); 1558 } 1559 1560 outputReferenceItemNameSuffix++; 1561 } 1562 } 1563 1564 addedReferenceGuids = referenceGuids.ToString(); 1565 } 1566 1567 /// <summary> 1568 /// Add a PropertyGroup to the project for a particular Asp.Net configuration. This PropertyGroup 1569 /// will have the correct values for all the Asp.Net properties for this project and this configuration. 1570 /// </summary> AddPropertyGroupForAspNetConfiguration( ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string configurationName, AspNetCompilerParameters aspNetCompilerParameters, string solutionFile )1571 private void AddPropertyGroupForAspNetConfiguration 1572 ( 1573 ProjectInstance traversalProject, 1574 ProjectInstance metaprojectInstance, 1575 ProjectInSolution project, 1576 string configurationName, 1577 AspNetCompilerParameters aspNetCompilerParameters, 1578 string solutionFile 1579 ) 1580 { 1581 // If the configuration doesn't match, don't add the properties. 1582 if (!traversalProject.EvaluateCondition(String.Format(CultureInfo.InvariantCulture, " '$(AspNetConfiguration)' == '{0}' ", EscapingUtilities.Escape(configurationName)))) 1583 { 1584 return; 1585 } 1586 1587 // Add properties into the property group for each of the AspNetCompiler properties. 1588 // REVIEW: SetProperty takes an evaluated value. Are we doing the right thing here? 1589 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetVirtualPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetVirtualPath)); 1590 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetPhysicalPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetPhysicalPath)); 1591 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetTargetPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetTargetPath)); 1592 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetForce"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetForce)); 1593 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetUpdateable"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetUpdateable)); 1594 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetDebug"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetDebug)); 1595 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetKeyFile"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetKeyFile)); 1596 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetKeyContainer"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetKeyContainer)); 1597 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetDelaySign"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetDelaySign)); 1598 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetAPTCA"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetAPTCA)); 1599 metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetFixedNames"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetFixedNames)); 1600 1601 string aspNetPhysicalPath = aspNetCompilerParameters.aspNetPhysicalPath; 1602 if (!String.IsNullOrEmpty(aspNetPhysicalPath)) 1603 { 1604 // Trim the trailing slash if one exists. 1605 if ( 1606 (aspNetPhysicalPath[aspNetPhysicalPath.Length - 1] == Path.AltDirectorySeparatorChar) || 1607 (aspNetPhysicalPath[aspNetPhysicalPath.Length - 1] == Path.DirectorySeparatorChar) 1608 ) 1609 { 1610 aspNetPhysicalPath = aspNetPhysicalPath.Substring(0, aspNetPhysicalPath.Length - 1); 1611 } 1612 1613 // This gets us the last folder in the physical path. 1614 string lastFolderInPhysicalPath = null; 1615 1616 try 1617 { 1618 lastFolderInPhysicalPath = Path.GetFileName(aspNetPhysicalPath); 1619 } 1620 catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) 1621 { 1622 ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile 1623 ( 1624 false, 1625 "SubCategoryForSolutionParsingErrors", 1626 new BuildEventFileInfo(solutionFile), 1627 e, 1628 "SolutionParseInvalidProjectFileName", 1629 project.RelativePath, 1630 e.Message 1631 ); 1632 } 1633 1634 if (!String.IsNullOrEmpty(lastFolderInPhysicalPath)) 1635 { 1636 // If there is a global property called "OutDir" set, that means the caller is trying to 1637 // override the AspNetTargetPath. What we want to do in this case is concatenate: 1638 // $(OutDir) + "\_PublishedWebsites" + (the last portion of the folder in the AspNetPhysicalPath). 1639 if (traversalProject.EvaluateCondition(" '$(OutDir)' != '' ")) 1640 { 1641 string outDirValue = String.Empty; 1642 ProjectPropertyInstance outdir = metaprojectInstance.GetProperty("OutDir"); 1643 1644 if (outdir != null) 1645 { 1646 outDirValue = ProjectInstance.GetPropertyValueEscaped(outdir); 1647 } 1648 1649 // Make sure the path we are appending to has no leading slash to prevent double slashes. 1650 string publishWebsitePath = EscapingUtilities.Escape(WebProjectOverrideFolder) + Path.DirectorySeparatorChar + EscapingUtilities.Escape(lastFolderInPhysicalPath) + Path.DirectorySeparatorChar; 1651 1652 metaprojectInstance.SetProperty 1653 ( 1654 GenerateSafePropertyName(project, "AspNetTargetPath"), 1655 outDirValue + publishWebsitePath 1656 ); 1657 } 1658 } 1659 } 1660 } 1661 1662 /// <summary> 1663 /// When adding a target to build a web project, we want to put a Condition on the Target node that 1664 /// effectively says "Only build this target if the web project is active (marked for building) in the 1665 /// current solution configuration. 1666 /// </summary> ComputeTargetConditionForWebProject(ProjectInSolution project)1667 private string ComputeTargetConditionForWebProject(ProjectInSolution project) 1668 { 1669 StringBuilder condition = new StringBuilder(" ('$(CurrentSolutionConfigurationContents)' != '') and (false"); 1670 1671 // Loop through all the solution configurations. 1672 foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations) 1673 { 1674 // Find out if the web project has a project configuration for this solution configuration. 1675 // (Actually, web projects only have one project configuration, so the TryGetValue should 1676 // pretty much always return "true". 1677 ProjectConfigurationInSolution projectConfiguration = null; 1678 if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out projectConfiguration)) 1679 { 1680 // See if the web project is marked as active for this solution configuration. If so, 1681 // we'll build the target. Otherwise not. 1682 if (projectConfiguration.IncludeInBuild) 1683 { 1684 condition.Append(" or ("); 1685 condition.Append(GetConditionStringForConfiguration(solutionConfiguration)); 1686 condition.Append(")"); 1687 } 1688 } 1689 else if (String.Compare(solutionConfiguration.ConfigurationName, "Release", StringComparison.OrdinalIgnoreCase) == 0 || 1690 String.Compare(solutionConfiguration.ConfigurationName, "Debug", StringComparison.OrdinalIgnoreCase) == 0) 1691 { 1692 // we don't have a project configuration that matches the solution configuration but 1693 // the solution configuration is called "Release" or "Debug" which are standard AspNetConfigurations 1694 // so these should be available in the solution project 1695 condition.Append(" or ("); 1696 condition.Append(GetConditionStringForConfiguration(solutionConfiguration)); 1697 condition.Append(")"); 1698 } 1699 } 1700 1701 condition.Append(") "); 1702 return condition.ToString(); 1703 } 1704 1705 /// <summary> 1706 /// Add a target to the project called "GetFrameworkPathAndRedistList". This target calls the 1707 /// GetFrameworkPath task and then CreateItem to populate @(_CombinedTargetFrameworkDirectoriesItem) and 1708 /// @(InstalledAssemblyTables), so that we can pass these into the ResolveAssemblyReference task 1709 /// when building web projects. 1710 /// </summary> AddTargetForGetFrameworkPathAndRedistList(ProjectInstance metaprojectInstance)1711 private void AddTargetForGetFrameworkPathAndRedistList(ProjectInstance metaprojectInstance) 1712 { 1713 if (metaprojectInstance.Targets.ContainsKey("GetFrameworkPathAndRedistList")) 1714 { 1715 return; 1716 } 1717 1718 ProjectTargetInstance frameworkPathAndRedistListTarget = metaprojectInstance.AddTarget("GetFrameworkPathAndRedistList", String.Empty, null, null, null, null, null, false /* legacy target returns behaviour */); 1719 1720 ProjectTaskInstance getFrameworkPathTask = frameworkPathAndRedistListTarget.AddTask("GetFrameworkPath", String.Empty, null); 1721 1722 // Follow the same logic we use in Microsoft.Common.targets to choose the target framework 1723 // directories (which are then used to find the set of redist lists). 1724 getFrameworkPathTask.AddOutputItem( 1725 "Path", 1726 "_CombinedTargetFrameworkDirectoriesItem", 1727 "'$(MSBuildToolsVersion)' == '2.0'"); 1728 1729 // TFV v4.0 supported by TV 4.0+ 1730 getFrameworkPathTask.AddOutputItem( 1731 "FrameworkVersion40Path", 1732 "_CombinedTargetFrameworkDirectoriesItem", 1733 " '$(TargetFrameworkVersion)' == 'v4.0' and '$(MSBuildToolsVersion)' != '2.0' and '$(MSBuildToolsVersion)' != '3.5'"); 1734 1735 // TFV v3.5 supported by TV 3.5+ 1736 getFrameworkPathTask.AddOutputItem( 1737 "FrameworkVersion35Path", 1738 "_CombinedTargetFrameworkDirectoriesItem", 1739 " ('$(TargetFrameworkVersion)' == 'v3.5' or '$(TargetFrameworkVersion)' == 'v4.0') and '$(MSBuildToolsVersion)' != '2.0'"); 1740 1741 // TFV v3.0 supported by TV 3.5+ (there was no TV 3.0) 1742 getFrameworkPathTask.AddOutputItem( 1743 "FrameworkVersion30Path", 1744 "_CombinedTargetFrameworkDirectoriesItem", 1745 " ('$(TargetFrameworkVersion)' == 'v3.0' or '$(TargetFrameworkVersion)' == 'v3.5' or '$(TargetFrameworkVersion)' == 'v4.0') and '$(MSBuildToolsVersion)' != '2.0'"); 1746 1747 // TFV v2.0 supported by TV 3.5+ (there was no TV 3.0). This property was not added until toolsversion 3.5 therefore it cannot be used for toolsversion 2.0 1748 getFrameworkPathTask.AddOutputItem( 1749 "FrameworkVersion20Path", 1750 "_CombinedTargetFrameworkDirectoriesItem", 1751 "'$(MSBuildToolsVersion)' != '2.0'"); 1752 1753 ProjectTaskInstance createItemTask = frameworkPathAndRedistListTarget.AddTask("CreateItem", null, null); 1754 createItemTask.SetParameter("Include", @"@(_CombinedTargetFrameworkDirectoriesItem->'%(Identity)\RedistList\*.xml')"); 1755 createItemTask.AddOutputItem("Include", "InstalledAssemblyTables", null); 1756 } 1757 1758 /// <summary> 1759 /// Adds a target for a project whose type is unknown and we cannot build. We will emit an error or warning as appropriate. 1760 /// </summary> AddMetaprojectTargetForUnknownProjectType(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string targetName, string unknownProjectTypeErrorMessage)1761 private void AddMetaprojectTargetForUnknownProjectType(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string targetName, string unknownProjectTypeErrorMessage) 1762 { 1763 ProjectTargetInstance newTarget = metaprojectInstance.AddTarget(targetName ?? "Build", "'$(CurrentSolutionConfigurationContents)' != ''", null, null, null, null, null, false /* legacy target returns behaviour */); 1764 1765 foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations) 1766 { 1767 ProjectConfigurationInSolution projectConfiguration = null; 1768 ProjectTaskInstance newTask = null; 1769 1770 if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out projectConfiguration)) 1771 { 1772 if (projectConfiguration.IncludeInBuild) 1773 { 1774 // Only add the task if it would run in this configuration. 1775 if (!traversalProject.EvaluateCondition(GetConditionStringForConfiguration(solutionConfiguration))) 1776 { 1777 continue; 1778 } 1779 1780 if (unknownProjectTypeErrorMessage == null) 1781 { 1782 // we haven't encountered any problems accessing the project file in the past, but do not support 1783 // building this project type 1784 newTask = AddErrorWarningMessageInstance 1785 ( 1786 newTarget, 1787 null, 1788 XMakeElements.warning, 1789 true, 1790 "SolutionParseUnknownProjectType", 1791 project.RelativePath 1792 ); 1793 } 1794 else 1795 { 1796 // this project file may be of supported type, but we have encountered problems accessing it 1797 newTask = AddErrorWarningMessageInstance 1798 ( 1799 newTarget, 1800 null, 1801 XMakeElements.warning, 1802 true, 1803 "SolutionParseErrorReadingProject", 1804 project.RelativePath, 1805 unknownProjectTypeErrorMessage 1806 ); 1807 } 1808 } 1809 else 1810 { 1811 newTask = AddErrorWarningMessageInstance 1812 ( 1813 newTarget, 1814 null, 1815 XMakeElements.message, 1816 true, 1817 "SolutionProjectSkippedForBuilding", 1818 project.ProjectName, 1819 solutionConfiguration.FullName 1820 ); 1821 } 1822 } 1823 else 1824 { 1825 newTask = AddErrorWarningMessageInstance 1826 ( 1827 newTarget, 1828 null, 1829 XMakeElements.warning, 1830 true, 1831 "SolutionProjectConfigurationMissing", 1832 project.ProjectName, 1833 solutionConfiguration.FullName 1834 ); 1835 } 1836 } 1837 } 1838 1839 /// <summary> 1840 /// Adds a target which verifies that all of the project references and configurations are valid. 1841 /// </summary> AddValidateProjectsTarget(ProjectInstance traversalProject, List<ProjectInSolution> projects)1842 private void AddValidateProjectsTarget(ProjectInstance traversalProject, List<ProjectInSolution> projects) 1843 { 1844 ProjectTargetInstance newTarget = traversalProject.AddTarget("ValidateProjects", null, null, null, null, null, null, false /* legacy target returns behaviour */); 1845 1846 foreach (ProjectInSolution project in projects) 1847 { 1848 foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations) 1849 { 1850 ProjectConfigurationInSolution projectConfiguration = null; 1851 string condition = GetConditionStringForConfiguration(solutionConfiguration); 1852 1853 if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out projectConfiguration)) 1854 { 1855 if (!projectConfiguration.IncludeInBuild) 1856 { 1857 ProjectTaskInstance messageTask = AddErrorWarningMessageInstance 1858 ( 1859 newTarget, 1860 condition, 1861 XMakeElements.message, 1862 true, 1863 "SolutionProjectSkippedForBuilding", 1864 project.ProjectName, 1865 solutionConfiguration.FullName 1866 ); 1867 } 1868 } 1869 else 1870 { 1871 ProjectTaskInstance warningTask = AddErrorWarningMessageInstance 1872 ( 1873 newTarget, 1874 condition, 1875 XMakeElements.warning, 1876 true, 1877 "SolutionProjectConfigurationMissing", 1878 project.ProjectName, 1879 solutionConfiguration.FullName 1880 ); 1881 } 1882 } 1883 } 1884 } 1885 1886 ///<summary> 1887 /// Creates the target used to build all of the references in the traversal project. 1888 ///</summary> AddTraversalReferencesTarget(ProjectInstance traversalProject, string targetName, string outputItem)1889 private void AddTraversalReferencesTarget(ProjectInstance traversalProject, string targetName, string outputItem) 1890 { 1891 string outputItemAsItem = null; 1892 if (!String.IsNullOrEmpty(outputItem)) 1893 { 1894 outputItemAsItem = "@(" + outputItem + ")"; 1895 } 1896 1897 ProjectTargetInstance target = traversalProject.AddTarget(targetName ?? "Build", String.Empty, String.Empty, outputItemAsItem, null, String.Empty, String.Empty, false /* legacy target returns behaviour */); 1898 AddReferencesBuildTask(traversalProject, target, targetName, outputItem); 1899 } 1900 1901 /// <summary> 1902 /// Adds a task which builds the @(ProjectReference) items. 1903 /// </summary> AddReferencesBuildTask(ProjectInstance projectInstance, ProjectTargetInstance target, string targetToBuild, string outputItem)1904 private void AddReferencesBuildTask(ProjectInstance projectInstance, ProjectTargetInstance target, string targetToBuild, string outputItem) 1905 { 1906 ProjectTaskInstance task = target.AddTask("MSBuild", String.Empty, String.Empty); 1907 if (String.Equals(targetToBuild, "Clean", StringComparison.OrdinalIgnoreCase)) 1908 { 1909 task.SetParameter("Projects", "@(ProjectReference->Reverse())"); 1910 } 1911 else 1912 { 1913 task.SetParameter("Projects", "@(ProjectReference)"); // The references already have the tools versions and properties set on them. 1914 } 1915 1916 if (targetToBuild != null) 1917 { 1918 task.SetParameter("Targets", targetToBuild); 1919 } 1920 1921 task.SetParameter("BuildInParallel", "True"); 1922 task.SetParameter("Properties", SolutionProperties); 1923 1924 // We only want to build "nonexistent" projects if we're building metaprojects, since they don't exist on disk. Otherwise, 1925 // we still want to error when the referenced project doesn't exist. 1926 task.SetParameter("SkipNonexistentProjects", "%(ProjectReference.SkipNonexistentProjects)"); 1927 1928 if (outputItem != null) 1929 { 1930 task.AddOutputItem("TargetOutputs", outputItem, String.Empty); 1931 } 1932 } 1933 1934 /// <summary> 1935 /// Adds a traversal target which invokes a specified target on a single project. This creates targets called "Project", "Project:Rebuild", "Project:Clean", "Project:Publish" etc. 1936 /// </summary> AddTraversalTargetForProject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetToBuild, string outputItem, bool canBuildDirectly)1937 private void AddTraversalTargetForProject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetToBuild, string outputItem, bool canBuildDirectly) 1938 { 1939 string baseProjectName = ProjectInSolution.DisambiguateProjectTargetName(project.GetUniqueProjectName()); 1940 string actualTargetName = baseProjectName; 1941 if (targetToBuild != null) 1942 { 1943 actualTargetName += ":" + targetToBuild; 1944 } 1945 1946 // Don't add the target again. The user might have specified /t:Project:target which was already added but only this method knows about Project:Target so 1947 // after coming up with that target name, it can check if it has already been added. 1948 if (traversalProject.Targets.ContainsKey(actualTargetName)) 1949 { 1950 return; 1951 } 1952 1953 // The output item name is the concatenation of the project name with the specified outputItem. In the typical case, if the 1954 // project name is MyProject, the outputItemName will be MyProjectBuildOutput, and the outputItemAsItem will be @(MyProjectBuildOutput). 1955 // In the case where the project contains characters not allowed as Xml element attribute values, those characters will 1956 // be replaced with underscores. In the case where MyProject is actually unrepresentable in Xml, then the 1957 // outputItemName would be _________BuildOutput. 1958 string outputItemName = null; 1959 string outputItemAsItem = null; 1960 if (!String.IsNullOrEmpty(outputItem)) 1961 { 1962 outputItemName = MakeIntoSafeItemName(baseProjectName) + outputItem; 1963 outputItemAsItem = "@(" + outputItemName + ")"; 1964 } 1965 1966 ProjectTargetInstance targetElement = traversalProject.AddTarget(actualTargetName, null, null, outputItemAsItem, null, null, null, false /* legacy target returns behaviour */); 1967 if (canBuildDirectly) 1968 { 1969 AddProjectBuildTask(traversalProject, project, projectConfiguration, targetElement, targetToBuild, "@(ProjectReference)", "'%(ProjectReference.Identity)' == '" + EscapingUtilities.Escape(project.AbsolutePath) + "'", outputItemName); 1970 } 1971 else 1972 { 1973 AddMetaprojectBuildTask(traversalProject, project, targetElement, targetToBuild, outputItemName); 1974 } 1975 } 1976 1977 /// <summary> 1978 /// Retrieves a dictionary representing the global properties which should be transferred to a metaproject. 1979 /// </summary> 1980 /// <param name="traversalProject">The traversal from which the global properties should be obtained.</param> 1981 /// <returns>A dictionary of global properties.</returns> GetMetaprojectGlobalProperties(ProjectInstance traversalProject)1982 private IDictionary<string, string> GetMetaprojectGlobalProperties(ProjectInstance traversalProject) 1983 { 1984 Dictionary<string, string> properties = new Dictionary<string, string>(_metaprojectGlobalProperties.Length, StringComparer.OrdinalIgnoreCase); 1985 foreach (Tuple<string, string> property in _metaprojectGlobalProperties) 1986 { 1987 if (property.Item2 == null) 1988 { 1989 properties[property.Item1] = EscapingUtilities.Escape(traversalProject.GetPropertyValue(property.Item1)); 1990 } 1991 else 1992 { 1993 properties[property.Item1] = EscapingUtilities.Escape(property.Item2); 1994 } 1995 } 1996 1997 // Now provide any which are explicitly set on the solution 1998 foreach (ProjectPropertyInstance globalProperty in traversalProject.GlobalPropertiesDictionary) 1999 { 2000 properties[globalProperty.Name] = ((IProperty)globalProperty).EvaluatedValueEscaped; 2001 } 2002 2003 // If we have a sub-toolset version, it will be set on the P2P from the solution metaproj, so we need 2004 // to make sure it's set here, too, so the global properties will match. 2005 if (traversalProject.SubToolsetVersion != null) 2006 { 2007 if (traversalProject.SubToolsetVersion.Equals("4.0", StringComparison.OrdinalIgnoreCase)) 2008 { 2009 properties[Constants.SubToolsetVersionPropertyName] = traversalProject.SubToolsetVersion; 2010 } 2011 } 2012 2013 return properties; 2014 } 2015 2016 /// <summary> 2017 /// Figures out what the ToolsVersion should be for child projects (used when scanning 2018 /// for dependencies) 2019 /// </summary> DetermineChildProjectToolsVersion(string wrapperProjectToolsVersion)2020 private string DetermineChildProjectToolsVersion(string wrapperProjectToolsVersion) 2021 { 2022 string childProjectToolsVersion = null; 2023 2024 _globalProperties.TryGetValue("ProjectToolsVersion", out childProjectToolsVersion); 2025 2026 if (childProjectToolsVersion == null) 2027 { 2028 childProjectToolsVersion = wrapperProjectToolsVersion; 2029 } 2030 2031 return childProjectToolsVersion; 2032 } 2033 2034 /// <summary> 2035 /// Normally the active solution configuration/platform is determined when we build the solution 2036 /// wrapper project, not when we create it. However, we need to know them to scan project references 2037 /// for the right project configuration/platform. It's unlikely that references would be conditional, 2038 /// but still possible and we want to get that case right. 2039 /// </summary> PredictActiveSolutionConfigurationName()2040 private string PredictActiveSolutionConfigurationName() 2041 { 2042 return PredictActiveSolutionConfigurationName(_solutionFile, _globalProperties); 2043 } 2044 2045 /// <summary> 2046 /// Loads each MSBuild project in this solution and looks for its project-to-project references so that 2047 /// we know what build order we should use when building the solution. 2048 /// </summary> ScanProjectDependencies(string childProjectToolsVersion, string fullSolutionConfigurationName)2049 private void ScanProjectDependencies(string childProjectToolsVersion, string fullSolutionConfigurationName) 2050 { 2051 // Don't bother with all this if the solution configuration doesn't even exist. 2052 if (fullSolutionConfigurationName == null) 2053 { 2054 return; 2055 } 2056 2057 foreach (ProjectInSolution project in _solutionFile.ProjectsInOrder) 2058 { 2059 // We only need to scan .wdproj projects: Everything else is either MSBuildFormat or 2060 // something we don't know how to do anything with anyway 2061 if (project.ProjectType == SolutionProjectType.WebDeploymentProject) 2062 { 2063 // Skip the project if we don't have its configuration in this solution configuration 2064 if (!project.ProjectConfigurations.ContainsKey(fullSolutionConfigurationName)) 2065 { 2066 continue; 2067 } 2068 2069 try 2070 { 2071 Project msbuildProject = new Project(project.AbsolutePath, _globalProperties, childProjectToolsVersion); 2072 2073 // ProjectDependency items work exactly like ProjectReference items from the point of 2074 // view of determining that project B depends on project A. This item must cause 2075 // project A to be built prior to project B. 2076 // 2077 // This has the format 2078 // <ProjectDependency Include="DependentProjectRelativePath"> 2079 // <Project>{GUID}</Project> 2080 // </Project> 2081 IEnumerable<ProjectItem> references = msbuildProject.GetItems("ProjectDependency"); 2082 2083 foreach (ProjectItem reference in references) 2084 { 2085 string referencedProjectGuid = reference.GetMetadataValue("Project"); 2086 AddDependencyByGuid(project, referencedProjectGuid); 2087 } 2088 2089 // If this is a web deployment project, we have a reference specified as a property 2090 // "SourceWebProject" rather than as a ProjectReference item. This has the format 2091 // {GUID}|PATH_TO_CSPROJ 2092 // where 2093 // GUID is the project guid for the "source" project 2094 // PATH_TO_CSPROJ is the solution-relative path to the csproj file. 2095 // 2096 // NOTE: This is obsolete and is intended only for backward compatability with 2097 // Whidbey-generated web deployment projects. New projects should use the 2098 // ProjectDependency item above. 2099 string referencedWebProjectGuid = msbuildProject.GetPropertyValue("SourceWebProject"); 2100 if (!string.IsNullOrEmpty(referencedWebProjectGuid)) 2101 { 2102 // Grab the guid with its curly braces... 2103 referencedWebProjectGuid = referencedWebProjectGuid.Substring(0, 38); 2104 AddDependencyByGuid(project, referencedWebProjectGuid); 2105 } 2106 } 2107 catch (Exception e) 2108 { 2109 // We don't want any problems scanning the project file to result in aborting the build. 2110 if (ExceptionHandling.IsCriticalException(e)) 2111 { 2112 throw; 2113 } 2114 2115 _loggingService.LogWarning 2116 ( 2117 _projectBuildEventContext, 2118 "SubCategoryForSolutionParsingErrors", 2119 new BuildEventFileInfo(project.RelativePath), 2120 "SolutionScanProjectDependenciesFailed", 2121 project.RelativePath, 2122 e.Message 2123 ); 2124 } 2125 } 2126 } 2127 } 2128 2129 /// <summary> 2130 /// Adds a dependency to the project based on the specified guid string. 2131 /// </summary> 2132 /// <remarks> 2133 /// If the string is null or empty, no dependency is added and this is not considered an error. 2134 /// </remarks> AddDependencyByGuid(ProjectInSolution project, string dependencyGuid)2135 private void AddDependencyByGuid(ProjectInSolution project, string dependencyGuid) 2136 { 2137 if (!String.IsNullOrEmpty(dependencyGuid)) 2138 { 2139 if (_solutionFile.ProjectsByGuid.ContainsKey(dependencyGuid)) 2140 { 2141 project.AddDependency(dependencyGuid); 2142 } 2143 else 2144 { 2145 _loggingService.LogWarning 2146 ( 2147 _projectBuildEventContext, 2148 "SubCategoryForSolutionParsingErrors", 2149 new BuildEventFileInfo(_solutionFile.FullPath), 2150 "SolutionParseProjectDepNotFoundError", 2151 project.ProjectGuid, 2152 dependencyGuid 2153 ); 2154 } 2155 } 2156 } 2157 2158 /// <summary> 2159 /// Creates default Configuration and Platform values based on solution configurations present in the solution 2160 /// </summary> AddConfigurationPlatformDefaults(ProjectRootElement traversalProject)2161 private void AddConfigurationPlatformDefaults(ProjectRootElement traversalProject) 2162 { 2163 ProjectPropertyGroupElement configurationDefaultingPropertyGroup = traversalProject.CreatePropertyGroupElement(); 2164 traversalProject.AppendChild(configurationDefaultingPropertyGroup); 2165 2166 configurationDefaultingPropertyGroup.Condition = " '$(Configuration)' == '' "; 2167 configurationDefaultingPropertyGroup.AddProperty("Configuration", EscapingUtilities.Escape(_solutionFile.GetDefaultConfigurationName())); 2168 2169 ProjectPropertyGroupElement platformDefaultingPropertyGroup = traversalProject.CreatePropertyGroupElement(); 2170 traversalProject.AppendChild(platformDefaultingPropertyGroup); 2171 2172 platformDefaultingPropertyGroup.Condition = " '$(Platform)' == '' "; 2173 platformDefaultingPropertyGroup.AddProperty("Platform", EscapingUtilities.Escape(_solutionFile.GetDefaultPlatformName())); 2174 } 2175 2176 /// <summary> 2177 /// Adds a new property group with contents of the given solution configuration to the project. 2178 /// </summary> AddPropertyGroupForSolutionConfiguration(ProjectRootElement traversalProject, SolutionConfigurationInSolution solutionConfiguration)2179 private void AddPropertyGroupForSolutionConfiguration(ProjectRootElement traversalProject, SolutionConfigurationInSolution solutionConfiguration) 2180 { 2181 AddPropertyGroupForSolutionConfiguration(traversalProject, _solutionFile, solutionConfiguration); 2182 } 2183 2184 /// <summary> 2185 /// Creates the default Venus configuration property based on the selected solution configuration. 2186 /// Unfortunately, Venus projects only expose one project configuration in the IDE (Debug) although 2187 /// they allow building Debug and Release from command line. This means that if we wanted to use 2188 /// the project configuration from the active solution configuration for Venus projects, we'd always 2189 /// end up with Debug and there'd be no way to build the Release configuration. To work around this, 2190 /// we use a special mechanism for choosing ASP.NET project configuration: we set it to Release if 2191 /// we're building a Release solution configuration, and to Debug if we're building a Debug solution 2192 /// configuration. The property is also settable from the command line, in which case it takes 2193 /// precedence over this algorithm. 2194 /// </summary> AddVenusConfigurationDefaults(ProjectRootElement traversalProject)2195 private void AddVenusConfigurationDefaults(ProjectRootElement traversalProject) 2196 { 2197 ProjectPropertyGroupElement venusConfiguration = traversalProject.CreatePropertyGroupElement(); 2198 traversalProject.AppendChild(venusConfiguration); 2199 2200 venusConfiguration.Condition = " ('$(AspNetConfiguration)' == '') "; 2201 venusConfiguration.AddProperty("AspNetConfiguration", "$(Configuration)"); 2202 } 2203 2204 /// <summary> 2205 /// Adds solution related build event macros and other global properties to the wrapper project 2206 /// </summary> AddGlobalProperties(ProjectRootElement traversalProject)2207 private void AddGlobalProperties(ProjectRootElement traversalProject) 2208 { 2209 ProjectPropertyGroupElement globalProperties = traversalProject.CreatePropertyGroupElement(); 2210 traversalProject.AppendChild(globalProperties); 2211 2212 string directoryName = _solutionFile.SolutionFileDirectory; 2213 if (!directoryName.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) 2214 { 2215 directoryName += Path.DirectorySeparatorChar; 2216 } 2217 2218 globalProperties.AddProperty("SolutionDir", EscapingUtilities.Escape(directoryName)); 2219 globalProperties.AddProperty("SolutionExt", EscapingUtilities.Escape(Path.GetExtension(_solutionFile.FullPath))); 2220 globalProperties.AddProperty("SolutionFileName", EscapingUtilities.Escape(Path.GetFileName(_solutionFile.FullPath))); 2221 globalProperties.AddProperty("SolutionName", EscapingUtilities.Escape(Path.GetFileNameWithoutExtension(_solutionFile.FullPath))); 2222 2223 globalProperties.AddProperty(SolutionPathPropertyName, EscapingUtilities.Escape(Path.Combine(_solutionFile.SolutionFileDirectory, Path.GetFileName(_solutionFile.FullPath)))); 2224 2225 // Add other global properties 2226 ProjectPropertyGroupElement frameworkVersionProperties = traversalProject.CreatePropertyGroupElement(); 2227 traversalProject.AppendChild(frameworkVersionProperties); 2228 2229 // Set the property "TargetFrameworkVersion". This is needed for the GetFrameworkPath target. 2230 // If TargetFrameworkVersion is already set by the user, use that value. 2231 // Otherwise if MSBuildToolsVersion is 2.0, use "v2.0" 2232 // Otherwise if MSBuildToolsVersion is 3.5, use "v3.5" 2233 // Otherwise use "v4.0". 2234 ProjectPropertyElement tfv20Property = frameworkVersionProperties.AddProperty("TargetFrameworkVersion", "v2.0"); 2235 ProjectPropertyElement tfv35Property = frameworkVersionProperties.AddProperty("TargetFrameworkVersion", "v3.5"); 2236 ProjectPropertyElement tfv40Property = frameworkVersionProperties.AddProperty("TargetFrameworkVersion", "v4.0"); 2237 tfv20Property.Condition = "'$(TargetFrameworkVersion)' == '' and '$(MSBuildToolsVersion)' == '2.0'"; 2238 tfv35Property.Condition = "'$(TargetFrameworkVersion)' == '' and ('$(MSBuildToolsVersion)' == '3.5' or '$(MSBuildToolsVersion)' == '3.0')"; 2239 tfv40Property.Condition = "'$(TargetFrameworkVersion)' == '' and !('$(MSBuildToolsVersion)' == '3.5' or '$(MSBuildToolsVersion)' == '3.0' or '$(MSBuildToolsVersion)' == '2.0')"; 2240 } 2241 2242 /// <summary> 2243 /// Special hack for web projects. It can happen that there is no Release configuration for solutions 2244 /// containing web projects, yet we still want to be able to build the Release configuration for 2245 /// those projects. Since the ASP.NET project configuration defaults to the solution configuration, 2246 /// we allow Release even if it doesn't actually exist in the solution. 2247 /// </summary> AddFakeReleaseSolutionConfigurationIfNecessary()2248 private void AddFakeReleaseSolutionConfigurationIfNecessary() 2249 { 2250 if (_solutionFile.ContainsWebProjects) 2251 { 2252 bool solutionHasReleaseConfiguration = false; 2253 foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations) 2254 { 2255 if (string.Compare(solutionConfiguration.ConfigurationName, "Release", StringComparison.OrdinalIgnoreCase) == 0) 2256 { 2257 solutionHasReleaseConfiguration = true; 2258 break; 2259 } 2260 } 2261 2262 if ((!solutionHasReleaseConfiguration) && (_solutionFile.SolutionConfigurations.Count > 0)) 2263 { 2264 _solutionFile.AddSolutionConfiguration("Release", _solutionFile.GetDefaultPlatformName()); 2265 } 2266 } 2267 } 2268 2269 /// <summary> 2270 /// Adds the initial target to the solution wrapper project, necessary for a few message/error tags 2271 /// </summary> AddInitialTargets(ProjectInstance traversalProject, List<ProjectInSolution> projects)2272 private void AddInitialTargets(ProjectInstance traversalProject, List<ProjectInSolution> projects) 2273 { 2274 AddValidateSolutionConfigurationTarget(traversalProject); 2275 AddValidateToolsVersionsTarget(traversalProject); 2276 AddValidateProjectsTarget(traversalProject, projects); 2277 AddGetSolutionConfigurationContentsTarget(traversalProject); 2278 } 2279 2280 /// <summary> 2281 /// Adds the target which validates that the solution configuration specified by the user is supported. 2282 /// </summary> AddValidateSolutionConfigurationTarget(ProjectInstance traversalProject)2283 private void AddValidateSolutionConfigurationTarget(ProjectInstance traversalProject) 2284 { 2285 ProjectTargetInstance initialTarget = traversalProject.AddTarget("ValidateSolutionConfiguration", null, null, null, null, null, null, false /* legacy target returns behaviour */); 2286 2287 if (_solutionFile.SolutionConfigurations.Count > 0) 2288 { 2289 ProjectTaskInstance errorTask = AddErrorWarningMessageInstance 2290 ( 2291 initialTarget, 2292 "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')", 2293 XMakeElements.error, 2294 false /* do not treat as literal */, 2295 "SolutionInvalidSolutionConfiguration", 2296 "$(Configuration)|$(Platform)" 2297 ); 2298 2299 ProjectTaskInstance warningTask = AddErrorWarningMessageInstance 2300 ( 2301 initialTarget, 2302 "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')", 2303 XMakeElements.warning, 2304 false /* do not treat as literal */, 2305 "SolutionInvalidSolutionConfiguration", 2306 "$(Configuration)|$(Platform)" 2307 ); 2308 2309 ProjectTaskInstance messageTask = AddErrorWarningMessageInstance 2310 ( 2311 initialTarget, 2312 "'$(CurrentSolutionConfigurationContents)' != ''", 2313 XMakeElements.message, 2314 false /* do not treat as literal */, 2315 "SolutionBuildingSolutionConfiguration", 2316 "$(Configuration)|$(Platform)" 2317 ); 2318 } 2319 } 2320 2321 /// <summary> 2322 /// Adds the target which validates that the tools version is supported. 2323 /// </summary> AddValidateToolsVersionsTarget(ProjectInstance traversalProject)2324 private void AddValidateToolsVersionsTarget(ProjectInstance traversalProject) 2325 { 2326 ProjectTargetInstance validateToolsVersionsTarget = traversalProject.AddTarget("ValidateToolsVersions", null, null, null, null, null, null, false /* legacy target returns behaviour */); 2327 ProjectTaskInstance toolsVersionErrorTask = AddErrorWarningMessageInstance 2328 ( 2329 validateToolsVersionsTarget, 2330 "'$(MSBuildToolsVersion)' == '2.0' and ('$(ProjectToolsVersion)' != '2.0' and '$(ProjectToolsVersion)' != '')", 2331 XMakeElements.error, 2332 false /* do not treat as literal */, 2333 "SolutionToolsVersionDoesNotSupportProjectToolsVersion", 2334 "$(MSBuildToolsVersion)" 2335 ); 2336 } 2337 2338 /// <summary> Adds the target to fetch solution configuration contents for given configuration|platform combo. </summary> AddGetSolutionConfigurationContentsTarget(ProjectInstance traversalProject)2339 private void AddGetSolutionConfigurationContentsTarget(ProjectInstance traversalProject) 2340 { 2341 var initialTarget = traversalProject.AddTarget( 2342 targetName: "GetSolutionConfigurationContents", 2343 condition: null, 2344 inputs: null, 2345 outputs: "$(SolutionConfigurationContents)", 2346 returns: null, 2347 keepDuplicateOutputs: null, 2348 dependsOnTargets: null, 2349 parentProjectSupportsReturnsAttribute: false); 2350 2351 var property = new ProjectPropertyGroupTaskPropertyInstance( 2352 "SolutionConfigurationContents", 2353 "@(SolutionConfiguration->WithMetadataValue('Identity', '$(Configuration)|$(Platform)')->'%(Content)')", 2354 string.Empty, 2355 initialTarget.Location, 2356 initialTarget.Location); 2357 2358 initialTarget.AddProjectTargetInstanceChild(new ProjectPropertyGroupTaskInstance( 2359 string.Empty, 2360 initialTarget.Location, 2361 initialTarget.Location, 2362 new List<ProjectPropertyGroupTaskPropertyInstance> { property })); 2363 } 2364 2365 #endregion // Methods 2366 } 2367 } 2368