File: Construction\Solution\SolutionProjectGenerator.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
#if FEATURE_ASPNET_COMPILER
using System.Collections;
#endif
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
 
using Microsoft.Build.BackEnd.SdkResolution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
 
using Project = Microsoft.Build.Evaluation.Project;
using ProjectCollection = Microsoft.Build.Evaluation.ProjectCollection;
using ProjectItem = Microsoft.Build.Evaluation.ProjectItem;
using IProperty = Microsoft.Build.Evaluation.IProperty;
 
using Constants = Microsoft.Build.Internal.Constants;
using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
 
#if FEATURE_ASPNET_COMPILER
using FrameworkName = System.Runtime.Versioning.FrameworkName;
#endif
using Microsoft.Build.Execution;
 
using Microsoft.NET.StringTools;
 
#nullable disable
 
namespace Microsoft.Build.Construction
{
    /// <summary>
    /// This class is used to generate an MSBuild wrapper project for a solution file.
    /// </summary>
    internal class SolutionProjectGenerator
    {
        /// <summary>
        /// Name of the property used to store the path to the solution being built.
        /// </summary>
        internal const string SolutionPathPropertyName = "SolutionPath";
 
#if FEATURE_ASPNET_COMPILER
        /// <summary>
        /// The path node to add in when the output directory for a website is overridden.
        /// </summary>
        private const string WebProjectOverrideFolder = "_PublishedWebsites";
#endif // FEATURE_ASPNET_COMPILER
 
        /// <summary>
        /// Property set by VS when building projects. It's an XML containing the project configurations for ALL projects in the solution for the currently selected solution configuration.
        /// </summary>
        internal const string CurrentSolutionConfigurationContents = nameof(CurrentSolutionConfigurationContents);
 
        /// <summary>
        /// The set of properties all projects in the solution should be built with
        /// </summary>
        private const string SolutionProperties = "BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)";
 
        /// <summary>
        /// The set of properties which identify the configuration and platform to build a project with
        /// </summary>
        private const string SolutionConfigurationAndPlatformProperties = "Configuration=$(Configuration); Platform=$(Platform)";
 
        /// <summary>
        /// The Special Target name which when <see cref="_batchProjectTargets"/> is enabled, all P2P references will just execute this target.
        /// </summary>
        internal const string SolutionProjectReferenceAllTargets = "SlnProjectResolveProjectReference";
 
        /// <summary>
        /// A known list of target names to create.  This is for backwards compatibility.
        /// </summary>
        internal static readonly ImmutableHashSet<string> _defaultTargetNames = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase,
            "Build",
            "Clean",
            "Rebuild",
            "Publish",
            "ValidateSolutionConfiguration",
            "ValidateToolsVersions",
            "ValidateProjects",
            "GetSolutionConfigurationContents");
 
#if FEATURE_ASPNET_COMPILER
        /// <summary>
        /// Version 2.0
        /// </summary>
        private readonly Version _version20 = new Version(2, 0);
 
        /// <summary>
        /// Version 4.0
        /// </summary>
        private readonly Version _version40 = new Version(4, 0);
#endif // FEATURE_ASPNET_COMPILER
 
        /// <summary>
        /// The list of global properties we set on each metaproject and which get passed to each project when building.
        /// </summary>
        private readonly Tuple<string, string>[] _metaprojectGlobalProperties =
        {
            new Tuple<string, string>("Configuration", null), // This is the solution configuration in a metaproject, and project configuration on an actual project
            new Tuple<string, string>("Platform", null), // This is the solution platform in a metaproject, and project platform on an actual project
            new Tuple<string, string>("BuildingSolutionFile", "true"),
            new Tuple<string, string>("CurrentSolutionConfigurationContents", null),
            new Tuple<string, string>("SolutionDir", null),
            new Tuple<string, string>("SolutionExt", null),
            new Tuple<string, string>("SolutionFileName", null),
            new Tuple<string, string>("SolutionName", null),
            new Tuple<string, string>(SolutionPathPropertyName, null)
        };
 
        /// <summary>
        /// The SolutionFile containing information about the solution we're generating a wrapper for.
        /// </summary>
        private readonly SolutionFile _solutionFile;
 
        /// <summary>
        /// The global properties passed under which the project should be opened.
        /// </summary>
        private readonly IDictionary<string, string> _globalProperties;
 
        /// <summary>
        /// The ToolsVersion passed on the commandline, if any.  May be null.
        /// </summary>
        private readonly string _toolsVersionOverride;
 
        /// <summary>
        /// The context of this build (used for logging purposes).
        /// </summary>
        private readonly BuildEventContext _projectBuildEventContext;
 
        /// <summary>
        /// The LoggingService used to log messages.
        /// </summary>
        private readonly ILoggingService _loggingService;
 
        /// <summary>
        /// The list of targets specified to use.
        /// </summary>
        private readonly IReadOnlyCollection<string> _targetNames = new Collection<string>();
 
        /// <summary>
        /// The solution configuration selected for this build.
        /// </summary>
        private string _selectedSolutionConfiguration;
 
        /// <summary>
        /// The <see cref="ISdkResolverService"/> to use.
        /// </summary>
        private readonly ISdkResolverService _sdkResolverService;
 
        /// <summary>
        /// The current build submission ID.
        /// </summary>
        private readonly int _submissionId;
 
        /// <summary>
        /// Create a solution metaproj with one MSBuild task with all project references.
        /// </summary>
        private readonly bool _batchProjectTargets;
 
        /// <summary>
        /// Constructor.
        /// </summary>
        private SolutionProjectGenerator(
            SolutionFile solution,
            IDictionary<string, string> globalProperties,
            string toolsVersionOverride,
            BuildEventContext projectBuildEventContext,
            ILoggingService loggingService,
            IReadOnlyCollection<string> targetNames,
            ISdkResolverService sdkResolverService,
            int submissionId)
        {
            _solutionFile = solution;
            _globalProperties = globalProperties ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            _toolsVersionOverride = toolsVersionOverride;
            _projectBuildEventContext = projectBuildEventContext;
            _loggingService = loggingService;
            _sdkResolverService = sdkResolverService ?? SdkResolverService.Instance;
            _submissionId = submissionId;
            _batchProjectTargets = Traits.Instance.SolutionBatchTargets;
 
            if (targetNames != null)
            {
                _targetNames = targetNames.Select(i => i.Split(new char[] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries).Last()).ToList();
            }
        }
 
        /// <summary>
        /// This method generates an MSBuild project file from the list of projects and project dependencies
        /// that have been collected from the solution file.
        /// </summary>
        /// <param name="solution">The parser which contains the solution file.</param>
        /// <param name="globalProperties">The global properties.</param>
        /// <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>
        /// <param name="projectBuildEventContext">The logging context for this project.</param>
        /// <param name="loggingService">The logging service.</param>
        /// <param name="targetNames">A collection of target names the user requested to be built.</param>
        /// <param name="sdkResolverService">An <see cref="ISdkResolverService"/> to use.</param>
        /// <param name="submissionId">The current build submission ID.</param>
        /// <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>
        internal static ProjectInstance[] 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)
        {
            SolutionProjectGenerator projectGenerator = new SolutionProjectGenerator(
                solution,
                globalProperties,
                toolsVersionOverride,
                projectBuildEventContext,
                loggingService,
                targetNames,
                sdkResolverService,
                submissionId);
 
            return projectGenerator.Generate();
        }
 
        /// <summary>
        /// Adds a new property group with contents of the given solution configuration to the project
        /// Internal for unit-testing.
        /// </summary>
        internal static void AddPropertyGroupForSolutionConfiguration(ProjectRootElement msbuildProject, SolutionFile solutionFile, SolutionConfigurationInSolution solutionConfiguration)
        {
            ProjectPropertyGroupElement solutionConfigurationProperties = msbuildProject.CreatePropertyGroupElement();
            msbuildProject.AppendChild(solutionConfigurationProperties);
            solutionConfigurationProperties.Condition = GetConditionStringForConfiguration(solutionConfiguration);
 
            string escapedSolutionConfigurationContents = GetSolutionConfiguration(solutionFile, solutionConfiguration);
 
            solutionConfigurationProperties.AddProperty("CurrentSolutionConfigurationContents", escapedSolutionConfigurationContents);
 
            msbuildProject.AddItem(
                "SolutionConfiguration",
                solutionConfiguration.FullName,
                new Dictionary<string, string>
                {
                    { "Configuration", solutionConfiguration.ConfigurationName },
                    { "Platform", solutionConfiguration.PlatformName },
                    { "Content", escapedSolutionConfigurationContents },
                });
        }
 
        internal static string GetSolutionConfiguration(SolutionFile solutionFile, SolutionConfigurationInSolution solutionConfiguration)
        {
            var solutionConfigurationContents = new StringBuilder(1024);
            var settings = new XmlWriterSettings
            {
                Indent = true,
                OmitXmlDeclaration = true
            };
            using (XmlWriter xw = XmlWriter.Create(solutionConfigurationContents, settings))
            {
                // TODO: Consider augmenting SolutionConfiguration with this code
                xw.WriteStartElement("SolutionConfiguration");
 
                // add a project configuration entry for each project in the solution
                foreach (ProjectInSolution project in solutionFile.ProjectsInOrder)
                {
                    if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out ProjectConfigurationInSolution projectConfiguration))
                    {
                        xw.WriteStartElement("ProjectConfiguration");
                        xw.WriteAttributeString("Project", project.ProjectGuid);
                        xw.WriteAttributeString("AbsolutePath", project.AbsolutePath);
                        xw.WriteAttributeString("BuildProjectInSolution", projectConfiguration.IncludeInBuild.ToString());
                        xw.WriteString(projectConfiguration.FullName);
 
                        foreach (string dependencyProjectGuid in project.Dependencies)
                        {
                            // This is a project that the current project depends *ON* (ie., it must build first)
                            if (!solutionFile.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out ProjectInSolution dependencyProject))
                            {
                                // If it's not itself part of the solution, that's an invalid solution
                                ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(dependencyProject != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solutionFile.FullPath), "SolutionParseProjectDepNotFoundError", project.ProjectGuid, dependencyProjectGuid);
                            }
 
                            // Add it to the list of dependencies, but only if it should build in this solution configuration
                            // (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)
                            // .. and only if it's known to be MSBuild format, as projects can't use the information otherwise
                            if (dependencyProject.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat)
                            {
                                if (dependencyProject.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out ProjectConfigurationInSolution dependencyProjectConfiguration) &&
                                    WouldProjectBuild(solutionFile, solutionConfiguration.FullName, dependencyProject, dependencyProjectConfiguration))
                                {
                                    xw.WriteStartElement("ProjectDependency");
                                    xw.WriteAttributeString("Project", dependencyProjectGuid);
                                    xw.WriteEndElement();
                                }
                            }
                        }
 
                        xw.WriteEndElement(); // </ProjectConfiguration>
                    }
                }
 
                xw.WriteEndElement(); // </SolutionConfiguration>
            }
 
            string escapedSolutionConfigurationContents = EscapingUtilities.Escape(solutionConfigurationContents.ToString());
            return escapedSolutionConfigurationContents;
        }
 
        /// <summary>
        /// Add a new error/warning/message tag into the given target
        /// </summary>
        internal static ProjectTaskElement AddErrorWarningMessageElement(
            ProjectTargetElement target,
            string elementType,
            bool treatAsLiteral,
            string textResourceName,
            params object[] args)
        {
            string text = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(out string code, out string helpKeyword, textResourceName, args);
 
            if (treatAsLiteral)
            {
                text = EscapingUtilities.Escape(text);
            }
 
            ProjectTaskElement task = target.AddTask(elementType);
            task.SetParameter("Text", text);
 
            if ((elementType != XMakeElements.message) && (code != null))
            {
                task.SetParameter("Code", EscapingUtilities.Escape(code));
            }
 
            if ((elementType != XMakeElements.message) && (helpKeyword != null))
            {
                task.SetParameter("HelpKeyword", EscapingUtilities.Escape(helpKeyword));
            }
 
            return task;
        }
 
        /// <summary>
        /// Normally the active solution configuration/platform is determined when we build the solution
        /// wrapper project, not when we create it. However, we need to know them to scan project references
        /// for the right project configuration/platform. It's unlikely that references would be conditional,
        /// but still possible and we want to get that case right.
        /// </summary>
        internal static string PredictActiveSolutionConfigurationName(SolutionFile solutionFile, IDictionary<string, string> globalProperties)
        {
            string candidateFullSolutionConfigurationName = DetermineLikelyActiveSolutionConfiguration(solutionFile, globalProperties);
 
            // Now check if this solution configuration actually exists
            string fullSolutionConfigurationName = null;
 
            foreach (SolutionConfigurationInSolution solutionConfiguration in solutionFile.SolutionConfigurations)
            {
                if (String.Equals(solutionConfiguration.FullName, candidateFullSolutionConfigurationName, StringComparison.OrdinalIgnoreCase))
                {
                    fullSolutionConfigurationName = solutionConfiguration.FullName;
                    break;
                }
            }
 
            return fullSolutionConfigurationName;
        }
 
        /// <summary>
        /// Returns the name of the metaproject for an actual project.
        /// </summary>
        /// <param name="fullPathToProject">The full path to the actual project</param>
        /// <returns>The metaproject path name</returns>
        private static string GetMetaprojectName(string fullPathToProject)
        {
            return EscapingUtilities.Escape(fullPathToProject + ".metaproj");
        }
 
        /// <summary>
        /// Figure out what tools version to build the solution wrapper project with. If a /tv
        /// switch was passed in, use that; otherwise fall back to the default (12.0).
        /// </summary>
        private static string DetermineWrapperProjectToolsVersion(string toolsVersionOverride, out bool explicitToolsVersionSpecified)
        {
            string wrapperProjectToolsVersion = toolsVersionOverride;
 
            if (wrapperProjectToolsVersion == null)
            {
                explicitToolsVersionSpecified = false;
                wrapperProjectToolsVersion = Constants.defaultSolutionWrapperProjectToolsVersion;
            }
            else
            {
                explicitToolsVersionSpecified = true;
            }
 
            return wrapperProjectToolsVersion;
        }
 
