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