File: Microsoft\Build\Tasks\Windows\GenerateTemporaryTargetAssembly.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationBuildTasks\PresentationBuildTasks.csproj (PresentationBuildTasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//---------------------------------------------------------------------------
//
// Description: This is a MSBuild task which generates a temporary target assembly
//              if current project contains a xaml file with local-type-reference.
//
//              It generates a temporary project file and then call build-engine
//              to compile it.
//
//              The new project file will replace all the Reference Items with the
//              resolved ReferencePath, add all the generated code file into Compile
//              Item list.
//
//---------------------------------------------------------------------------
 
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Xml;
 
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
 
using MS.Utility;
 
// Since we disable PreSharp warnings in this file, PreSharp warning is unknown to C# compiler.
// We first need to disable warnings about unknown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691
 
namespace Microsoft.Build.Tasks.Windows
{
    #region GenerateTemporaryTargetAssembly Task class
 
    /// <summary>
    ///   This task is used to generate a temporary target assembly. It generates
    ///   a temporary project file and then compile it.
    ///
    ///   The generated project file is based on current project file, with below
    ///   modification:
    ///
    ///       A:  Add the generated code files (.g.cs) to Compile Item list.
    ///       B:  Replace Reference Item list with ReferenctPath item list.
    ///           So that it doesn't need to rerun time-consuming task
    ///           ResolveAssemblyReference (RAR) again.
    ///
    /// </summary>
    public sealed class GenerateTemporaryTargetAssembly : Task
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        /// Constructor
        /// </summary>
        public GenerateTemporaryTargetAssembly()
            : base(SR.SharedResourceManager)
        {
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        /// ITask Execute method
        /// </summary>
        /// <returns></returns>
        /// <remarks>Catching all exceptions in this method is appropriate - it will allow the build process to resume if possible after logging errors</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public override bool Execute()
        {
            if (!string.Equals(IncludePackageReferencesDuringMarkupCompilation, "false", StringComparison.OrdinalIgnoreCase))
            {
                return ExecuteGenerateTemporaryTargetAssemblyWithPackageReferenceSupport();
            }
            else
            {
                return ExecuteLegacyGenerateTemporaryTargetAssembly();
            }
        }
 
        /// <summary>
        /// ExecuteLegacyGenerateTemporaryTargetAssembly
        ///
        /// Creates a project file based on the parent project and compiles a temporary assembly.
        ///
        /// Passes IntermediateOutputPath, AssemblyName, and TemporaryTargetAssemblyName as global properties.
        ///
        /// </summary>
        /// <returns></returns>
        /// <remarks>Catching all exceptions in this method is appropriate - it will allow the build process to resume if possible after logging errors</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private bool ExecuteLegacyGenerateTemporaryTargetAssembly()
        {
            bool retValue = true;
 
            // Verification
            try
            {
                XmlDocument xmlProjectDoc = null;
 
                xmlProjectDoc = new XmlDocument( );
                //Bugfix for GB chars, exception thrown when using Load(CurrentProject), when project name has GB characters in it.
                //Using a filestream instead of using string path to avoid the need to properly compose Uri (which is another way of fixing - but more complicated).
                using(FileStream fs = File.OpenRead(CurrentProject))
                {
                    xmlProjectDoc.Load(fs);
                }
                //
                // remove all the WinFX specific item lists
                // ApplicationDefinition, Page, MarkupResource and Resource
                //
 
                RemoveItemsByName(xmlProjectDoc, APPDEFNAME);
                RemoveItemsByName(xmlProjectDoc, PAGENAME);
                RemoveItemsByName(xmlProjectDoc, MARKUPRESOURCENAME);
                RemoveItemsByName(xmlProjectDoc, RESOURCENAME);
 
                // Replace the Reference Item list with ReferencePath
 
                RemoveItemsByName(xmlProjectDoc, REFERENCETYPENAME);
                AddNewItems(xmlProjectDoc, ReferencePathTypeName, ReferencePath);
 
                // Add GeneratedCodeFiles to Compile item list.
                AddNewItems(xmlProjectDoc, CompileTypeName, GeneratedCodeFiles);
 
                string currentProjectName = Path.GetFileNameWithoutExtension(CurrentProject);
                string currentProjectExtension = Path.GetExtension(CurrentProject);
 
                // Create a random file name
                // This can fix the problem of project cache in VS.NET environment.
                //
                // GetRandomFileName( ) could return any possible file name and extension
                // Since this temporary file will be used to represent an MSBUILD project file,
                // we will use the same extension as that of the current project file
                //
                string randomFileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());
 
                // Don't call Path.ChangeExtension to append currentProjectExtension. It will do
                // odd things with project names that already contains a period (like System.Windows.
                // Contols.Ribbon.csproj). Instead, just append the extension - after all, we already know
                // for a fact that this name (i.e., tempProj) lacks a file extension.
                string tempProjPrefix = string.Join("_", currentProjectName, randomFileName, WPFTMP);
                string tempProj = tempProjPrefix  + currentProjectExtension;
 
 
                // Save the xmlDocument content into the temporary project file.
                xmlProjectDoc.Save(tempProj);
 
                //
                // Invoke MSBUILD engine to build this temporary project file.
                //
 
                Hashtable globalProperties = new Hashtable(3);
 
                // Add AssemblyName, IntermediateOutputPath and _TargetAssemblyProjectName to the global property list
                // Note that _TargetAssemblyProjectName is not defined as a property with Output attribute - that doesn't do us much
                // good here. We need _TargetAssemblyProjectName to be a well-known property in the new (temporary) project
                // file, and having it be available in the current MSBUILD process is not useful.
                globalProperties[intermediateOutputPathPropertyName] = IntermediateOutputPath;
 
                globalProperties[assemblyNamePropertyName] = AssemblyName;
                globalProperties[targetAssemblyProjectNamePropertyName] = currentProjectName;
                globalProperties["EmbedUntrackedSources"] = "false";
                Dictionary<string, ITaskItem[]> targetOutputs = new Dictionary<string, ITaskItem[]>();
                retValue = BuildEngine.BuildProjectFile(tempProj, new string[] { CompileTargetName }, globalProperties, targetOutputs);
 
                // If the inner build succeeds, retrieve the path to the local type assembly from the task's TargetOutputs.
                if (retValue)
                {
                    // See Microsoft.WinFX.targets: TargetOutputs from '_CompileTemporaryAssembly' will always contain one item.
                    // <Target Name="_CompileTemporaryAssembly"  DependsOnTargets="$(_CompileTemporaryAssemblyDependsOn)" Returns="$(IntermediateOutputPath)$(TargetFileName)"/>
                    Debug.Assert(targetOutputs.ContainsKey(CompileTargetName));
                    Debug.Assert(targetOutputs[CompileTargetName].Length == 1);
                    TemporaryAssemblyForLocalTypeReference = targetOutputs[CompileTargetName][0].ItemSpec;
                }
 
                // Delete the temporary project file and generated files unless diagnostic mode has been requested
                if (!GenerateTemporaryTargetAssemblyDebuggingInformation)
                {
                    try
                    {
                        File.Delete(tempProj);
 
                        DirectoryInfo intermediateOutputPath = new DirectoryInfo(IntermediateOutputPath);
                        foreach (FileInfo temporaryProjectFile in intermediateOutputPath.EnumerateFiles($"{tempProjPrefix}*"))
                        {
                            temporaryProjectFile.Delete();
                        }
                    }
                    catch (IOException e)
                    {
                        // Failure to delete the file is a non fatal error
                        Log.LogWarningFromException(e);
                    }
                }
            }
            catch (Exception e)
            {
                Log.LogErrorFromException(e);
                retValue = false;
            }
 
            return retValue;
        }
 
        /// <summary>
        /// ExecuteGenerateTemporaryTargetAssemblyWithPackageReferenceSupport
        ///
        /// Creates a project file based on the parent project and compiles a temporary assembly.
        ///
        /// Receives the temporary project name as a parameter and writes properties in to the project file itself.
        ///
        /// No global properties are set.
        ///
        /// </summary>
        /// <returns></returns>
        /// <remarks>Catching all exceptions in this method is appropriate - it will allow the build process to resume if possible after logging errors</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private bool ExecuteGenerateTemporaryTargetAssemblyWithPackageReferenceSupport()
        {
            bool retValue = true;
 
            //
            // Create the temporary target assembly project
            //
            try
            {
                XmlDocument xmlProjectDoc = null;
 
                xmlProjectDoc = new XmlDocument( );
                //Bugfix for GB chars, exception thrown when using Load(CurrentProject), when project name has GB characters in it.
                //Using a filestream instead of using string path to avoid the need to properly compose Uri (which is another way of fixing - but more complicated).
                using(FileStream fs = File.OpenRead(CurrentProject))
                {
                    xmlProjectDoc.Load(fs);
                }
                // remove all the WinFX specific item lists
                // ApplicationDefinition, Page, MarkupResource and Resource
                RemoveItemsByName(xmlProjectDoc, APPDEFNAME);
                RemoveItemsByName(xmlProjectDoc, PAGENAME);
                RemoveItemsByName(xmlProjectDoc, MARKUPRESOURCENAME);
                RemoveItemsByName(xmlProjectDoc, RESOURCENAME);
 
                // Replace the Reference Item list with ReferencePath
                RemoveItemsByName(xmlProjectDoc, REFERENCETYPENAME);
                AddNewItems(xmlProjectDoc, ReferencePathTypeName, ReferencePath);
 
                // Add GeneratedCodeFiles to Compile item list.
                AddNewItems(xmlProjectDoc, CompileTypeName, GeneratedCodeFiles);
 
                // Add Analyzers to Analyzer item list.
                AddNewItems(xmlProjectDoc, AnalyzerTypeName, Analyzers);
 
                // Replace implicit SDK imports with explicit SDK imports
                ReplaceImplicitImports(xmlProjectDoc);
 
                // Add properties required for temporary assembly compilation
                var properties = new List<(string PropertyName, string PropertyValue)>
                {
                    ( nameof(AssemblyName), AssemblyName ),
                    ( nameof(IntermediateOutputPath), IntermediateOutputPath ),
                    ( nameof(BaseIntermediateOutputPath), BaseIntermediateOutputPath ),
                    ( nameof(MSBuildProjectExtensionsPath), MSBuildProjectExtensionsPath ),
                    ( "_TargetAssemblyProjectName", Path.GetFileNameWithoutExtension(CurrentProject) ),
                    ( nameof(RootNamespace), RootNamespace ),
                };
 
                //Removing duplicate AssemblyName
                RemovePropertiesByName(xmlProjectDoc, nameof(AssemblyName));
 
                AddNewProperties(xmlProjectDoc, properties);
 
                // Save the xmlDocument content into the temporary project file.
                xmlProjectDoc.Save(TemporaryTargetAssemblyProjectName);
 
                //
                //  Compile the temporary target assembly project
                //
                Dictionary<string, ITaskItem[]> targetOutputs = new Dictionary<string, ITaskItem[]>();
                retValue = BuildEngine.BuildProjectFile(TemporaryTargetAssemblyProjectName, new string[] { CompileTargetName }, null, targetOutputs);
 
                // If the inner build succeeds, retrieve the path to the local type assembly from the task's TargetOutputs.
                if (retValue)
                {
                    // See Microsoft.WinFX.targets: TargetOutputs from '_CompileTemporaryAssembly' will always contain one item.
                    // <Target Name="_CompileTemporaryAssembly"  DependsOnTargets="$(_CompileTemporaryAssemblyDependsOn)" Returns="$(IntermediateOutputPath)$(TargetFileName)"/>
                    Debug.Assert(targetOutputs.ContainsKey(CompileTargetName));
                    Debug.Assert(targetOutputs[CompileTargetName].Length == 1);
                    TemporaryAssemblyForLocalTypeReference = targetOutputs[CompileTargetName][0].ItemSpec;
                }
 
                // Delete the temporary project file and generated files unless diagnostic mode has been requested
                if (!GenerateTemporaryTargetAssemblyDebuggingInformation)
                {
                    try
                    {
                        File.Delete(TemporaryTargetAssemblyProjectName);
 
                        DirectoryInfo intermediateOutputPath = new DirectoryInfo(IntermediateOutputPath);
                        foreach (FileInfo temporaryProjectFile in intermediateOutputPath.EnumerateFiles($"{Path.GetFileNameWithoutExtension(TemporaryTargetAssemblyProjectName)}*"))
                        {
                            temporaryProjectFile.Delete();
                        }
                    }
                    catch (IOException e)
                    {
                        // Failure to delete the file is a non fatal error
                        Log.LogWarningFromException(e);
                    }
                }
 
            }
            catch (Exception e)
            {
                Log.LogErrorFromException(e);
                retValue = false;
            }
 
            return retValue;
        }
 
        #endregion Public Methods
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// CurrentProject
        ///    The full path of current project file.
        /// </summary>
        [Required]
        public string  CurrentProject
        {
            get { return _currentProject; }
            set { _currentProject = value; }
        }
 
        /// <summary>
        /// MSBuild Binary Path.
        ///   This is required for Project to work correctly.
        /// </summary>
        [Required]
        public string MSBuildBinPath
        {
            get { return _msbuildBinPath; }
            set { _msbuildBinPath = value; }
        }
 
        /// <summary>
        /// GeneratedCodeFiles
        ///    A list of generated code files, it could be empty.
        ///    The list will be added to the Compile item list in new generated project file.
        /// </summary>
        public ITaskItem[] GeneratedCodeFiles
        {
            get { return _generatedCodeFiles; }
            set { _generatedCodeFiles = value; }
        }
 
        /// <summary>
        /// CompileTypeName
        ///   The appropriate item name which can be accepted by managed compiler task.
        ///   It is "Compile" for now.
        ///
        ///   Adding this property is to make the type name configurable, if it is changed,
        ///   No code is required to change in this task, but set a new type name in project file.
        /// </summary>
        [Required]
        public string CompileTypeName
        {
            get { return _compileTypeName; }
            set { _compileTypeName = value; }
        }
 
 
        /// <summary>
        /// ReferencePath
        ///    A list of resolved reference assemblies.
        ///    The list will replace the original Reference item list in generated project file.
        /// </summary>
        public ITaskItem[] ReferencePath
        {
            get { return _referencePath; }
            set { _referencePath = value; }
        }
 
        /// <summary>
        /// ReferencePathTypeName
        ///   The appropriate item name which is used to keep the Reference list in managed compiler task.
        ///   It is "ReferencePath" for now.
        ///
        ///   Adding this property is to make the type name configurable, if it is changed,
        ///   No code is required to change in this task, but set a new type name in project file.
        /// </summary>
        [Required]
        public string ReferencePathTypeName
        {
            get { return _referencePathTypeName; }
            set { _referencePathTypeName = value; }
        }
 
 
        /// <summary>
        /// IntermediateOutputPath
        ///
        /// The value which is set to IntermediateOutputPath property in current project file.
        ///
        /// Passing this value explicitly is to make sure to generate temporary target assembly
        /// in expected directory.
        /// </summary>
        [Required]
        public string IntermediateOutputPath
        {
            get { return _intermediateOutputPath; }
            set { _intermediateOutputPath = value; }
        }
 
        /// <summary>
        /// AssemblyName
        ///
        /// The value which is set to AssemblyName property in current project file.
        /// Passing this value explicitly is to make sure to generate the expected
        /// temporary target assembly.
        ///
        /// </summary>
        [Required]
        public string AssemblyName
        {
            get { return _assemblyName; }
            set { _assemblyName = value; }
        }
 
        /// <summary>
        /// CompileTargetName
        ///
        /// The msbuild target name which is used to generate assembly from source code files.
        /// Usually it is "CoreCompile"
        ///
        /// </summary>
        [Required]
        public string CompileTargetName
        {
            get { return _compileTargetName; }
            set { _compileTargetName = value; }
        }
 
        /// <summary>
        /// Optional <see cref="Boolean"/> task parameter
        ///
        /// When <code>true</code>, debugging information is enabled for the <see cref="GenerateTemporaryTargetAssembly"/>
        /// Task. At this time, the only debugging information that is generated consists of the temporary project that is
        /// created to generate the temporary target assembly. This temporary project is normally deleted at the end of this
        /// MSBUILD task; when <see cref="GenerateTemporaryTargetAssemblyDebuggingInformation"/> is enable, this temporary project
        /// will be retained for inspection by the developer.
        ///
        /// This is a diagnostic parameter, and it defaults to <code>false</code>.
        /// </summary>
        public bool GenerateTemporaryTargetAssemblyDebuggingInformation
        {
            get { return _generateTemporaryTargetAssemblyDebuggingInformation; }
            set { _generateTemporaryTargetAssemblyDebuggingInformation = value; }
        }
 
        /// <summary>
        /// Analyzers
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// </summary>
        public ITaskItem[] Analyzers
        { get; set; }
 
        /// <summary>
        /// AnalyzerTypeName
        ///   The appropriate item name which can be accepted by managed compiler task.
        ///   It is "Analyzer" for now.
        ///
        ///   Adding this property is to make the type name configurable, if it is changed,
        ///   No code is required to change in this task, but set a new type name in project file.
        /// </summary>
        [Required]
        public string AnalyzerTypeName { get; set; }
 
        /// <summary>
        /// RootNamespace
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// </summary>
        public string RootNamespace { get; set; }
 
        /// <summary>
        /// BaseIntermediateOutputPath
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// </summary>
        public string BaseIntermediateOutputPath
        {
            get; set;
        }
 
        /// <summary>
        /// IncludePackageReferencesDuringMarkupCompilation
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// Set this property to 'false' to use the .NET Core 3.0 behavior for this task.
        ///
        /// </summary>
        public string IncludePackageReferencesDuringMarkupCompilation
        { get; set; }
 
        /// <summary>
        /// TemporaryTargetAssemblyProjectName
        ///
        /// Required for PackageReference support.
        ///
        /// This property may be null if 'IncludePackageReferencesDuringMarkupCompilation' is 'false'.
        ///
        /// The file name with extension of the randomly generated project name for the temporary assembly
        ///
        /// </summary>
        public string TemporaryTargetAssemblyProjectName
        { get; set; }
 
        /// <summary>
        ///
        /// MSBuildProjectExtensionsPath
        ///
        /// Required for PackageReference support.
        ///
        /// MSBuildProjectExtensionsPath may be overridden and must be passed into the temporary project.
        ///
        /// This is required for some VS publishing scenarios.
        ///
        /// </summary>
        public string MSBuildProjectExtensionsPath
        { get; set; }
 
        /// <summary>
        ///
        /// TemporaryAssemblyForLocalTypeReference
        ///
        /// The path of the generated temporary local type assembly.
        ///
        /// </summary>
        [Output]
        public string TemporaryAssemblyForLocalTypeReference
        { get; set; }
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        //
        // Remove specific entity from project file.
        //
        private void RemoveEntityByName(XmlDocument xmlProjectDoc, string sItemName, string groupName)
        {
 
            if (xmlProjectDoc == null || String.IsNullOrEmpty(sItemName))
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }
 
            //
            // The project file format is always like below:
            //
            //  <Project  xmlns="...">
            //     <ProjectGroup>
            //         ......
            //     </ProjectGroup>
            //
            //     ...
            //     <ItemGroup>
            //         <ItemNameHere ..../>
            //         ....
            //     </ItemGroup>
            //
            //     ....
            //     <Import ... />
            //     ...
            //     <Target Name="xxx" ..../>
            //
            //      ...
            //
            //  </Project>
            //
            //
            // The order of children nodes under Project Root element is random
            //
 
            XmlNode root = xmlProjectDoc.DocumentElement;
 
            if (root.HasChildNodes == false)
            {
                // If there is no child element in this project file, just return immediatelly.
                return;
            }
 
            for (int i = 0; i < root.ChildNodes.Count; i++)
            {
                XmlElement nodeGroup = root.ChildNodes[i] as XmlElement;
 
                if (nodeGroup != null && string.Equals(nodeGroup.Name, groupName, StringComparison.OrdinalIgnoreCase))
                {
                    //
                    // This is ItemGroup element.
                    //
                    if (nodeGroup.HasChildNodes)
                    {
                        ArrayList itemToRemove = new ArrayList();
 
                        for (int j = 0; j < nodeGroup.ChildNodes.Count; j++)
                        {
                            XmlElement nodeItem = nodeGroup.ChildNodes[j] as XmlElement;
 
                            if (nodeItem != null && string.Equals(nodeItem.Name, sItemName, StringComparison.OrdinalIgnoreCase))
                            {
                                // This is the item that need to remove.
                                // Add it into the temporary array list.
                                // Don't delete it here, since it would affect the ChildNodes of parent element.
                                //
                                itemToRemove.Add(nodeItem);
                            }
                        }
 
                        //
                        // Now it is the right time to delete the elements.
                        //
                        if (itemToRemove.Count > 0)
                        {
                            foreach (object node in itemToRemove)
                            {
                                XmlElement item = node as XmlElement;
 
                                //
                                // Remove this item from its parent node.
                                // the parent node should be nodeGroup.
                                //
                                if (item != null)
                                {
                                    nodeGroup.RemoveChild(item);
                                }
                            }
                        }
                    }
 
                    //
                    // Removed all the items with given name from this item group.
                    //
                    // Continue the loop for the next ItemGroup.
                }
 
            }   // end of "for i" statement.
        }
 
        //
        // Remove specific items from project file. Every item should be under an ItemGroup.
        //
        private void RemoveItemsByName(XmlDocument xmlProjectDoc, string sItemName)
        {
            RemoveEntityByName(xmlProjectDoc, sItemName, ITEMGROUP_NAME);
        }
 
        //
        // Remove specific property from project file. Every property should be under an PropertyGroup.
        //
        private void RemovePropertiesByName(XmlDocument xmlProjectDoc, string sPropertyName)
        {
            RemoveEntityByName(xmlProjectDoc, sPropertyName, PROPERTYGROUP_NAME);
        }
 
        //
        // Add a list of files into an Item in the project file, the ItemName is specified by sItemName.
        //
        private void AddNewItems(XmlDocument xmlProjectDoc, string sItemName, ITaskItem[] pItemList)
        {
            if (xmlProjectDoc == null || String.IsNullOrEmpty(sItemName) || pItemList == null)
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }
 
            XmlNode root = xmlProjectDoc.DocumentElement;
 
            // Create a new ItemGroup element
            XmlNode nodeItemGroup = xmlProjectDoc.CreateElement(ITEMGROUP_NAME, root.NamespaceURI);
 
            // Append this new ItemGroup item into the list of children of the document root.
            root.AppendChild(nodeItemGroup);
 
            XmlElement embedItem = null;
 
            for (int i = 0; i < pItemList.Length; i++)
            {
                // Create an element for the given sItemName
                XmlElement nodeItem = xmlProjectDoc.CreateElement(sItemName, root.NamespaceURI);
 
                // Create an Attribute "Include"
                XmlAttribute attrInclude = xmlProjectDoc.CreateAttribute(INCLUDE_ATTR_NAME);
 
                ITaskItem pItem = pItemList[i];
 
                // Set the value for Include attribute.
                attrInclude.Value = pItem.ItemSpec;
 
                // Add the attribute to current item node.
                nodeItem.SetAttributeNode(attrInclude);
 
                string embedInteropTypesMetadata = pItem.GetMetadata(EMBEDINTEROPTYPES);
                if (!String.IsNullOrEmpty(embedInteropTypesMetadata))
                {
                    embedItem = xmlProjectDoc.CreateElement(EMBEDINTEROPTYPES, root.NamespaceURI);
                    embedItem.InnerText = embedInteropTypesMetadata;
                    nodeItem.AppendChild(embedItem);
                }
 
                string aliases = pItem.GetMetadata(ALIASES);
                if (!String.IsNullOrEmpty(aliases))
                {
                    embedItem = xmlProjectDoc.CreateElement(ALIASES, root.NamespaceURI);
                    embedItem.InnerText = aliases;
                    nodeItem.AppendChild(embedItem);
                }
 
                // Add current item node into the children list of ItemGroup
                nodeItemGroup.AppendChild(nodeItem);
            }
        }
 
        private void AddNewProperties(XmlDocument xmlProjectDoc, List<(string PropertyName, string PropertyValue)> properties )
        {
            if (xmlProjectDoc == null || properties == null )
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }
 
            XmlNode root = xmlProjectDoc.DocumentElement;
 
            // Create a new PropertyGroup element
            XmlNode nodeItemGroup = xmlProjectDoc.CreateElement("PropertyGroup", root.NamespaceURI);
            root.PrependChild(nodeItemGroup);
 
            // Append this new ItemGroup item into the list of children of the document root.
            foreach(var property in properties)
            {
                // Skip empty properties
                if (!string.IsNullOrEmpty(property.PropertyValue))
                {
                    // Create an element for the given propertyName
                    XmlElement nodeItem = xmlProjectDoc.CreateElement(property.PropertyName, root.NamespaceURI);
                    nodeItem.InnerText = property.PropertyValue;
 
                    // Add current item node into the PropertyGroup
                    nodeItemGroup.AppendChild(nodeItem);
                }
            }
        }
 
        //
        // Replace implicit SDK imports with explicit imports
        //
        private static void ReplaceImplicitImports(XmlDocument xmlProjectDoc)
        {
            if (xmlProjectDoc == null)
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }
 
            XmlNode root = xmlProjectDoc.DocumentElement;
 
            for (int i = 0; i < root.Attributes.Count; i++)
            {
                XmlAttribute xmlAttribute = root.Attributes[i];
 
                if (xmlAttribute.Name.Equals("Sdk", StringComparison.OrdinalIgnoreCase))
                {
                    string sdks = xmlAttribute.Value;
 
                    bool removedSdkAttribute = false;
                    XmlNode previousNodeImportProps = null;
                    XmlNode previousNodeImportTargets = null;
 
                    foreach (string sdk in sdks.Split(s_semicolonChar).Select(i => i.Trim()))
                    {
                        //  <Project Sdk="Microsoft.NET.Sdk">
                        //  <Project Sdk="My.Custom.Sdk/1.0.0">
                        //  <Project Sdk="My.Custom.Sdk/min=1.0.0">
                        if (!SdkReference.TryParse(sdk, out SdkReference sdkReference))
                            break;
 
                        // Remove Sdk attribute
                        if (!removedSdkAttribute)
                        {
                            root.Attributes.Remove(xmlAttribute);
 
                            removedSdkAttribute = true;
                        }
 
                        //
                        // Add explicit top import
                        //
                        //  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
                        //  <Import Project="Sdk.props" Sdk="My.Custom.Sdk" Version="1.0.0" />
                        //  <Import Project="Sdk.props" Sdk="My.Custom.Sdk" MinimumVersion="1.0.0" />
                        //
                        XmlNode nodeImportProps = CreateImportProjectSdkNode(xmlProjectDoc, "Sdk.props", sdkReference);
 
                        // Prepend this Import to the root of the XML document
                        if (previousNodeImportProps == null)
                        {
                            previousNodeImportProps = root.PrependChild(nodeImportProps);
                        }
                        else
                        {
                            previousNodeImportProps = root.InsertAfter(nodeImportProps, previousNodeImportProps);
                        }
 
                        //
                        // Add explicit bottom import
                        //
                        //  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
                        //  <Import Project="Sdk.targets" Sdk="My.Custom.Sdk" Version="1.0.0" />
                        //  <Import Project="Sdk.targets" Sdk="My.Custom.Sdk" MinimumVersion="1.0.0" />
                        //
                        XmlNode nodeImportTargets = CreateImportProjectSdkNode(xmlProjectDoc, "Sdk.targets", sdkReference);
 
                        // Append this Import to the end of the XML document
                        if (previousNodeImportTargets == null)
                        {
                            previousNodeImportTargets = root.AppendChild(nodeImportTargets);
                        }
                        else
                        {
                            previousNodeImportTargets = root.InsertAfter(nodeImportTargets, previousNodeImportTargets);
                        }
                    }
                }
            }
        }
 
        // Creates an XmlNode that contains an Import Project element
        //
        //  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
        static XmlNode CreateImportProjectSdkNode(XmlDocument xmlProjectDoc, string projectAttributeValue, SdkReference sdkReference)
        {
            XmlNode nodeImport = xmlProjectDoc.CreateElement("Import", xmlProjectDoc.DocumentElement.NamespaceURI);
            XmlAttribute projectAttribute = xmlProjectDoc.CreateAttribute("Project");
            projectAttribute.Value = projectAttributeValue;
            XmlAttribute sdkAttributeProps = xmlProjectDoc.CreateAttribute("Sdk");
            sdkAttributeProps.Value = sdkReference.Name;
            nodeImport.Attributes.Append(projectAttribute);
            nodeImport.Attributes.Append(sdkAttributeProps);
 
            if (!string.IsNullOrEmpty(sdkReference.Version))
            {
                XmlAttribute sdkVersionAttributeProps = xmlProjectDoc.CreateAttribute("Version");
                sdkVersionAttributeProps.Value = sdkReference.Version;
                nodeImport.Attributes.Append(sdkVersionAttributeProps);
            }
 
            if (!string.IsNullOrEmpty(sdkReference.MinimumVersion))
            {
                XmlAttribute sdkVersionAttributeProps = xmlProjectDoc.CreateAttribute("MinimumVersion");
                sdkVersionAttributeProps.Value = sdkReference.MinimumVersion;
                nodeImport.Attributes.Append(sdkVersionAttributeProps);
            }
 
            return nodeImport;
        }
 
        #endregion Private Methods
 
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        private string _currentProject = String.Empty;
 
        private ITaskItem[] _generatedCodeFiles;
        private ITaskItem[] _referencePath;
 
        private string _referencePathTypeName;
        private string _compileTypeName;
 
        private string _msbuildBinPath;
 
        private string  _intermediateOutputPath;
        private string  _assemblyName;
        private string  _compileTargetName;
        private bool _generateTemporaryTargetAssemblyDebuggingInformation = false;
 
        private const string intermediateOutputPathPropertyName = "IntermediateOutputPath";
        private const string assemblyNamePropertyName = "AssemblyName";
        private const string targetAssemblyProjectNamePropertyName = "_TargetAssemblyProjectName";
 
        private const string ALIASES = "Aliases";
        private const string REFERENCETYPENAME = "Reference";
        private const string EMBEDINTEROPTYPES = "EmbedInteropTypes";
        private const string APPDEFNAME = "ApplicationDefinition";
        private const string PAGENAME = "Page";
        private const string MARKUPRESOURCENAME = "MarkupResource";
        private const string RESOURCENAME = "Resource";
 
        private const string ITEMGROUP_NAME = "ItemGroup";
        private const string PROPERTYGROUP_NAME = "PropertyGroup";
        private const string INCLUDE_ATTR_NAME = "Include";
 
        private const string WPFTMP = "wpftmp";
 
        private static readonly char[] s_semicolonChar = [';'];
 
        #endregion Private Fields
 
    }
 
    #endregion GenerateProjectForLocalTypeReference Task class
}