#if FEATURE_ASPNET_COMPILER
        /// <summary>
        /// Add a call to the ResolveAssemblyReference task to crack the pre-resolved referenced
        /// assemblies for the complete list of dependencies, PDBs, satellites, etc.  The invoke
        /// the Copy task to copy all these files (or at least the ones that RAR determined should
        /// be copied local) into the web project's bin directory.
        /// </summary>
        private static void AddTasksToCopyAllDependenciesIntoBinDir(
            ProjectTargetInstance target,
            ProjectInSolution project,
            string referenceItemName,
            string conditionDescribingValidConfigurations)
        {
            string copyLocalFilesItemName = referenceItemName + "_CopyLocalFiles";
            string targetFrameworkDirectoriesName = GenerateSafePropertyName(project, "_TargetFrameworkDirectories");
            string fullFrameworkRefAssyPathName = GenerateSafePropertyName(project, "_FullFrameworkReferenceAssemblyPaths");
            string destinationFolder = String.Format(CultureInfo.InvariantCulture, @"$({0})\Bin\", GenerateSafePropertyName(project, "AspNetPhysicalPath"));
 
            // This is a bit of a hack.  We're actually calling the "Copy" task on all of
            // the *non-existent* files.  Why?  Because we want to emit a warning in the
            // log for each non-existent file, and the Copy task does that nicely for us.
            // I would have used the <Warning> task except for the fact that we are in
            // string-resource lockdown.
            ProjectTaskInstance copyNonExistentReferencesTask = target.AddTask("Copy", String.Format(CultureInfo.InvariantCulture, "!Exists('%({0}.Identity)')", referenceItemName), "true");
            copyNonExistentReferencesTask.SetParameter("SourceFiles", "@(" + referenceItemName + "->'%(FullPath)')");
            copyNonExistentReferencesTask.SetParameter("DestinationFolder", destinationFolder);
 
            // We need to determine the appropriate TargetFrameworkMoniker to pass to GetReferenceAssemblyPaths,
            // so that we will pass the appropriate target framework directories to RAR.
            ProjectTaskInstance getRefAssembliesTask = target.AddTask("GetReferenceAssemblyPaths", null, null);
            getRefAssembliesTask.SetParameter("TargetFrameworkMoniker", project.TargetFrameworkMoniker);
            getRefAssembliesTask.SetParameter("RootPath", "$(TargetFrameworkRootPath)");
            getRefAssembliesTask.AddOutputProperty("ReferenceAssemblyPaths", targetFrameworkDirectoriesName, null);
            getRefAssembliesTask.AddOutputProperty("FullFrameworkReferenceAssemblyPaths", fullFrameworkRefAssyPathName, null);
 
            // Call ResolveAssemblyReference on each of the .DLL files that were found on
            // disk from the .REFRESH files as well as the P2P references.  RAR will crack
            // the dependencies, find PDBs, satellite assemblies, etc., and determine which
            // files need to be copy-localed.
            ProjectTaskInstance rarTask = target.AddTask("ResolveAssemblyReference", String.Format(CultureInfo.InvariantCulture, "Exists('%({0}.Identity)')", referenceItemName), null);
            rarTask.SetParameter("Assemblies", "@(" + referenceItemName + "->'%(FullPath)')");
            rarTask.SetParameter("TargetFrameworkDirectories", "$(" + targetFrameworkDirectoriesName + ")");
            rarTask.SetParameter("FullFrameworkFolders", "$(" + fullFrameworkRefAssyPathName + ")");
            rarTask.SetParameter("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
            rarTask.SetParameter("FindDependencies", "true");
            rarTask.SetParameter("FindSatellites", "true");
            rarTask.SetParameter("FindSerializationAssemblies", "true");
            rarTask.SetParameter("FindRelatedFiles", "true");
            rarTask.SetParameter("TargetFrameworkMoniker", project.TargetFrameworkMoniker);
            rarTask.AddOutputItem("CopyLocalFiles", copyLocalFilesItemName, null);
 
            // Copy all the copy-local files (reported by RAR) to the web project's "bin"
            // directory.
            ProjectTaskInstance copyTask = target.AddTask("Copy", conditionDescribingValidConfigurations, null);
            copyTask.SetParameter("SourceFiles", "@(" + copyLocalFilesItemName + ")");
            copyTask.SetParameter(
                "DestinationFiles",
                String.Format(CultureInfo.InvariantCulture, @"@({0}->'{1}%(DestinationSubDirectory)%(Filename)%(Extension)')", copyLocalFilesItemName, destinationFolder));
        }
 
        /// <summary>
        /// This code handles the *.REFRESH files that are in the "bin" subdirectory of
        /// a web project.  These .REFRESH files are just text files that contain absolute or
        /// relative paths to the referenced assemblies.  The goal of these tasks is to
        /// search all *.REFRESH files and extract fully-qualified absolute paths for
        /// each of the references.
        /// </summary>
        private static void AddTasksToResolveAutoRefreshFileReferences(
            ProjectTargetInstance target,
            ProjectInSolution project,
            string referenceItemName)
        {
            string webRoot = "$(" + GenerateSafePropertyName(project, "AspNetPhysicalPath") + ")";
 
            // Create an item list containing each of the .REFRESH files.
            ProjectTaskInstance createItemTask = target.AddTask("CreateItem", null, null);
            createItemTask.SetParameter("Include", webRoot + @"\Bin\*.refresh");
            createItemTask.AddOutputItem("Include", referenceItemName + "_RefreshFile", null);
 
            // Read the lines out of each .REFRESH file; they should be paths to .DLLs.  Put these paths
            // into an item list.
            ProjectTaskInstance readLinesTask = target.AddTask("ReadLinesFromFile", String.Format(CultureInfo.InvariantCulture, @" '%({0}_RefreshFile.Identity)' != '' ", referenceItemName), null);
            readLinesTask.SetParameter("File", String.Format(CultureInfo.InvariantCulture, @"%({0}_RefreshFile.Identity)", referenceItemName));
            readLinesTask.AddOutputItem("Lines", referenceItemName + "_ReferenceRelPath", null);
 
            // Take those paths and combine them with the root of the web project to form either
            // an absolute path or a path relative to the .SLN file.  These paths can be passed
            // directly to RAR later.
            ProjectTaskInstance combinePathTask = target.AddTask("CombinePath", null, null);
            combinePathTask.SetParameter("BasePath", webRoot);
            combinePathTask.SetParameter("Paths", String.Format(CultureInfo.InvariantCulture, @"@({0}_ReferenceRelPath)", referenceItemName));
            combinePathTask.AddOutputItem("CombinedPaths", referenceItemName, null);
        }
 
        /// <summary>
        /// Adds an MSBuild task to the specified target
        /// </summary>
        private static ProjectTaskInstance AddMSBuildTaskInstance(
            ProjectTargetInstance target,
            string projectPath,
            string msbuildTargetName,
            string configurationName,
            string platformName,
            bool specifyProjectToolsVersion)
        {
            ProjectTaskInstance msbuildTask = target.AddTask("MSBuild", null, null);
            msbuildTask.SetParameter("Projects", EscapingUtilities.Escape(projectPath));
 
            if (!string.IsNullOrEmpty(msbuildTargetName))
            {
                msbuildTask.SetParameter("Targets", msbuildTargetName);
            }
 
            string additionalProperties = string.Format(
                CultureInfo.InvariantCulture,
                "Configuration={0}; Platform={1}; BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)",
                EscapingUtilities.Escape(configurationName),
                EscapingUtilities.Escape(platformName));
 
            msbuildTask.SetParameter("Properties", additionalProperties);
            if (specifyProjectToolsVersion)
            {
                msbuildTask.SetParameter("ToolsVersion", "$(ProjectToolsVersion)");
            }
 
            return msbuildTask;
        }
 
        /// <summary>
        /// Takes a project in the solution and a base property name, and creates a new property name
        /// that can safely be used as an XML element name, and is also unique to that project (by
        /// embedding the project's GUID into the property name.
        /// </summary>
        private static string GenerateSafePropertyName(
            ProjectInSolution proj,
            string propertyName)
        {
            // XML element names cannot contain curly braces, so get rid of them from the project guid.
            string projectGuid = proj.ProjectGuid.Substring(1, proj.ProjectGuid.Length - 2);
            return "Project_" + projectGuid + "_" + propertyName;
        }
#endif // FEATURE_ASPNET_COMPILER
 
        /// <summary>
        /// Makes a legal item name from a given string by replacing invalid characters with '_'
        /// </summary>
        private static string MakeIntoSafeItemName(string name)
        {
            var builder = new StringBuilder(name);
 
            if (name.Length > 0)
            {
                if (!XmlUtilities.IsValidInitialElementNameCharacter(name[0]))
                {
                    builder[0] = '_';
                }
            }
 
            for (int i = 1; i < builder.Length; i++)
            {
                if (!XmlUtilities.IsValidSubsequentElementNameCharacter(builder[i]))
                {
                    builder[i] = '_';
                }
            }
 
            return builder.ToString();
        }
 
        /// <summary>
        /// Add a new error/warning/message tag into the given target
        /// </summary>
        private static ProjectTaskInstance AddErrorWarningMessageInstance(
            ProjectTargetInstance target,
            string condition,
            string elementType,
            bool treatAsLiteral,
            string textResourceName,
            params object[] args)
        {
            string text = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(out string code, out string helpKeyword, textResourceName, args);
 
            if (treatAsLiteral)
            {
                text = EscapingUtilities.Escape(text);
            }
 
            ProjectTaskInstance task = target.AddTask(elementType, condition, null);
            task.SetParameter("Text", text);
 
            if ((elementType != XMakeElements.message) && (code != null))
            {
                task.SetParameter("Code", EscapingUtilities.Escape(code));
            }
 
            if ((elementType != XMakeElements.message) && (helpKeyword != null))
            {
                task.SetParameter("HelpKeyword", EscapingUtilities.Escape(helpKeyword));
            }
 
            return task;
        }
 
        /// <summary>
        /// A helper method for constructing conditions for a solution configuration
        /// </summary>
        /// <remarks>
        /// Sample configuration condition:
        /// '$(Configuration)' == 'Release' and '$(Platform)' == 'Any CPU'
        /// </remarks>
        private static string GetConditionStringForConfiguration(SolutionConfigurationInSolution configuration)
        {
            return string.Format(
                CultureInfo.InvariantCulture,
                " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
                EscapingUtilities.Escape(configuration.ConfigurationName),
                EscapingUtilities.Escape(configuration.PlatformName));
        }
 
        /// <summary>
        /// Figure out what solution configuration we are going to build, whether or not it actually exists in the solution
        /// file.
        /// </summary>
        private static string DetermineLikelyActiveSolutionConfiguration(SolutionFile solutionFile, IDictionary<string, string> globalProperties)
        {
            globalProperties.TryGetValue("Configuration", out string activeSolutionConfiguration);
            globalProperties.TryGetValue("Platform", out string activeSolutionPlatform);
 
            if (String.IsNullOrEmpty(activeSolutionConfiguration))
            {
                activeSolutionConfiguration = solutionFile.GetDefaultConfigurationName();
            }
 
            if (String.IsNullOrEmpty(activeSolutionPlatform))
            {
                activeSolutionPlatform = solutionFile.GetDefaultPlatformName();
            }
 
            var configurationInSolution = new SolutionConfigurationInSolution(activeSolutionConfiguration, activeSolutionPlatform);
 
            return configurationInSolution.FullName;
        }
 
        /// <summary>
        /// Returns true if the specified project will build in the currently selected solution configuration.
        /// </summary>
        internal static bool WouldProjectBuild(SolutionFile solutionFile, string selectedSolutionConfiguration, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration)
        {
            // If the solution filter does not contain this project, do not build it.
            if (!solutionFile.ProjectShouldBuild(project.RelativePath))
            {
                return false;
            }
 
            if (projectConfiguration == null)
            {
                if (project.ProjectType == SolutionProjectType.WebProject)
                {
                    // Sometimes web projects won't have the configuration we need (Release typically.)  But they should still build if there is
                    // a solution configuration for it
                    foreach (SolutionConfigurationInSolution configuration in solutionFile.SolutionConfigurations)
                    {
                        if (String.Equals(configuration.FullName, selectedSolutionConfiguration, StringComparison.OrdinalIgnoreCase))
                        {
                            return true;
                        }
                    }
                }
 
                // No configuration, so it can't build.
                return false;
            }
 
            if (!projectConfiguration.IncludeInBuild)
            {
                // Not included in the build.
                return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Private method: generates an MSBuild wrapper project for the solution passed in; the MSBuild wrapper
        /// project to be generated is the private variable "msbuildProject" and the SolutionFile containing information
        /// about the solution is the private variable "solutionFile"
        /// </summary>
        private ProjectInstance[] Generate()
        {
            // Validate against our minimum for upgradable projects
            ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
                _solutionFile.Version >= SolutionFile.slnFileMinVersion,
                "SubCategoryForSolutionParsingErrors",
                new BuildEventFileInfo(_solutionFile.FullPath),
                "SolutionParseUpgradeNeeded");
 
            // This is needed in order to make decisions about tools versions such as whether to put a
            // ToolsVersion parameter on <MSBuild> task tags and what MSBuildToolsPath to use when
            // scanning child projects for dependency information.
            // The knowledge of whether it was explicitly specified is required because otherwise we
            // don't know whether we need to pass the ToolsVersion on to the child projects or not.
            string wrapperProjectToolsVersion = DetermineWrapperProjectToolsVersion(_toolsVersionOverride, out bool explicitToolsVersionSpecified);
 
            return CreateSolutionProject(wrapperProjectToolsVersion, explicitToolsVersionSpecified);
        }
 
        /// <summary>
        /// Given a parsed solution, generate a top level traversal project and the metaprojects representing the dependencies for each real project
        /// referenced in the solution.
        /// </summary>
        private ProjectInstance[] CreateSolutionProject(string wrapperProjectToolsVersion, bool explicitToolsVersionSpecified)
        {
            AddFakeReleaseSolutionConfigurationIfNecessary();
 
            if (_solutionFile.ContainsWebDeploymentProjects)
            {
                // If there are Web Deployment projects, we need to scan those project files
                // and specify the references explicitly.
                // Other references are either ProjectReferences (taken care of by MSBuild) or
                // explicit manual references in the solution file -- which get parsed out by
                // the SolutionParser.
                string childProjectToolsVersion = DetermineChildProjectToolsVersion(wrapperProjectToolsVersion);
                string fullSolutionConfigurationName = PredictActiveSolutionConfigurationName();
 
                ScanProjectDependencies(childProjectToolsVersion, fullSolutionConfigurationName);
            }
 
            // Get a list of all actual projects in the solution
            var projectsInOrder = new List<ProjectInSolution>(_solutionFile.ProjectsInOrder.Count);
            foreach (ProjectInSolution project in _solutionFile.ProjectsInOrder)
            {
                if (SolutionFile.IsBuildableProject(project))
                {
                    projectsInOrder.Add(project);
                }
            }
 
            // Create the list of our generated projects.
            var projectInstances = new List<ProjectInstance>(projectsInOrder.Count + 1);
 
            // Create the project instance for the traversal project.
            ProjectInstance traversalInstance = CreateTraversalInstance(wrapperProjectToolsVersion, explicitToolsVersionSpecified, projectsInOrder);
 
            // Compute the solution configuration which will be used for this build.  We will use it later.
            _selectedSolutionConfiguration = String.Format(CultureInfo.InvariantCulture, "{0}|{1}", traversalInstance.GetProperty("Configuration").EvaluatedValue, traversalInstance.GetProperty("Platform").EvaluatedValue);
            projectInstances.Add(traversalInstance);
 
            // Now evaluate all of the projects in the solution and handle them appropriately.
            EvaluateAndAddProjects(projectsInOrder, projectInstances, traversalInstance, _selectedSolutionConfiguration);
 
            if (_batchProjectTargets)
            {
                var targetElement = traversalInstance.AddTarget(
                    SolutionProjectReferenceAllTargets,
                    string.Empty,
                    string.Empty,
                    string.Empty,
                    null,
                    string.Empty,
                    string.Empty,
                    string.Empty,
                    string.Empty,
                    false);
 
                // Add global project reference
                AddProjectBuildTask(traversalInstance, null, targetElement, string.Join(";", _targetNames), "@(ProjectReference)", string.Empty, string.Empty);
            }
 
            // Special environment variable to allow people to see the in-memory MSBuild project generated
            // to represent the SLN.
            foreach (ProjectInstance instance in projectInstances)
            {
                EmitMetaproject(instance.ToProjectRootElement(), instance.FullPath);
            }
 
            return projectInstances.ToArray();
        }
 
        /// <summary>
        /// Examine each project in the solution, add references and targets for it, and create metaprojects if necessary.
        /// </summary>
        private void EvaluateAndAddProjects(List<ProjectInSolution> projectsInOrder, List<ProjectInstance> projectInstances, ProjectInstance traversalInstance, string selectedSolutionConfiguration)
        {
            // Now add all of the per-project items, targets and metaprojects.
            foreach (ProjectInSolution project in projectsInOrder)
            {
                project.ProjectConfigurations.TryGetValue(selectedSolutionConfiguration, out ProjectConfigurationInSolution projectConfiguration);
                if (!WouldProjectBuild(_solutionFile, selectedSolutionConfiguration, project, projectConfiguration))
                {
                    // Project wouldn't build, so omit it from further processing.
                    continue;
                }
 
                bool canBuildDirectly = CanBuildDirectly(traversalInstance, project, projectConfiguration);
 
                // Add an entry to @(ProjectReference) for the project.  This will be either a reference directly to the project, or to the
                // metaproject, as appropriate.
                AddProjectReference(traversalInstance, traversalInstance, project, projectConfiguration, canBuildDirectly);
 
                // Add the targets to the traversal project for each standard target.  These will either invoke the project directly or invoke the
                // metaproject, as appropriate
                AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, null, "BuildOutput", canBuildDirectly);
                AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Clean", null, canBuildDirectly);
                AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Rebuild", "BuildOutput", canBuildDirectly);
                AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Publish", null, canBuildDirectly);
 
                // Add any other targets specified by the user that were not already added. A target's presence or absence must be determined at the last
                // minute because whether traversalInstance.Targets.ContainsKey(i) is true or not can change during the enumeration.
                foreach (string targetName in _targetNames.Where(i => !traversalInstance.Targets.ContainsKey(i)))
                {
                    AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, targetName, null, canBuildDirectly);
                }
 
                // If we cannot build the project directly, then we need to generate a metaproject for it.
                if (!canBuildDirectly)
                {
                    ProjectInstance metaproject = CreateMetaproject(traversalInstance, project, projectConfiguration);
                    projectInstances.Add(metaproject);
                }
            }
 
            // Add any other targets specified by the user that were not already added
            foreach (string targetName in _targetNames.Where(i => !traversalInstance.Targets.ContainsKey(i)))
            {
                AddTraversalReferencesTarget(traversalInstance, targetName, null, _batchProjectTargets);
            }
        }
 
        /// <summary>
        /// Adds the standard targets to the traversal project.
        /// </summary>
        private void AddStandardTraversalTargets(ProjectInstance traversalInstance, List<ProjectInSolution> projectsInOrder)
        {
            // Add the initial target with some solution configuration validation/information
            AddInitialTargets(traversalInstance, projectsInOrder);
 
            // Add the targets to traverse the metaprojects.
            AddTraversalReferencesTarget(traversalInstance, null, "CollectedBuildOutput", _batchProjectTargets);
            AddTraversalReferencesTarget(traversalInstance, "Clean", null, _batchProjectTargets);
            AddTraversalReferencesTarget(traversalInstance, "Rebuild", "CollectedBuildOutput", _batchProjectTargets);
            AddTraversalReferencesTarget(traversalInstance, "Publish", null, _batchProjectTargets);
        }
 
        /// <summary>
        /// Creates the traversal project instance.  This has all of the properties against which we can perform evaluations for the remainder of the process.
        /// </summary>
        private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersion, bool explicitToolsVersionSpecified, List<ProjectInSolution> projectsInOrder)
        {
            // Create the traversal project's root element.  We will later instantiate this, and use it for evaluation of conditions on
            // the metaprojects.
            ProjectRootElement traversalProject = ProjectRootElement.Create();
            traversalProject.ToolsVersion = wrapperProjectToolsVersion;
            traversalProject.DefaultTargets = "Build";
            traversalProject.InitialTargets = "ValidateSolutionConfiguration;ValidateToolsVersions;ValidateProjects";
            traversalProject.FullPath = _solutionFile.FullPath + ".metaproj";
 
            // Add default solution configuration/platform names in case the user doesn't specify them on the command line
            AddConfigurationPlatformDefaults(traversalProject);
 
            // Add default Venus configuration names (for more details, see comments for this method)
            AddVenusConfigurationDefaults(traversalProject);
 
            // Add solution related macros
            AddGlobalProperties(traversalProject);
 
            // Add a property group for each solution configuration, each with one XML property containing the
            // project configurations in this solution configuration.
            foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations)
            {
                AddPropertyGroupForSolutionConfiguration(traversalProject, solutionConfiguration);
            }
 
            // Add our global extensibility points to the project representing the solution:
            // Imported at the top:  $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore\*
            // Imported at the bottom:  $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\*
            ProjectImportElement importBefore = traversalProject.CreateImportElement(@"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore\*");
            importBefore.Condition = @"'$(ImportByWildcardBeforeSolution)' != 'false' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore')"; // Avoids wildcard perf problem
 
            ProjectImportElement importAfter = traversalProject.CreateImportElement(@"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\*");
            importAfter.Condition = @"'$(ImportByWildcardBeforeSolution)' != 'false' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter')"; // Avoids wildcard perf problem
 
            /* The code below adds the following XML:
 
            - TOP -
 
                <PropertyGroup Condition="'$(ImportDirectorySolutionProps)' != 'false' and '$(DirectorySolutionPropsPath)' == ''">
                  <_DirectorySolutionPropsFile Condition="'$(_DirectorySolutionPropsFile)' == ''">Directory.Solution.props</_DirectorySolutionPropsFile>
                  <_DirectorySolutionPropsBasePath Condition="'$(_DirectorySolutionPropsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectorySolutionPropsFile)'))</_DirectorySolutionPropsBasePath>
                  <DirectorySolutionPropsPath Condition="'$(_DirectorySolutionPropsBasePath)' != '' and '$(_DirectorySolutionPropsFile)' != ''">$([System.IO.Path]::Combine('$(_DirectorySolutionPropsBasePath)', '$(_DirectorySolutionPropsFile)'))</DirectorySolutionPropsPath>
                </PropertyGroup>
 
                <Import Project="$(DirectorySolutionPropsPath)" Condition="'$(ImportDirectorySolutionProps)' != 'false' and exists('$(DirectorySolutionPropsPath)')"/>
 
            - BOTTOM -
 
                <PropertyGroup Condition="'$(ImportDirectorySolutionTargets)' != 'false' and '$(DirectorySolutionTargetsPath)' == ''">
                  <_DirectorySolutionTargetsFile Condition="'$(_DirectorySolutionTargetsFile)' == ''">Directory.Solution.targets</_DirectorySolutionTargetsFile>
                  <_DirectorySolutionTargetsBasePath Condition="'$(_DirectorySolutionTargetsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectorySolutionTargetsFile)'))</_DirectorySolutionTargetsBasePath>
                  <DirectorySolutionTargetsPath Condition="'$(_DirectorySolutionTargetsBasePath)' != '' and '$(_DirectorySolutionTargetsFile)' != ''">$([System.IO.Path]::Combine('$(_DirectorySolutionTargetsBasePath)', '$(_DirectorySolutionTargetsFile)'))</DirectorySolutionTargetsPath>
                </PropertyGroup>
 
                <Import Project="$(DirectorySolutionTargetsPath)" Condition="'$(ImportDirectorySolutionTargets)' != 'false' and exists('$(DirectorySolutionTargetsPath)')"/>
            */
            ProjectPropertyGroupElement directorySolutionPropsPropertyGroup = traversalProject.CreatePropertyGroupElement();
            directorySolutionPropsPropertyGroup.Condition = "'$(ImportDirectorySolutionProps)' != 'false' and '$(DirectorySolutionPropsPath)' == ''";
 
            ProjectPropertyElement directorySolutionPropsFileProperty = traversalProject.CreatePropertyElement("_DirectorySolutionPropsFile");
            directorySolutionPropsFileProperty.Value = "Directory.Solution.props";
            directorySolutionPropsFileProperty.Condition = "'$(_DirectorySolutionPropsFile)' == ''";
 
            ProjectPropertyElement directorySolutionPropsBasePathProperty = traversalProject.CreatePropertyElement("_DirectorySolutionPropsBasePath");
            directorySolutionPropsBasePathProperty.Value = "$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectorySolutionPropsFile)'))";
            directorySolutionPropsBasePathProperty.Condition = "'$(_DirectorySolutionPropsBasePath)' == ''";
 
            ProjectPropertyElement directorySolutionPropsPathProperty = traversalProject.CreatePropertyElement("DirectorySolutionPropsPath");
            directorySolutionPropsPathProperty.Value = "$([System.IO.Path]::Combine('$(_DirectorySolutionPropsBasePath)', '$(_DirectorySolutionPropsFile)'))";
            directorySolutionPropsPathProperty.Condition = "'$(_DirectorySolutionPropsBasePath)' != '' and '$(_DirectorySolutionPropsFile)' != ''";
 
            ProjectImportElement directorySolutionPropsImport = traversalProject.CreateImportElement("$(DirectorySolutionPropsPath)");
            directorySolutionPropsImport.Condition = "'$(ImportDirectorySolutionProps)' != 'false' and exists('$(DirectorySolutionPropsPath)')";
 
            ProjectPropertyGroupElement directorySolutionTargetsPropertyGroup = traversalProject.CreatePropertyGroupElement();
            directorySolutionTargetsPropertyGroup.Condition = "'$(ImportDirectorySolutionTargets)' != 'false' and '$(DirectorySolutionTargetsPath)' == ''";
 
            ProjectPropertyElement directorySolutionTargetsFileProperty = traversalProject.CreatePropertyElement("_DirectorySolutionTargetsFile");
            directorySolutionTargetsFileProperty.Value = "Directory.Solution.targets";
            directorySolutionTargetsFileProperty.Condition = "'$(_DirectorySolutionTargetsFile)' == ''";
 
            ProjectPropertyElement directorySolutionTargetsBasePathProperty = traversalProject.CreatePropertyElement("_DirectorySolutionTargetsBasePath");
            directorySolutionTargetsBasePathProperty.Value = "$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectorySolutionTargetsFile)'))";
            directorySolutionTargetsBasePathProperty.Condition = "'$(_DirectorySolutionTargetsBasePath)' == ''";
 
            ProjectPropertyElement directorySolutionTargetsPathProperty = traversalProject.CreatePropertyElement("DirectorySolutionTargetsPath");
            directorySolutionTargetsPathProperty.Value = "$([System.IO.Path]::Combine('$(_DirectorySolutionTargetsBasePath)', '$(_DirectorySolutionTargetsFile)'))";
            directorySolutionTargetsPathProperty.Condition = "'$(_DirectorySolutionTargetsBasePath)' != '' and '$(_DirectorySolutionTargetsFile)' != ''";
 
            ProjectImportElement directorySolutionTargetsImport = traversalProject.CreateImportElement("$(DirectorySolutionTargetsPath)");
            directorySolutionTargetsImport.Condition = "'$(ImportDirectorySolutionTargets)' != 'false' and exists('$(DirectorySolutionTargetsPath)')";
 
            // Add our local extensibility points to the project representing the solution
            // Imported at the top: before.mysolution.sln.targets
            // Imported at the bottom: after.mysolution.sln.targets
            string escapedSolutionFile = EscapingUtilities.Escape(Path.GetFileName(_solutionFile.FullPath));
            string escapedSolutionDirectory = EscapingUtilities.Escape(_solutionFile.SolutionFileDirectory);
            string localFile = Path.Combine(escapedSolutionDirectory, "before." + escapedSolutionFile + ".targets");
            ProjectImportElement importBeforeLocal = traversalProject.CreateImportElement(localFile);
            importBeforeLocal.Condition = @"exists('" + localFile + "')";
 
            localFile = Path.Combine(escapedSolutionDirectory, "after." + escapedSolutionFile + ".targets");
            ProjectImportElement importAfterLocal = traversalProject.CreateImportElement(localFile);
            importAfterLocal.Condition = @"exists('" + localFile + "')";
 
            // Put locals second so they can override globals if they want
            traversalProject.PrependChild(importBeforeLocal);
            traversalProject.PrependChild(directorySolutionPropsImport);
            traversalProject.PrependChild(directorySolutionPropsPropertyGroup);
            traversalProject.PrependChild(importBefore);
            traversalProject.AppendChild(importAfter);
            traversalProject.AppendChild(directorySolutionTargetsPropertyGroup);
            traversalProject.AppendChild(directorySolutionTargetsImport);
            traversalProject.AppendChild(importAfterLocal);
 
            directorySolutionTargetsPropertyGroup.AppendChild(directorySolutionTargetsFileProperty);
            directorySolutionTargetsPropertyGroup.AppendChild(directorySolutionTargetsBasePathProperty);
            directorySolutionTargetsPropertyGroup.AppendChild(directorySolutionTargetsPathProperty);
 
            directorySolutionPropsPropertyGroup.AppendChild(directorySolutionPropsFileProperty);
            directorySolutionPropsPropertyGroup.AppendChild(directorySolutionPropsBasePathProperty);
            directorySolutionPropsPropertyGroup.AppendChild(directorySolutionPropsPathProperty);
 
            // These are just dummies necessary to make the evaluation into a project instance succeed when
            // any custom imported targets have declarations like BeforeTargets="Build"
            // They'll be replaced momentarily with the real ones.
            string[] dummyTargetsForEvaluationTime = _defaultTargetNames.Union(_targetNames).ToArray();
            foreach (string targetName in dummyTargetsForEvaluationTime)
            {
                ProjectTargetElement target = traversalProject.CreateTargetElement(targetName);
                // Prepend so that any imported target overrides these default ones.
                traversalProject.PrependChild(target);
            }
 
            // For debugging purposes: some information is lost when evaluating into a project instance,
            // so make it possible to see what we have at this point.
            string path = traversalProject.FullPath;
            string metaprojectPath = _solutionFile.FullPath + ".metaproj.tmp";
            EmitMetaproject(traversalProject, metaprojectPath);
            traversalProject.FullPath = path;
 
            // Create the instance.  From this point forward we can evaluate conditions against the traversal project directly.
            var traversalInstance = new ProjectInstance(
                traversalProject,
                _globalProperties,
                explicitToolsVersionSpecified ? wrapperProjectToolsVersion : null,
                _loggingService,
                _solutionFile.VisualStudioVersion,
                new ProjectCollection(),
                _sdkResolverService,
                _submissionId);
 
            // Traversal meta project entire state has to be serialized as it was generated and hence
            // does not have disk representation to load project from.
            traversalInstance.TranslateEntireState = true;
 
            // Make way for the real ones
            foreach (string targetName in dummyTargetsForEvaluationTime)
            {
                // Remove targets only if they were the dummy ones (from the metaproj path),
                // but leave them if they're from another source (imported/overridden).
                if (traversalInstance.Targets[targetName].Location.File == traversalProject.FullPath)
                {
                    traversalInstance.RemoveTarget(targetName);
                }
            }
 
            AddStandardTraversalTargets(traversalInstance, projectsInOrder);
 
            return traversalInstance;
        }
 
        private void EmitMetaproject(ProjectRootElement metaproject, string path)
        {
            if (Traits.Instance.EmitSolutionMetaproj)
            {
                metaproject.Save(path);
            }
            if (_loggingService.IncludeEvaluationMetaprojects)
            {
                var xml = new StringBuilder();
                using (var writer = new StringWriter(xml))
                {
                    metaproject.Save(writer);
                }
 
                string message = ResourceUtilities.GetResourceString("MetaprojectGenerated");
                var eventArgs = new MetaprojectGeneratedEventArgs(xml.ToString(), path, message)
                {
                    BuildEventContext = _projectBuildEventContext,
                };
                _loggingService.LogBuildEvent(eventArgs);
            }
        }
 
        /// <summary>
        /// This method adds a new ProjectReference item to the specified instance.  The reference will either be to its metaproject (if the project
        /// 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.)
        /// </summary>
        private void AddProjectReference(ProjectInstance traversalProject, ProjectInstance projectInstance, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration, bool direct)
        {
            ProjectItemInstance item;
 
            if (direct)
            {
                // We can build this project directly, so add its reference.
                item = projectInstance.AddItem("ProjectReference", EscapingUtilities.Escape(projectToAdd.AbsolutePath), null);
                item.SetMetadata("ToolsVersion", GetToolsVersionMetadataForDirectMSBuildTask(traversalProject));
                item.SetMetadata("SkipNonexistentProjects", "False"); // Skip if it doesn't exist on disk.
                item.SetMetadata("AdditionalProperties", GetPropertiesMetadataForProjectReference(traversalProject, GetConfigurationAndPlatformPropertiesString(projectConfiguration)));
            }
            else
            {
                // We cannot build directly, add the metaproject reference instead.
                item = projectInstance.AddItem("ProjectReference", GetMetaprojectName(projectToAdd), null);
                item.SetMetadata("ToolsVersion", traversalProject.ToolsVersion);
                item.SetMetadata("SkipNonexistentProjects", "Build"); // Instruct the MSBuild task to try to build even though the file doesn't exist on disk.
                item.SetMetadata("AdditionalProperties", GetPropertiesMetadataForProjectReference(traversalProject, SolutionConfigurationAndPlatformProperties));
            }
 
            // Set raw config and platform for custom build steps to use if they wish
            // Configuration is null for web projects
            if (projectConfiguration != null)
            {
                item.SetMetadata("Configuration", projectConfiguration.ConfigurationName);
                item.SetMetadata("Platform", projectConfiguration.PlatformName);
            }
        }
 
        /// <summary>
        /// The value to be passed to the ToolsVersion attribute of the MSBuild task used to directly build a project.
        /// </summary>
        private static string GetToolsVersionMetadataForDirectMSBuildTask(ProjectInstance traversalProject)
        {
            string directProjectToolsVersion = traversalProject.GetPropertyValue("ProjectToolsVersion");
            return directProjectToolsVersion;
        }
 
        /// <summary>
        /// The value to be passed to the ToolsVersion attribute of the MSBuild task used to directly build a project.
        /// </summary>
        private static string GetToolsVersionAttributeForDirectMSBuildTask()
        {
            return "$(ProjectToolsVersion)";
        }
 
        /// <summary>
        /// The value to be assigned to the metadata for a particular project reference.  Contains only configuration and platform specified in the project configuration, evaluated.
        /// </summary>
        private static string GetPropertiesMetadataForProjectReference(ProjectInstance traversalProject, string configurationAndPlatformProperties)
        {
            string directProjectProperties = traversalProject.ExpandString(configurationAndPlatformProperties);
 
            if (traversalProject.SubToolsetVersion != null)
            {
                // Note: it is enough below to compare traversalProject.SubToolsetVersion with 4.0 as a means to verify if
                // traversalProject.SubToolsetVersion < 12.0 since this path isn't followed for traversalProject.SubToolsetVersion values of 2.0 and 3.5
                if (traversalProject.SubToolsetVersion.Equals("4.0", StringComparison.OrdinalIgnoreCase))
                {
                    directProjectProperties = String.Format(CultureInfo.InvariantCulture, "{0}; {1}={2}", directProjectProperties, Constants.SubToolsetVersionPropertyName, traversalProject.SubToolsetVersion);
                }
            }
 
            return directProjectProperties;
        }
 
        /// <summary>
        /// Gets the project configuration and platform values as an attribute string for an MSBuild task used to build the project.
        /// </summary>
        private static string GetConfigurationAndPlatformPropertiesString(ProjectConfigurationInSolution projectConfiguration)
        {
            string directProjectProperties = String.Format(CultureInfo.InvariantCulture, "Configuration={0}; Platform={1}", projectConfiguration.ConfigurationName, projectConfiguration.PlatformName);
            return directProjectProperties;
        }
 
        /// <summary>
        /// The value to be passed to the Properties attribute of the MSBuild task to build a specific project.  Contains reference to project configuration and
        /// platform as well as the solution configuration bits.
        /// </summary>
        private static string GetPropertiesAttributeForDirectMSBuildTask(ProjectConfigurationInSolution projectConfiguration)
        {
            string directProjectProperties = Strings.WeakIntern(String.Join(";", GetConfigurationAndPlatformPropertiesString(projectConfiguration), SolutionProperties));
            return directProjectProperties;
        }
 
        /// <summary>
        /// Returns true if the specified project can be built directly, without using a metaproject.
        /// </summary>
        private bool CanBuildDirectly(ProjectInstance traversalProject, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration)
        {
            // Can we build this project directly, without a metaproject?  We can if it's MSBuild-able and has no references building in this configuration.
            bool canBuildDirectly = false;
            if ((projectToAdd.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) ||
                (projectToAdd.CanBeMSBuildProjectFile(out _)))
            {
                canBuildDirectly = true;
                foreach (string dependencyProjectGuid in projectToAdd.Dependencies)
                {
                    if (!_solutionFile.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out ProjectInSolution dependencyProject))
                    {
                        ProjectFileErrorUtilities.ThrowInvalidProjectFile(
                            "SubCategoryForSolutionParsingErrors",
                            new BuildEventFileInfo(traversalProject.FullPath),
                            "SolutionParseProjectDepNotFoundError",
                            projectToAdd.ProjectGuid,
                            dependencyProjectGuid);
                    }
 
                    if (WouldProjectBuild(_solutionFile, _selectedSolutionConfiguration, dependencyProject, projectConfiguration))
                    {
                        // This is a reference we would have to build, so we can't build the project directly.
                        canBuildDirectly = false;
                        break;
                    }
                }
            }
 
            return canBuildDirectly;
        }
 
        /// <summary>
        /// Produces a set of targets which allows the MSBuild scheduler to schedule projects in the order automatically by
        /// following their dependencies without enforcing build levels.
        /// </summary>
        /// <remarks>
        /// We want MSBuild to be able to parallelize the builds of these projects where possible and still honor references.
        /// Since the project files referenced by the solution do not (necessarily) themselves contain actual project references
        /// to the projects they depend on, we need to synthesize this relationship ourselves.  This is done by creating a target
        /// which first invokes the project's dependencies, then invokes the actual project itself.  However, invoking the
        /// dependencies must also invoke their dependencies and so on down the line.
        ///
        /// Additionally, we do not wish to create a separate MSBuild project to contain this target yet we want to parallelize
        /// calls to these targets.  The way to do this is to pass in different global properties to the same project in the same
        /// MSBuild call.  MSBuild easily allows this using the AdditionalProperties metadata which can be specified on an Item.
        ///
        /// Assuming the solution project we are generating is called "foo.proj", we can accomplish this parallelism as follows:
        /// <ItemGroup>
        ///     <ProjectReference Include="Project0"/>
        ///     <ProjectReference Include="Project1"/>
        ///     <ProjectReference Include="Project2"/>
        /// </ItemGroup>
        ///
        /// We now have expressed the top level reference to all projects as @(SolutionReference) and each project's
        /// set of references as @(PROJECTNAMEReference).  We construct our target as:
        ///
        /// <Target Name="Build">
        ///     <MSBuild Projects="@(ProjectReference)" Targets="Build" />
        ///     <MSBuild Projects="actualProjectName" Targets="Build" />
        /// </Target>
        ///
        /// The first MSBuild call re-invokes the solution project instructing it to build the reference projects for the
        /// current project.  The second MSBuild call invokes the actual project itself.  Because all reference projects have
        /// the same additional properties, MSBuild will only build the first one it comes across and the rest will be
        /// satisfied from the cache.
        /// </remarks>
        private ProjectInstance CreateMetaproject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration)
        {
            // Create a new project instance with global properties and tools version from the existing project
            ProjectInstance metaprojectInstance = new ProjectInstance(EscapingUtilities.UnescapeAll(GetMetaprojectName(project)), traversalProject, GetMetaprojectGlobalProperties(traversalProject));
 
            // Traversal meta project entire state has to be serialized as it was generated and hence
            // does not have disk representation to load project from.
            metaprojectInstance.TranslateEntireState = true;
 
            // Add the project references which must build before this one.
            AddMetaprojectReferenceItems(traversalProject, metaprojectInstance, project);
 
            if (project.ProjectType == SolutionProjectType.WebProject)
            {
#if !FEATURE_ASPNET_COMPILER
                ProjectFileErrorUtilities.ThrowInvalidProjectFile(
                    "SubCategoryForSolutionParsingErrors",
                    new BuildEventFileInfo(_solutionFile.FullPath),
                    "AspNetCompiler.UnsupportedMSBuildVersion",
                    project.ProjectName);
#else
                AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, null);
                AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Clean");
                AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Rebuild");
                AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Publish");
 
                foreach (string targetName in _targetNames.Where(i => !metaprojectInstance.Targets.ContainsKey(i)))
                {
                    AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, targetName);
                }
#endif
            }
            else if ((project.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) ||
                     (project.CanBeMSBuildProjectFile(out string unknownProjectTypeErrorMessage)))
            {
                // unknownProjectTypeErrorMessage holds the error message generated when we try to determine if a project is an MSBuild format
                // project but it is not.
 
                string safeItemNameFromProjectName = MakeIntoSafeItemName(project.ProjectName);
                string targetOutputItemName = string.Format(CultureInfo.InvariantCulture, "{0}BuildOutput", safeItemNameFromProjectName);
 
                AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Clean", null);
                AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, null, targetOutputItemName);
                AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Rebuild", targetOutputItemName);
                AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Publish", null);
 
                foreach (string targetName in _targetNames.Where(i => !metaprojectInstance.Targets.ContainsKey(i)))
                {
                    AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, targetName, null);
                }
            }
            else
            {
                AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, null, unknownProjectTypeErrorMessage);
                AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Clean", unknownProjectTypeErrorMessage);
                AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Rebuild", unknownProjectTypeErrorMessage);
                AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Publish", unknownProjectTypeErrorMessage);
 
                foreach (string targetName in _targetNames.Where(i => !metaprojectInstance.Targets.ContainsKey(i)))
                {
                    AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, targetName, unknownProjectTypeErrorMessage);
                }
            }
 
            return metaprojectInstance;
        }
 
        /// <summary>
        /// Returns the metaproject name for a given project.
        /// </summary>
        private string GetMetaprojectName(ProjectInSolution project)
        {
            string baseName;
            if (project.ProjectType == SolutionProjectType.WebProject)
            {
                baseName = Path.Combine(_solutionFile.SolutionFileDirectory, MakeIntoSafeItemName(project.GetUniqueProjectName()));
            }
            else
            {
                baseName = project.AbsolutePath;
            }
 
            if (String.IsNullOrEmpty(baseName))
            {
                baseName = project.ProjectName;
            }
 
            baseName = FileUtilities.EnsureNoTrailingSlash(baseName);
 
            return GetMetaprojectName(baseName);
        }
 
        /// <summary>
        /// Adds a set of items which describe the references for this project.
        /// </summary>
        private void AddMetaprojectReferenceItems(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project)
        {
            foreach (string dependencyProjectGuid in project.Dependencies)
            {
                if (!_solutionFile.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out ProjectInSolution dependencyProject))
                {
                    ProjectFileErrorUtilities.ThrowInvalidProjectFile(
                        "SubCategoryForSolutionParsingErrors",
                        new BuildEventFileInfo(traversalProject.FullPath),
                        "SolutionParseProjectDepNotFoundError",
                        project.ProjectGuid,
                        dependencyProjectGuid);
                }
                else
                {
                    if (dependencyProject.ProjectConfigurations.TryGetValue(_selectedSolutionConfiguration, out ProjectConfigurationInSolution dependencyProjectConfiguration) &&
                        WouldProjectBuild(_solutionFile, _selectedSolutionConfiguration, dependencyProject, dependencyProjectConfiguration))
                    {
                        bool canBuildDirectly = CanBuildDirectly(traversalProject, dependencyProject, dependencyProjectConfiguration);
                        AddProjectReference(traversalProject, metaprojectInstance, dependencyProject, dependencyProjectConfiguration, canBuildDirectly);
                    }
                }
            }
        }
 
        /// <summary>
        /// Adds the targets which build the dependencies and actual project for a metaproject.
        /// </summary>
        private static void AddMetaprojectTargetForManagedProject(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetName, string outputItem)
        {
            string outputItemAsItem = null;
            if (!String.IsNullOrEmpty(outputItem))
            {
                outputItemAsItem = "@(" + outputItem + ")";
            }
 
            ProjectTargetInstance target = metaprojectInstance.AddTarget(targetName ?? "Build", String.Empty, String.Empty, outputItemAsItem, null, String.Empty, String.Empty, String.Empty, String.Empty, false /* legacy target returns behaviour */);
 
            AddReferencesBuildTask(target, targetName, null /* No need to capture output */);
 
            // Add the task to build the actual project.
            AddProjectBuildTask(traversalProject, projectConfiguration, target, targetName, EscapingUtilities.Escape(project.AbsolutePath), String.Empty, outputItem);
        }
 
        /// <summary>
        /// Adds an MSBuild task to a real project.
        /// </summary>
        private static void AddProjectBuildTask(ProjectInstance traversalProject, ProjectConfigurationInSolution projectConfiguration, ProjectTargetInstance target, string targetToBuild, string sourceItems, string condition, string outputItem)
        {
            ProjectTaskInstance task = target.AddTask("MSBuild", condition, String.Empty);
            task.SetParameter("Projects", sourceItems);
            if (targetToBuild != null)
            {
                task.SetParameter("Targets", targetToBuild);
            }
 
            task.SetParameter("BuildInParallel", "True");
 
            task.SetParameter("ToolsVersion", GetToolsVersionAttributeForDirectMSBuildTask());
 
            if (projectConfiguration != null)
            {
                task.SetParameter("Properties", GetPropertiesAttributeForDirectMSBuildTask(projectConfiguration));
            }
            else
            {
                task.SetParameter("Properties", SolutionProperties);
            }
 
            if (!string.IsNullOrEmpty(outputItem))
            {
                task.AddOutputItem("TargetOutputs", outputItem, String.Empty);
            }
        }
 
        /// <summary>
        /// Adds an MSBuild task to a single metaproject.  This is used in the traversal project.
        /// </summary>
        private void AddMetaprojectBuildTask(ProjectInSolution project, ProjectTargetInstance target, string targetToBuild, string outputItem)
        {
            ProjectTaskInstance task = target.AddTask("MSBuild", Strings.WeakIntern("'%(ProjectReference.Identity)' == '" + GetMetaprojectName(project) + "'"), String.Empty);
            task.SetParameter("Projects", "@(ProjectReference)");
 
            if (targetToBuild != null)
            {
                task.SetParameter("Targets", targetToBuild);
            }
 
            task.SetParameter("BuildInParallel", "True");
            task.SetParameter("ToolsVersion", "Current");
            task.SetParameter("Properties", SolutionProperties);
 
            if (outputItem != null)
            {
                task.AddOutputItem("TargetOutputs", outputItem, String.Empty);
            }
        }
 
#if FEATURE_ASPNET_COMPILER
        /// <summary>
        /// Add a target for a Venus project into the XML doc that's being generated.  This
        /// target will call the AspNetCompiler task.
        /// </summary>
        private void AddMetaprojectTargetForWebProject(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string targetName)
        {
            // Add a supporting target called "GetFrameworkPathAndRedistList".
            AddTargetForGetFrameworkPathAndRedistList(metaprojectInstance);
 
            ProjectTargetInstance newTarget = metaprojectInstance.AddTarget(targetName ?? "Build", ComputeTargetConditionForWebProject(project), null, null, null, null, "GetFrameworkPathAndRedistList", null, null, false /* legacy target returns behaviour */);
 
            // Build the references
            AddReferencesBuildTask(newTarget, targetName, null /* No need to capture output */);
 
            if (targetName == "Clean")
            {
                // Well, hmmm.  The AspNetCompiler task doesn't support any kind of
                // a "Clean" operation.  The best we can really do is offer up a
                // message saying so.
                AddErrorWarningMessageInstance(newTarget, null, XMakeElements.message, true, "SolutionVenusProjectNoClean");
            }
            else if (targetName == "Publish")
            {
                // Well, hmmm.  The AspNetCompiler task doesn't support any kind of
                // a "Publish" operation.  The best we can really do is offer up a
                // message saying so.
                AddErrorWarningMessageInstance(newTarget, null, XMakeElements.message, true, "SolutionVenusProjectNoPublish");
            }
            else
            {
                // For normal build and "Rebuild", just call the AspNetCompiler task with the
                // correct parameters.  But before calling the AspNetCompiler task, we need to
                // do a bunch of prep work regarding references.
 
                // We're going to build up an MSBuild condition string that represents the valid Configurations.
                // We do this by OR'ing together individual conditions, each of which compares $(Configuration)
                // with a valid configuration name.  We init our condition string to "false", so we can easily
                // OR together more stuff as we go, and also easily take the negation of the condition by putting
                // a ! around the whole thing.
                var conditionDescribingValidConfigurations = new StringBuilder("(false)");
 
                // Loop through all the valid configurations and add a PropertyGroup for each one.
                foreach (DictionaryEntry aspNetConfiguration in project.AspNetConfigurations)
                {
                    string configurationName = (string)aspNetConfiguration.Key;
                    var aspNetCompilerParameters = (AspNetCompilerParameters)aspNetConfiguration.Value;
 
                    // We only add the PropertyGroup once per Venus project.  Without the following "if", we would add
                    // the same identical PropertyGroup twice, once when AddTargetForWebProject is called with
                    // subTargetName=null and once when subTargetName="Rebuild".
                    if (targetName == null)
                    {
                        AddPropertyGroupForAspNetConfiguration(traversalProject, metaprojectInstance, project, configurationName, aspNetCompilerParameters, _solutionFile.FullPath);
                    }
 
                    // Update our big condition string to include this configuration.
                    conditionDescribingValidConfigurations.Append(" or ");
                    conditionDescribingValidConfigurations.AppendFormat(CultureInfo.InvariantCulture, "('$(AspNetConfiguration)' == '{0}')", EscapingUtilities.Escape(configurationName));
                }
 
                StringBuilder referenceItemName = new StringBuilder(GenerateSafePropertyName(project, "References"));
                if (!string.IsNullOrEmpty(targetName))
                {
                    referenceItemName.Append('_');
                    referenceItemName.Append(targetName);
                }
 
                // Add tasks to resolve project references of this web project, if any
                if (project.ProjectReferences.Count > 0)
                {
                    // This is a bit tricky. Even though web projects don't use solution configurations,
                    // we want to use the current solution configuration to build the proper configurations
                    // of referenced projects.
                    foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations)
                    {
                        AddResolveProjectReferenceTasks(
                            traversalProject,
                            newTarget,
                            project,
                            solutionConfiguration,
                            referenceItemName.ToString(),
                            out _);
                    }
                }
 
                // Add tasks to capture the auto-refreshed file references (those .REFRESH files).
                AddTasksToResolveAutoRefreshFileReferences(newTarget, project, referenceItemName.ToString());
 
                // Add a call to RAR (ResolveAssemblyReference) and the Copy task to put the referenced
                // project outputs in the right place
                AddTasksToCopyAllDependenciesIntoBinDir(newTarget, project, referenceItemName.ToString(), conditionDescribingValidConfigurations.ToString());
 
                // Add a call to the AspNetCompiler task, conditioned on having a valid Configuration.
                AddTaskForAspNetCompiler(newTarget, project, conditionDescribingValidConfigurations.ToString());
 
                // Add a call to the <Message> task, conditioned on having an *invalid* Configuration.  The
                // message says that we're skipping the Venus project because it's either not enabled
                // for precompilation, or doesn't support the given configuration.
                AddErrorWarningMessageInstance(
                    newTarget,
                    "!(" + conditionDescribingValidConfigurations + ")",
                    XMakeElements.message,
                    false,
                    "SolutionVenusProjectSkipped");
            }
        }
 
        /// <summary>
        /// Helper method to add a call to the AspNetCompiler task into the given target.
        /// </summary>
        private void AddTaskForAspNetCompiler(
            ProjectTargetInstance target,
            ProjectInSolution project,
            string conditionDescribingValidConfigurations)
        {
            // Add a call to the AspNetCompiler task, conditioned on having a valid Configuration.
            ProjectTaskInstance newTask = target.AddTask("AspNetCompiler", conditionDescribingValidConfigurations, null);
            newTask.SetParameter("VirtualPath", "$(" + GenerateSafePropertyName(project, "AspNetVirtualPath") + ")");
            newTask.SetParameter("PhysicalPath", "$(" + GenerateSafePropertyName(project, "AspNetPhysicalPath") + ")");
            newTask.SetParameter("TargetPath", "$(" + GenerateSafePropertyName(project, "AspNetTargetPath") + ")");
            newTask.SetParameter("Force", "$(" + GenerateSafePropertyName(project, "AspNetForce") + ")");
            newTask.SetParameter("Updateable", "$(" + GenerateSafePropertyName(project, "AspNetUpdateable") + ")");
            newTask.SetParameter("Debug", "$(" + GenerateSafePropertyName(project, "AspNetDebug") + ")");
            newTask.SetParameter("KeyFile", "$(" + GenerateSafePropertyName(project, "AspNetKeyFile") + ")");
            newTask.SetParameter("KeyContainer", "$(" + GenerateSafePropertyName(project, "AspNetKeyContainer") + ")");
            newTask.SetParameter("DelaySign", "$(" + GenerateSafePropertyName(project, "AspNetDelaySign") + ")");
            newTask.SetParameter("AllowPartiallyTrustedCallers", "$(" + GenerateSafePropertyName(project, "AspNetAPTCA") + ")");
            newTask.SetParameter("FixedNames", "$(" + GenerateSafePropertyName(project, "AspNetFixedNames") + ")");
 
            ValidateTargetFrameworkForWebProject(project);
 
            try
            {
                SetToolPathForAspNetCompilerTask(project, newTask);
            }
            catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
            {
                ProjectFileErrorUtilities.ThrowInvalidProjectFile(
                    new BuildEventFileInfo(_solutionFile.FullPath),
                    e,
                    "AspNetCompiler.InvalidTargetFrameworkMonikerFromException",
                    project.ProjectName,
                    project.TargetFrameworkMoniker,
                    e.Message);
            }
        }
 
        private void ValidateTargetFrameworkForWebProject(ProjectInSolution project)
        {
            var targetFramework = new FrameworkName(project.TargetFrameworkMoniker);
            bool isDotNetFramework = String.Equals(targetFramework.Identifier, ".NETFramework", StringComparison.OrdinalIgnoreCase);
 
            if (targetFramework.Version > _version40)
            {
                _loggingService.LogComment(
                    _projectBuildEventContext,
                    MessageImportance.Low,
                    "AspNetCompiler.TargetingHigherFrameworksDefaultsTo40",
                    project.ProjectName,
                    targetFramework.Version.ToString());
            }
            if (!isDotNetFramework)
            {
                ProjectFileErrorUtilities.ThrowInvalidProjectFile(
                    "SubCategoryForSolutionParsingErrors",
                    new BuildEventFileInfo(_solutionFile.FullPath),
                    "AspNetCompiler.InvalidTargetFrameworkMonikerNotDotNET",
                    project.ProjectName,
                    project.TargetFrameworkMoniker);
            }
        }
 
        // As of .NET Framework 4.0, there are only two versions of aspnet_compiler.exe: 2.0 and 4.0.  If
        // the TargetFrameworkVersion is less than 4.0, use the 2.0 version.  Otherwise, just use the 4.0
        // version of the executable, so that if say FV 4.1 is passed in, we don't throw an error.
        private void SetToolPathForAspNetCompilerTask(ProjectInSolution project, ProjectTaskInstance task)
        {
            // generate the target .NET Framework version based on the passed in TargetFrameworkMoniker.
            var targetFramework = new FrameworkName(project.TargetFrameworkMoniker);
            bool shouldDefaultToVersion40 = targetFramework.Version.Major >= 4;
            Version aspnetCompilerVersion = shouldDefaultToVersion40 ? _version40 : _version20;
            string aspnetCompilerPath = FrameworkLocationHelper.GetPathToDotNetFramework(aspnetCompilerVersion);
 
            ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
                aspnetCompilerPath != null,
                "SubCategoryForSolutionParsingErrors",
                new BuildEventFileInfo(_solutionFile.FullPath),
                "AspNetCompiler.20NotInstalled");
 
            task.SetParameter("ToolPath", aspnetCompilerPath);
        }
 
        /// <summary>
        /// Adds MSBuild tasks to a project target to pre-resolve its project references
        /// </summary>
        private void AddResolveProjectReferenceTasks(
            ProjectInstance traversalProject,
            ProjectTargetInstance target,
            ProjectInSolution project,
            SolutionConfigurationInSolution solutionConfiguration,
            string outputReferenceItemName,
            out string addedReferenceGuids)
        {
            var referenceGuids = new StringBuilder();
 
            // Suffix for the reference item name. Since we need to attach additional (different) metadata to every
            // reference item, we need to have helper item lists each with only one item
            int outputReferenceItemNameSuffix = 0;
 
            // Pre-resolve the MSBuild project references
            foreach (string projectReferenceGuid in project.ProjectReferences)
            {
                ProjectInSolution referencedProject = _solutionFile.ProjectsByGuid[projectReferenceGuid];
 
                if ((referencedProject != null) &&
                    (referencedProject.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out ProjectConfigurationInSolution referencedProjectConfiguration)) &&
                    (referencedProjectConfiguration != null))
                {
                    string outputReferenceItemNameWithSuffix = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", outputReferenceItemName, outputReferenceItemNameSuffix);
 
                    if ((referencedProject.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) ||
                        ((referencedProject.ProjectType == SolutionProjectType.Unknown) && (referencedProject.CanBeMSBuildProjectFile(out _))))
                    {
                        string condition = GetConditionStringForConfiguration(solutionConfiguration);
                        if (traversalProject.EvaluateCondition(condition))
                        {
                            bool specifyProjectToolsVersion =
                                !String.Equals(traversalProject.ToolsVersion, "2.0", StringComparison.OrdinalIgnoreCase);
 
                            ProjectTaskInstance msbuildTask = AddMSBuildTaskInstance(
                                target,
                                referencedProject.RelativePath,
                                "GetTargetPath",
                                referencedProjectConfiguration.ConfigurationName,
                                referencedProjectConfiguration.PlatformName,
                                specifyProjectToolsVersion);
                            msbuildTask.AddOutputItem("TargetOutputs", outputReferenceItemNameWithSuffix, null);
                        }
 
                        if (referenceGuids.Length > 0)
                        {
                            referenceGuids.Append(';');
                        }
 
                        referenceGuids.Append(projectReferenceGuid);
 
                        // This merges the one-item item list into the main list, adding the appropriate guid metadata
                        ProjectTaskInstance createItemTask = target.AddTask("CreateItem", null, null);
                        createItemTask.SetParameter("Include", "@(" + outputReferenceItemNameWithSuffix + ")");
                        createItemTask.SetParameter("AdditionalMetadata", "Guid=" + projectReferenceGuid);
                        createItemTask.AddOutputItem("Include", outputReferenceItemName, null);
                    }
 
                    outputReferenceItemNameSuffix++;
                }
            }
 
            addedReferenceGuids = referenceGuids.ToString();
        }
 
        /// <summary>
        /// Add a PropertyGroup to the project for a particular Asp.Net configuration.  This PropertyGroup
        /// will have the correct values for all the Asp.Net properties for this project and this configuration.
        /// </summary>
        private static void AddPropertyGroupForAspNetConfiguration(
            ProjectInstance traversalProject,
            ProjectInstance metaprojectInstance,
            ProjectInSolution project,
            string configurationName,
            AspNetCompilerParameters aspNetCompilerParameters,
            string solutionFile)
        {
            // If the configuration doesn't match, don't add the properties.
            if (!traversalProject.EvaluateCondition(String.Format(CultureInfo.InvariantCulture, " '$(AspNetConfiguration)' == '{0}' ", EscapingUtilities.Escape(configurationName))))
            {
                return;
            }
 
            // Add properties into the property group for each of the AspNetCompiler properties.
            // REVIEW: SetProperty takes an evaluated value.  Are we doing the right thing here?
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetVirtualPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetVirtualPath));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetPhysicalPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetPhysicalPath));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetTargetPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetTargetPath));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetForce"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetForce));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetUpdateable"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetUpdateable));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetDebug"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetDebug));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetKeyFile"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetKeyFile));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetKeyContainer"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetKeyContainer));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetDelaySign"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetDelaySign));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetAPTCA"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetAPTCA));
            metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetFixedNames"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetFixedNames));
 
            string aspNetPhysicalPath = aspNetCompilerParameters.aspNetPhysicalPath;
            if (!String.IsNullOrEmpty(aspNetPhysicalPath))
            {
                // Trim the trailing slash if one exists.
                if (
                        (aspNetPhysicalPath[aspNetPhysicalPath.Length - 1] == Path.AltDirectorySeparatorChar) ||
                        (aspNetPhysicalPath[aspNetPhysicalPath.Length - 1] == Path.DirectorySeparatorChar))
                {
                    aspNetPhysicalPath = aspNetPhysicalPath.Substring(0, aspNetPhysicalPath.Length - 1);
                }
 
                // This gets us the last folder in the physical path.
                string lastFolderInPhysicalPath = null;
 
                try
                {
                    lastFolderInPhysicalPath = Path.GetFileName(aspNetPhysicalPath);
                }
                catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
                {
                    ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
                        false,
                        "SubCategoryForSolutionParsingErrors",
                        new BuildEventFileInfo(solutionFile),
                        e,
                        "SolutionParseInvalidProjectFileName",
                        project.RelativePath,
                        e.Message);
                }
 
                if (!String.IsNullOrEmpty(lastFolderInPhysicalPath))
                {
                    // If there is a global property called "OutDir" set, that means the caller is trying to
                    // override the AspNetTargetPath.  What we want to do in this case is concatenate:
                    // $(OutDir) + "\_PublishedWebsites" + (the last portion of the folder in the AspNetPhysicalPath).
                    if (traversalProject.EvaluateCondition(" '$(OutDir)' != '' "))
                    {
                        string outDirValue = String.Empty;
                        ProjectPropertyInstance outdir = metaprojectInstance.GetProperty("OutDir");
 
                        if (outdir != null)
                        {
                            outDirValue = ProjectInstance.GetPropertyValueEscaped(outdir);
                        }
 
                        // Make sure the path we are appending to has no leading slash to prevent double slashes.
                        string publishWebsitePath = EscapingUtilities.Escape(WebProjectOverrideFolder) + Path.DirectorySeparatorChar + EscapingUtilities.Escape(lastFolderInPhysicalPath) + Path.DirectorySeparatorChar;
 
                        metaprojectInstance.SetProperty(
                            GenerateSafePropertyName(project, "AspNetTargetPath"),
                            outDirValue + publishWebsitePath);
                    }
                }
            }
        }
 
        /// <summary>
        /// When adding a target to build a web project, we want to put a Condition on the Target node that
        /// effectively says "Only build this target if the web project is active (marked for building) in the
        /// current solution configuration.
        /// </summary>
        private string ComputeTargetConditionForWebProject(ProjectInSolution project)
        {
            var condition = new StringBuilder(" ('$(CurrentSolutionConfigurationContents)' != '') and (false");
 
            // Loop through all the solution configurations.
            foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations)
            {
                // Find out if the web project has a project configuration for this solution configuration.
                // (Actually, web projects only have one project configuration, so the TryGetValue should
                // pretty much always return "true".
                if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out ProjectConfigurationInSolution projectConfiguration))
                {
                    // See if the web project is marked as active for this solution configuration.  If so,
                    // we'll build the target.  Otherwise not.
                    if (projectConfiguration.IncludeInBuild)
                    {
                        condition.Append(" or (");
                        condition.Append(GetConditionStringForConfiguration(solutionConfiguration));
                        condition.Append(')');
                    }
                }
                else if (String.Equals(solutionConfiguration.ConfigurationName, "Release", StringComparison.OrdinalIgnoreCase) ||
                         String.Equals(solutionConfiguration.ConfigurationName, "Debug", StringComparison.OrdinalIgnoreCase))
                {
                    // we don't have a project configuration that matches the solution configuration but
                    // the solution configuration is called "Release" or "Debug" which are standard AspNetConfigurations
                    // so these should be available in the solution project
                    condition.Append(" or (");
                    condition.Append(GetConditionStringForConfiguration(solutionConfiguration));
                    condition.Append(')');
                }
            }
 
            condition.Append(") ");
            return condition.ToString();
        }
 
        /// <summary>
        /// Add a target to the project called "GetFrameworkPathAndRedistList".  This target calls the
        /// GetFrameworkPath task and then CreateItem to populate @(_CombinedTargetFrameworkDirectoriesItem) and
        /// @(InstalledAssemblyTables), so that we can pass these into the ResolveAssemblyReference task
        /// when building web projects.
        /// </summary>
        private static void AddTargetForGetFrameworkPathAndRedistList(ProjectInstance metaprojectInstance)
        {
            if (metaprojectInstance.Targets.ContainsKey("GetFrameworkPathAndRedistList"))
            {
                return;
            }
 
            ProjectTargetInstance frameworkPathAndRedistListTarget = metaprojectInstance.AddTarget("GetFrameworkPathAndRedistList", String.Empty, null, null, null, null, null, null, null, false /* legacy target returns behaviour */);
 
            ProjectTaskInstance getFrameworkPathTask = frameworkPathAndRedistListTarget.AddTask("GetFrameworkPath", String.Empty, null);
 
            // Follow the same logic we use in Microsoft.Common.targets to choose the target framework
            // directories (which are then used to find the set of redist lists).
            getFrameworkPathTask.AddOutputItem(
                "Path",
                "_CombinedTargetFrameworkDirectoriesItem",
                "'$(MSBuildToolsVersion)' == '2.0'");
 
            // TFV v4.0 supported by TV 4.0+
            getFrameworkPathTask.AddOutputItem(
                "FrameworkVersion40Path",
                "_CombinedTargetFrameworkDirectoriesItem",
                " '$(TargetFrameworkVersion)' == 'v4.0' and '$(MSBuildToolsVersion)' != '2.0' and '$(MSBuildToolsVersion)' != '3.5'");
 
            // TFV v3.5 supported by TV 3.5+
            getFrameworkPathTask.AddOutputItem(
                "FrameworkVersion35Path",
                "_CombinedTargetFrameworkDirectoriesItem",
                " ('$(TargetFrameworkVersion)' == 'v3.5' or '$(TargetFrameworkVersion)' == 'v4.0') and '$(MSBuildToolsVersion)' != '2.0'");
 
            // TFV v3.0 supported by TV 3.5+ (there was no TV 3.0)
            getFrameworkPathTask.AddOutputItem(
                "FrameworkVersion30Path",
                "_CombinedTargetFrameworkDirectoriesItem",
                " ('$(TargetFrameworkVersion)' == 'v3.0' or '$(TargetFrameworkVersion)' == 'v3.5' or '$(TargetFrameworkVersion)' == 'v4.0') and '$(MSBuildToolsVersion)' != '2.0'");
 
            // 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
            getFrameworkPathTask.AddOutputItem(
                "FrameworkVersion20Path",
                "_CombinedTargetFrameworkDirectoriesItem",
                "'$(MSBuildToolsVersion)' != '2.0'");
 
            ProjectTaskInstance createItemTask = frameworkPathAndRedistListTarget.AddTask("CreateItem", null, null);
            createItemTask.SetParameter("Include", @"@(_CombinedTargetFrameworkDirectoriesItem->'%(Identity)\RedistList\*.xml')");
            createItemTask.AddOutputItem("Include", "InstalledAssemblyTables", null);
        }
#endif // FEATURE_ASPNET_COMPILER
 
        /// <summary>
        /// Adds a target for a project whose type is unknown and we cannot build.  We will emit an error or warning as appropriate.
        /// </summary>
        private void AddMetaprojectTargetForUnknownProjectType(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string targetName, string unknownProjectTypeErrorMessage)
        {
            ProjectTargetInstance newTarget = metaprojectInstance.AddTarget(targetName ?? "Build", "'$(CurrentSolutionConfigurationContents)' != ''", null, null, null, null, null, null, null, false /* legacy target returns behaviour */);
 
            foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations)
            {
                if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out ProjectConfigurationInSolution projectConfiguration))
                {
                    if (projectConfiguration.IncludeInBuild)
                    {
                        // Only add the task if it would run in this configuration.
                        if (!traversalProject.EvaluateCondition(GetConditionStringForConfiguration(solutionConfiguration)))
                        {
                            continue;
                        }
 
                        if (unknownProjectTypeErrorMessage == null)
                        {
                            // we haven't encountered any problems accessing the project file in the past, but do not support
                            // building this project type
                            AddErrorWarningMessageInstance(
                                newTarget,
                                null,
                                XMakeElements.warning,
                                true,
                                "SolutionParseUnknownProjectType",
                                project.RelativePath);
                        }
                        else
                        {
                            // this project file may be of supported type, but we have encountered problems accessing it
                            AddErrorWarningMessageInstance(
                                newTarget,
                                null,
                                XMakeElements.warning,
                                true,
                                "SolutionParseErrorReadingProject",
                                project.RelativePath,
                                unknownProjectTypeErrorMessage);
                        }
                    }
                    else
                    {
                        AddErrorWarningMessageInstance(
                            newTarget,
                            null,
                            XMakeElements.message,
                            true,
                            "SolutionProjectSkippedForBuilding",
                            project.ProjectName,
                            solutionConfiguration.FullName);
                    }
                }
                else
                {
                    AddErrorWarningMessageInstance(
                        newTarget,
                        null,
                        XMakeElements.warning,
                        true,
                        "SolutionProjectConfigurationMissing",
                        project.ProjectName,
                        solutionConfiguration.FullName);
                }
            }
        }
 
        /// <summary>
        /// Adds a target which verifies that all of the project references and configurations are valid.
        /// </summary>
        private void AddValidateProjectsTarget(ProjectInstance traversalProject, List<ProjectInSolution> projects)
        {
            ProjectTargetInstance newTarget = traversalProject.AddTarget("ValidateProjects", null, null, null, null, null, null, null, null, false /* legacy target returns behaviour */);
 
            foreach (ProjectInSolution project in projects)
            {
                foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations)
                {
                    string condition = GetConditionStringForConfiguration(solutionConfiguration);
 
                    if (project.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out ProjectConfigurationInSolution projectConfiguration))
                    {
                        if (!projectConfiguration.IncludeInBuild)
                        {
                            AddErrorWarningMessageInstance(
                                newTarget,
                                condition,
                                XMakeElements.message,
                                true,
                                "SolutionProjectSkippedForBuilding",
                                project.ProjectName,
                                solutionConfiguration.FullName);
                        }
                    }
                    else
                    {
                        AddErrorWarningMessageInstance(
                            newTarget,
                            condition,
                            XMakeElements.warning,
                            true,
                            "SolutionProjectConfigurationMissing",
                            project.ProjectName,
                            solutionConfiguration.FullName);
                    }
                }
            }
        }
 
        /// <summary>
        /// Creates the target used to build all of the references in the traversal project.
        /// </summary>
        private static void AddTraversalReferencesTarget(ProjectInstance traversalProject, string targetName, string outputItem, bool batchBuildTargets)
        {
            string outputItemAsItem = null;
            if (!String.IsNullOrEmpty(outputItem))
            {
                outputItemAsItem = "@(" + outputItem + ")";
            }
 
            string correctedTargetName = targetName ?? "Build";
 
            traversalProject.RemoveTarget(correctedTargetName);
            string dependOnTargets = batchBuildTargets ? SolutionProjectReferenceAllTargets : string.Empty;
            ProjectTargetInstance target = traversalProject.AddTarget(correctedTargetName, string.Empty, string.Empty, outputItemAsItem, null, string.Empty, dependOnTargets, string.Empty, string.Empty, false /* legacy target returns behaviour */);
 
            if (!batchBuildTargets)
            {
                AddReferencesBuildTask(target, targetName, outputItem);
            }
        }
 
        /// <summary>
        /// Adds a task which builds the @(ProjectReference) items.
        /// </summary>
        private static void AddReferencesBuildTask(ProjectTargetInstance target, string targetToBuild, string outputItem)
        {
            ProjectTaskInstance task = target.AddTask("MSBuild", String.Empty, String.Empty);
            if (String.Equals(targetToBuild, "Clean", StringComparison.OrdinalIgnoreCase))
            {
                task.SetParameter("Projects", "@(ProjectReference->Reverse())");
            }
            else
            {
                task.SetParameter("Projects", "@(ProjectReference)");  // The references already have the tools versions and properties set on them.
            }
 
            if (targetToBuild != null)
            {
                task.SetParameter("Targets", targetToBuild);
            }
 
            task.SetParameter("BuildInParallel", "True");
            task.SetParameter("Properties", SolutionProperties);
 
            if (outputItem != null)
            {
                task.AddOutputItem("TargetOutputs", outputItem, String.Empty);
            }
        }
 
        /// <summary>
        /// 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.
        /// </summary>
        private void AddTraversalTargetForProject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetToBuild, string outputItem, bool canBuildDirectly)
        {
            string baseProjectName = ProjectInSolution.DisambiguateProjectTargetName(project.GetUniqueProjectName());
            string actualTargetName = baseProjectName;
            if (targetToBuild != null)
            {
                actualTargetName += ":" + targetToBuild;
            }
 
            // 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
            // after coming up with that target name, it can check if it has already been added.
            if (traversalProject.Targets.ContainsKey(actualTargetName))
            {
                return;
            }
 
            // The output item name is the concatenation of the project name with the specified outputItem.  In the typical case, if the
            // project name is MyProject, the outputItemName will be MyProjectBuildOutput, and the outputItemAsItem will be @(MyProjectBuildOutput).
            // In the case where the project contains characters not allowed as Xml element attribute values, those characters will
            // be replaced with underscores.  In the case where MyProject is actually unrepresentable in Xml, then the
            // outputItemName would be _________BuildOutput.
            string outputItemName = null;
            string outputItemAsItem = null;
            if (!String.IsNullOrEmpty(outputItem))
            {
                outputItemName = MakeIntoSafeItemName(baseProjectName) + outputItem;
                outputItemAsItem = "@(" + outputItemName + ")";
            }
 
            ProjectTargetInstance targetElement = traversalProject.AddTarget(actualTargetName, null, null, outputItemAsItem, null, null, null, null, null, false /* legacy target returns behaviour */);
            if (canBuildDirectly)
            {
                AddProjectBuildTask(traversalProject, projectConfiguration, targetElement, targetToBuild, "@(ProjectReference)", "'%(ProjectReference.Identity)' == '" + EscapingUtilities.Escape(project.AbsolutePath) + "'", outputItemName);
            }
            else
            {
                AddMetaprojectBuildTask(project, targetElement, targetToBuild, outputItemName);
            }
        }
 
        /// <summary>
        /// Retrieves a dictionary representing the global properties which should be transferred to a metaproject.
        /// </summary>
        /// <param name="traversalProject">The traversal from which the global properties should be obtained.</param>
        /// <returns>A dictionary of global properties.</returns>
        private IDictionary<string, string> GetMetaprojectGlobalProperties(ProjectInstance traversalProject)
        {
            var properties = new Dictionary<string, string>(_metaprojectGlobalProperties.Length, StringComparer.OrdinalIgnoreCase);
            foreach (Tuple<string, string> property in _metaprojectGlobalProperties)
            {
                if (property.Item2 == null)
                {
                    properties[property.Item1] = EscapingUtilities.Escape(traversalProject.GetPropertyValue(property.Item1));
                }
                else
                {
                    properties[property.Item1] = EscapingUtilities.Escape(property.Item2);
                }
            }
 
            // Now provide any which are explicitly set on the solution
            foreach (ProjectPropertyInstance globalProperty in traversalProject.GlobalPropertiesDictionary)
            {
                properties[globalProperty.Name] = ((IProperty)globalProperty).EvaluatedValueEscaped;
            }
 
            // If we have a sub-toolset version, it will be set on the P2P from the solution metaproj, so we need
            // to make sure it's set here, too, so the global properties will match.
            if (traversalProject.SubToolsetVersion != null)
            {
                if (traversalProject.SubToolsetVersion.Equals("4.0", StringComparison.OrdinalIgnoreCase))
                {
                    properties[Constants.SubToolsetVersionPropertyName] = traversalProject.SubToolsetVersion;
                }
            }
 
            return properties;
        }
 
        /// <summary>
        /// Figures out what the ToolsVersion should be for child projects (used when scanning
        /// for dependencies)
        /// </summary>
        private string DetermineChildProjectToolsVersion(string wrapperProjectToolsVersion)
        {
            _globalProperties.TryGetValue("ProjectToolsVersion", out string childProjectToolsVersion);
 
            return childProjectToolsVersion ?? wrapperProjectToolsVersion;
        }
 
        /// <summary>
        /// Normally the active solution configuration/platform is determined when we build the solution
        /// wrapper project, not when we create it. However, we need to know them to scan project references
        /// for the right project configuration/platform. It's unlikely that references would be conditional,
        /// but still possible and we want to get that case right.
        /// </summary>
        private string PredictActiveSolutionConfigurationName()
        {
            return PredictActiveSolutionConfigurationName(_solutionFile, _globalProperties);
        }
 
        /// <summary>
        /// Loads each MSBuild project in this solution and looks for its project-to-project references so that
        /// we know what build order we should use when building the solution.
        /// </summary>
        private void ScanProjectDependencies(string childProjectToolsVersion, string fullSolutionConfigurationName)
        {
            // Don't bother with all this if the solution configuration doesn't even exist.
            if (fullSolutionConfigurationName == null)
            {
                return;
            }
 
            foreach (ProjectInSolution project in _solutionFile.ProjectsInOrder)
            {
                // We only need to scan .wdproj projects: Everything else is either MSBuildFormat or
                // something we don't know how to do anything with anyway
                if (project.ProjectType == SolutionProjectType.WebDeploymentProject)
                {
                    // Skip the project if we don't have its configuration in this solution configuration
                    if (!project.ProjectConfigurations.ContainsKey(fullSolutionConfigurationName))
                    {
                        continue;
                    }
 
                    try
                    {
                        Project msbuildProject = new Project(project.AbsolutePath, _globalProperties, childProjectToolsVersion);
 
                        // ProjectDependency items work exactly like ProjectReference items from the point of
                        // view of determining that project B depends on project A.  This item must cause
                        // project A to be built prior to project B.
                        //
                        // This has the format
                        // <ProjectDependency Include="DependentProjectRelativePath">
                        //   <Project>{GUID}</Project>
                        // </Project>
                        IEnumerable<ProjectItem> references = msbuildProject.GetItems("ProjectDependency");
 
                        foreach (ProjectItem reference in references)
                        {
                            string referencedProjectGuid = reference.GetMetadataValue("Project");
                            AddDependencyByGuid(project, referencedProjectGuid);
                        }
 
                        // If this is a web deployment project, we have a reference specified as a property
                        // "SourceWebProject" rather than as a ProjectReference item.  This has the format
                        // {GUID}|PATH_TO_CSPROJ
                        // where
                        // GUID is the project guid for the "source" project
                        // PATH_TO_CSPROJ is the solution-relative path to the csproj file.
                        //
                        // NOTE: This is obsolete and is intended only for backward compatability with
                        // Whidbey-generated web deployment projects.  New projects should use the
                        // ProjectDependency item above.
                        string referencedWebProjectGuid = msbuildProject.GetPropertyValue("SourceWebProject");
                        if (!string.IsNullOrEmpty(referencedWebProjectGuid))
                        {
                            // Grab the guid with its curly braces...
                            referencedWebProjectGuid = referencedWebProjectGuid.Substring(0, 38);
                            AddDependencyByGuid(project, referencedWebProjectGuid);
                        }
                    }
                    catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) // We don't want any problems scanning the project file to result in aborting the build.
                    {
                        _loggingService.LogWarning(
                            _projectBuildEventContext,
                            "SubCategoryForSolutionParsingErrors",
                            new BuildEventFileInfo(project.RelativePath),
                            "SolutionScanProjectDependenciesFailed",
                            project.RelativePath,
                            e.Message);
                    }
                }
            }
        }
 
        /// <summary>
        /// Adds a dependency to the project based on the specified guid string.
        /// </summary>
        /// <remarks>
        /// If the string is null or empty, no dependency is added and this is not considered an error.
        /// </remarks>
        private void AddDependencyByGuid(ProjectInSolution project, string dependencyGuid)
        {
            if (!String.IsNullOrEmpty(dependencyGuid))
            {
                if (_solutionFile.ProjectsByGuid.ContainsKey(dependencyGuid))
                {
                    project.AddDependency(dependencyGuid);
                }
                else
                {
                    _loggingService.LogWarning(
                        _projectBuildEventContext,
                        "SubCategoryForSolutionParsingErrors",
                        new BuildEventFileInfo(_solutionFile.FullPath),
                        "SolutionParseProjectDepNotFoundError",
                        project.ProjectGuid,
                        dependencyGuid);
                }
            }
        }
 
        /// <summary>
        /// Creates default Configuration and Platform values based on solution configurations present in the solution
        /// </summary>
        private void AddConfigurationPlatformDefaults(ProjectRootElement traversalProject)
        {
            ProjectPropertyGroupElement configurationDefaultingPropertyGroup = traversalProject.CreatePropertyGroupElement();
            traversalProject.AppendChild(configurationDefaultingPropertyGroup);
 
            configurationDefaultingPropertyGroup.Condition = " '$(Configuration)' == '' ";
            configurationDefaultingPropertyGroup.AddProperty("Configuration", EscapingUtilities.Escape(_solutionFile.GetDefaultConfigurationName()));
 
            ProjectPropertyGroupElement platformDefaultingPropertyGroup = traversalProject.CreatePropertyGroupElement();
            traversalProject.AppendChild(platformDefaultingPropertyGroup);
 
            platformDefaultingPropertyGroup.Condition = " '$(Platform)' == '' ";
            platformDefaultingPropertyGroup.AddProperty("Platform", EscapingUtilities.Escape(_solutionFile.GetDefaultPlatformName()));
        }
 
        /// <summary>
        /// Adds a new property group with contents of the given solution configuration to the project.
        /// </summary>
        private void AddPropertyGroupForSolutionConfiguration(ProjectRootElement traversalProject, SolutionConfigurationInSolution solutionConfiguration)
        {
            AddPropertyGroupForSolutionConfiguration(traversalProject, _solutionFile, solutionConfiguration);
        }
 
        /// <summary>
        /// Creates the default Venus configuration property based on the selected solution configuration.
        /// Unfortunately, Venus projects only expose one project configuration in the IDE (Debug) although
        /// they allow building Debug and Release from command line. This means that if we wanted to use
        /// the project configuration from the active solution configuration for Venus projects, we'd always
        /// end up with Debug and there'd be no way to build the Release configuration. To work around this,
        /// we use a special mechanism for choosing ASP.NET project configuration: we set it to Release if
        /// we're building a Release solution configuration, and to Debug if we're building a Debug solution
        /// configuration. The property is also settable from the command line, in which case it takes
        /// precedence over this algorithm.
        /// </summary>
        private static void AddVenusConfigurationDefaults(ProjectRootElement traversalProject)
        {
            ProjectPropertyGroupElement venusConfiguration = traversalProject.CreatePropertyGroupElement();
            traversalProject.AppendChild(venusConfiguration);
 
            venusConfiguration.Condition = " ('$(AspNetConfiguration)' == '') ";
            venusConfiguration.AddProperty("AspNetConfiguration", "$(Configuration)");
        }
 
        /// <summary>
        /// Adds solution related build event macros and other global properties to the wrapper project
        /// </summary>
        private void AddGlobalProperties(ProjectRootElement traversalProject)
        {
            ProjectPropertyGroupElement globalProperties = traversalProject.CreatePropertyGroupElement();
            traversalProject.AppendChild(globalProperties);
 
            string directoryName = _solutionFile.SolutionFileDirectory;
            if (!directoryName.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
            {
                directoryName += Path.DirectorySeparatorChar;
            }
 
            globalProperties.AddProperty("SolutionDir", EscapingUtilities.Escape(directoryName));
            globalProperties.AddProperty("SolutionExt", EscapingUtilities.Escape(Path.GetExtension(_solutionFile.FullPath)));
            globalProperties.AddProperty("SolutionFileName", EscapingUtilities.Escape(Path.GetFileName(_solutionFile.FullPath)));
            globalProperties.AddProperty("SolutionName", EscapingUtilities.Escape(Path.GetFileNameWithoutExtension(_solutionFile.FullPath)));
 
            globalProperties.AddProperty(SolutionPathPropertyName, EscapingUtilities.Escape(Path.Combine(_solutionFile.SolutionFileDirectory, Path.GetFileName(_solutionFile.FullPath))));
 
            // Add other global properties
            ProjectPropertyGroupElement frameworkVersionProperties = traversalProject.CreatePropertyGroupElement();
            traversalProject.AppendChild(frameworkVersionProperties);
 
            // Set the property "TargetFrameworkVersion". This is needed for the GetFrameworkPath target.
            // If TargetFrameworkVersion is already set by the user, use that value.
            // Otherwise if MSBuildToolsVersion is 2.0, use "v2.0"
            // Otherwise if MSBuildToolsVersion is 3.5, use "v3.5"
            // Otherwise use "v4.0".
            ProjectPropertyElement tfv20Property = frameworkVersionProperties.AddProperty("TargetFrameworkVersion", "v2.0");
            ProjectPropertyElement tfv35Property = frameworkVersionProperties.AddProperty("TargetFrameworkVersion", "v3.5");
            ProjectPropertyElement tfv40Property = frameworkVersionProperties.AddProperty("TargetFrameworkVersion", "v4.0");
            tfv20Property.Condition = "'$(TargetFrameworkVersion)' == '' and '$(MSBuildToolsVersion)' == '2.0'";
            tfv35Property.Condition = "'$(TargetFrameworkVersion)' == '' and ('$(MSBuildToolsVersion)' == '3.5' or '$(MSBuildToolsVersion)' == '3.0')";
            tfv40Property.Condition = "'$(TargetFrameworkVersion)' == '' and !('$(MSBuildToolsVersion)' == '3.5' or '$(MSBuildToolsVersion)' == '3.0' or '$(MSBuildToolsVersion)' == '2.0')";
        }
 
        /// <summary>
        /// Special hack for web projects. It can happen that there is no Release configuration for solutions
        /// containing web projects, yet we still want to be able to build the Release configuration for
        /// those projects. Since the ASP.NET project configuration defaults to the solution configuration,
        /// we allow Release even if it doesn't actually exist in the solution.
        /// </summary>
        private void AddFakeReleaseSolutionConfigurationIfNecessary()
        {
            if (_solutionFile.ContainsWebProjects)
            {
                bool solutionHasReleaseConfiguration = false;
                foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionFile.SolutionConfigurations)
                {
                    if (string.Equals(solutionConfiguration.ConfigurationName, "Release", StringComparison.OrdinalIgnoreCase))
                    {
                        solutionHasReleaseConfiguration = true;
                        break;
                    }
                }
 
                if ((!solutionHasReleaseConfiguration) && (_solutionFile.SolutionConfigurations.Count > 0))
                {
                    _solutionFile.AddSolutionConfiguration("Release", _solutionFile.GetDefaultPlatformName());
                }
            }
        }
 
        /// <summary>
        /// Adds the initial target to the solution wrapper project, necessary for a few message/error tags
        /// </summary>
        private void AddInitialTargets(ProjectInstance traversalProject, List<ProjectInSolution> projects)
        {
            AddValidateSolutionConfigurationTarget(traversalProject);
            AddValidateToolsVersionsTarget(traversalProject);
            AddValidateProjectsTarget(traversalProject, projects);
            AddGetSolutionConfigurationContentsTarget(traversalProject);
        }
 
        /// <summary>
        /// Adds the target which validates that the solution configuration specified by the user is supported.
        /// </summary>
        private void AddValidateSolutionConfigurationTarget(ProjectInstance traversalProject)
        {
            ProjectTargetInstance initialTarget = traversalProject.AddTarget("ValidateSolutionConfiguration", null, null, null, null, null, null, null, null, false /* legacy target returns behaviour */);
 
            if (_solutionFile.SolutionConfigurations.Count > 0)
            {
                AddErrorWarningMessageInstance(
                    initialTarget,
                    "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')",
                    XMakeElements.error,
                    false /* do not treat as literal */,
                    "SolutionInvalidSolutionConfiguration",
                    "$(Configuration)|$(Platform)");
 
                AddErrorWarningMessageInstance(
                    initialTarget,
                    "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')",
                    XMakeElements.warning,
                    false /* do not treat as literal */,
                    "SolutionInvalidSolutionConfiguration",
                    "$(Configuration)|$(Platform)");
 
                AddErrorWarningMessageInstance(
                    initialTarget,
                    "'$(CurrentSolutionConfigurationContents)' != ''",
                    XMakeElements.message,
                    false /* do not treat as literal */,
                    "SolutionBuildingSolutionConfiguration",
                    "$(Configuration)|$(Platform)");
            }
        }
 
        /// <summary>
        /// Adds the target which validates that the tools version is supported.
        /// </summary>
        private static void AddValidateToolsVersionsTarget(ProjectInstance traversalProject)
        {
            ProjectTargetInstance validateToolsVersionsTarget = traversalProject.AddTarget("ValidateToolsVersions", null, null, null, null, null, null, null, null, false /* legacy target returns behaviour */);
            ProjectTaskInstance toolsVersionErrorTask = AddErrorWarningMessageInstance(
                validateToolsVersionsTarget,
                "'$(MSBuildToolsVersion)' == '2.0' and ('$(ProjectToolsVersion)' != '2.0' and '$(ProjectToolsVersion)' != '')",
                XMakeElements.error,
                false /* do not treat as literal */,
                "SolutionToolsVersionDoesNotSupportProjectToolsVersion",
                "$(MSBuildToolsVersion)");
        }
 
        /// <summary> Adds the target to fetch solution configuration contents for given configuration|platform combo. </summary>
        private static void AddGetSolutionConfigurationContentsTarget(ProjectInstance traversalProject)
        {
            var initialTarget = traversalProject.AddTarget(
                targetName: "GetSolutionConfigurationContents",
                condition: null,
                inputs: null,
                outputs: "$(SolutionConfigurationContents)",
                returns: null,
                keepDuplicateOutputs: null,
                dependsOnTargets: null,
                beforeTargets: null,
                afterTargets: null,
                parentProjectSupportsReturnsAttribute: false);
 
            var property = new ProjectPropertyGroupTaskPropertyInstance(
                                                    "SolutionConfigurationContents",
                                                    "@(SolutionConfiguration->WithMetadataValue('Identity', '$(Configuration)|$(Platform)')->'%(Content)')",
                                                    string.Empty,
                                                    initialTarget.Location,
                                                    initialTarget.Location);
 
            initialTarget.AddProjectTargetInstanceChild(new ProjectPropertyGroupTaskInstance(
                                                            string.Empty,
                                                            initialTarget.Location,
                                                            initialTarget.Location,
                                                            new List<ProjectPropertyGroupTaskPropertyInstance> { property }));
        }
    }
}