File: Engine\ToolsetState.cs
Web Access
Project: ..\..\..\src\Deprecated\Engine\Microsoft.Build.Engine.csproj (Microsoft.Build.Engine)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE
// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL
// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED.
 
using System;
using System.IO;
using System.Xml;
 
using Microsoft.Build.Framework;
using Microsoft.Build.BuildEngine.Shared;
 
namespace Microsoft.Build.BuildEngine
{
    // internal delegates to make unit testing the TaskRegistry support easier
    internal delegate string[] GetFiles(string path, string pattern);
    internal delegate XmlDocument LoadXmlFromPath(string path);
 
    /// <summary>
    /// Encapsulates all the state associated with a tools version. Each ToolsetState
    /// aggregates a Toolset, which contains that part of the state that is externally visible.
    /// </summary>
    internal class ToolsetState
    {
        #region Constructors
        /// <summary>
        /// Internal constructor
        /// </summary>
        /// <param name="engine"></param>
        /// <param name="toolset"></param>
        internal ToolsetState(Engine engine, Toolset toolset)
            : this(engine,
                   toolset,
                   new GetFiles(Directory.GetFiles),
                   new LoadXmlFromPath(ToolsetState.LoadXmlDocumentFromPath)
                  )
        {
        }
 
        /// <summary>
        /// Additional constructor to make unit testing the TaskRegistry support easier
        /// </summary>
        /// <remarks>
        /// Internal for unit test purposes only.
        /// </remarks>
        /// <param name="engine"></param>
        /// <param name="toolset"></param>
        /// <param name="getFiles"></param>
        /// <param name="loadXmlFromPath"></param>
        internal ToolsetState(Engine engine,
                         Toolset toolset,
                         GetFiles getFiles,
                         LoadXmlFromPath loadXmlFromPath
                        )
        {
            this.parentEngine = engine;
            this.loggingServices = engine.LoggingServices;
 
            ErrorUtilities.VerifyThrowArgumentNull(toolset, nameof(toolset));
            this.toolset = toolset;
 
            this.getFiles = getFiles;
            this.loadXmlFromPath = loadXmlFromPath;
        }
 
        #endregion
 
        #region Properties
        /// <summary>
        /// Associated Toolset (version name, toolset path, optional associated properties)
        /// </summary>
        internal Toolset Toolset
        {
            get
            {
                return this.toolset;
            }
        }
 
        /// <summary>
        /// Tools version for this toolset
        /// </summary>
        internal string ToolsVersion
        {
            get
            {
                return this.toolset.ToolsVersion;
            }
        }
 
        /// <summary>
        /// Tools path for this toolset
        /// </summary>
        internal string ToolsPath
        {
            get
            {
                return this.toolset.ToolsPath;
            }
        }
 
        /// <summary>
        /// Wrapper for the Toolset property group
        /// </summary>
        internal BuildPropertyGroup BuildProperties
        {
            get
            {
                return this.toolset.BuildProperties;
            }
        }
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Used for validating the project (file) and its imported files against a designated schema.
        ///
        /// PERF NOTE: this property helps to delay creation of the ProjectSchemaValidationHandler object
        /// </summary>
        internal ProjectSchemaValidationHandler SchemaValidator(BuildEventContext buildEventContext)
        {
            if (schemaValidator == null)
            {
                schemaValidator = new ProjectSchemaValidationHandler(buildEventContext, loggingServices, toolset.ToolsPath);
            }
 
            return schemaValidator;
        }
 
        /// <summary>
        /// Return a task registry stub for the tasks in the *.tasks file for this toolset
        /// </summary>
        /// <param name="buildEventContext"></param>
        /// <returns></returns>
        internal ITaskRegistry GetTaskRegistry(BuildEventContext buildEventContext)
        {
            RegisterDefaultTasks(buildEventContext);
            return defaultTaskRegistry;
        }
 
        /// <summary>
        /// Sets the default task registry to the provided value.
        /// </summary>
        /// <param name="taskRegistry"></param>
        internal void SetTaskRegistry(ITaskRegistry taskRegistry)
        {
            ErrorUtilities.VerifyThrowArgumentNull(taskRegistry, nameof(taskRegistry));
            defaultTasksRegistrationAttempted = true;
            defaultTaskRegistry = taskRegistry;
        }
 
        /// <summary>
        /// Method extracted strictly to make unit testing easier.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static XmlDocument LoadXmlDocumentFromPath(string path)
        {
            XmlDocument xmlDocumentFromPath = new XmlDocument();
            xmlDocumentFromPath.Load(path);
            return xmlDocumentFromPath;
        }
 
        /// <summary>
        /// Used to load information about default MSBuild tasks i.e. tasks that do not need to be explicitly declared in projects
        /// with the &lt;UsingTask&gt; element. Default task information is read from special files, which are located in the same
        /// directory as the MSBuild binaries.
        /// </summary>
        /// <remarks>
        /// 1) a default tasks file needs the &lt;Project&gt; root tag in order to be well-formed
        /// 2) the XML declaration tag &lt;?xml ...&gt; is ignored
        /// 3) comment tags are always ignored regardless of their placement
        /// 4) the rest of the tags are expected to be &lt;UsingTask&gt; tags
        /// </remarks>
        private void RegisterDefaultTasks(BuildEventContext buildEventContext)
        {
            if (!defaultTasksRegistrationAttempted)
            {
                try
                {
                    this.defaultTaskRegistry = new TaskRegistry();
 
                    string[] defaultTasksFiles = { };
 
                    try
                    {
                        defaultTasksFiles = getFiles(toolset.ToolsPath, defaultTasksFilePattern);
 
                        if (defaultTasksFiles.Length == 0)
                        {
                            loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(/* this warning truly does not involve any file */ String.Empty),
                                "DefaultTasksFileLoadFailureWarning",
                                defaultTasksFilePattern, toolset.ToolsPath, String.Empty);
                        }
                    }
                    // handle security problems when finding the default tasks files
                    catch (UnauthorizedAccessException e)
                    {
                        loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(/* this warning truly does not involve any file */ String.Empty),
                            "DefaultTasksFileLoadFailureWarning",
                            defaultTasksFilePattern, toolset.ToolsPath, e.Message);
                    }
                    // handle problems when reading the default tasks files
                    catch (Exception e) // Catching Exception, but rethrowing unless it's an IO related exception.
                    {
                        if (ExceptionHandling.NotExpectedException(e))
                        {
                            throw;
                        }
 
                        loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(/* this warning truly does not involve any file */ String.Empty),
                            "DefaultTasksFileLoadFailureWarning",
                            defaultTasksFilePattern, toolset.ToolsPath, e.Message);
                    }
 
                    BuildPropertyGroup propertyBag = null;
 
                    foreach (string defaultTasksFile in defaultTasksFiles)
                    {
                        try
                        {
                            XmlDocument defaultTasks = loadXmlFromPath(defaultTasksFile);
 
                            // look for the first root tag that is not a comment or an XML declaration -- this should be the <Project> tag
                            // NOTE: the XML parser will guarantee there is only one real root element in the file
                            // but we need to find it amongst the other types of XmlNode at the root.
                            foreach (XmlNode topLevelNode in defaultTasks.ChildNodes)
                            {
                                if (XmlUtilities.IsXmlRootElement(topLevelNode))
                                {
                                    ProjectErrorUtilities.VerifyThrowInvalidProject(topLevelNode.LocalName == XMakeElements.project,
                                        topLevelNode, "UnrecognizedElement", topLevelNode.Name);
 
                                    ProjectErrorUtilities.VerifyThrowInvalidProject((topLevelNode.Prefix.Length == 0) && (String.Equals(topLevelNode.NamespaceURI, XMakeAttributes.defaultXmlNamespace, StringComparison.OrdinalIgnoreCase)),
                                        topLevelNode, "ProjectMustBeInMSBuildXmlNamespace", XMakeAttributes.defaultXmlNamespace);
 
                                    // the <Project> tag can only the XML namespace -- no other attributes
                                    foreach (XmlAttribute projectAttribute in topLevelNode.Attributes)
                                    {
                                        ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute(projectAttribute.Name == XMakeAttributes.xmlns, projectAttribute);
                                    }
 
                                    // look at all the child tags of the <Project> root tag we found
                                    foreach (XmlNode usingTaskNode in topLevelNode.ChildNodes)
                                    {
                                        if (usingTaskNode.NodeType != XmlNodeType.Comment)
                                        {
                                            ProjectErrorUtilities.VerifyThrowInvalidProject(usingTaskNode.Name == XMakeElements.usingTask,
                                                usingTaskNode, "UnrecognizedElement", usingTaskNode.Name);
 
                                            // Initialize the property bag if it hasn't been already.
                                            if (propertyBag == null)
                                            {
                                                // Set the value of the MSBuildBinPath/ToolsPath properties.
                                                BuildPropertyGroup reservedPropertyBag = new BuildPropertyGroup();
 
                                                reservedPropertyBag.SetProperty(
                                                    new BuildProperty(ReservedPropertyNames.binPath, EscapingUtilities.Escape(toolset.ToolsPath),
                                                    PropertyType.ReservedProperty));
 
                                                reservedPropertyBag.SetProperty(
                                                    new BuildProperty(ReservedPropertyNames.toolsPath, EscapingUtilities.Escape(toolset.ToolsPath),
                                                    PropertyType.ReservedProperty));
 
                                                // Also set MSBuildAssemblyVersion so that the tasks file can tell between v4 and v12 MSBuild
                                                reservedPropertyBag.SetProperty(
                                                    new BuildProperty(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion,
                                                    PropertyType.ReservedProperty));
 
                                                propertyBag = new BuildPropertyGroup();
                                                propertyBag.ImportInitialProperties(parentEngine.EnvironmentProperties, reservedPropertyBag, BuildProperties, parentEngine.GlobalProperties);
                                            }
 
                                            defaultTaskRegistry.RegisterTask(new UsingTask((XmlElement)usingTaskNode, true), new Expander(propertyBag), loggingServices, buildEventContext);
                                        }
                                    }
 
                                    break;
                                }
                            }
                        }
                        // handle security problems when loading the default tasks file
                        catch (UnauthorizedAccessException e)
                        {
                            loggingServices.LogError(buildEventContext, new BuildEventFileInfo(defaultTasksFile), "DefaultTasksFileFailure", e.Message);
                            break;
                        }
                        // handle problems when loading the default tasks file
                        catch (IOException e)
                        {
                            loggingServices.LogError(buildEventContext, new BuildEventFileInfo(defaultTasksFile), "DefaultTasksFileFailure", e.Message);
                            break;
                        }
                        // handle XML errors in the default tasks file
                        catch (XmlException e)
                        {
                            ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(e),
                                "DefaultTasksFileFailure", e.Message);
                        }
                    }
                }
                finally
                {
                    defaultTasksRegistrationAttempted = true;
                }
            }
        }
        #endregion
 
        #region Data
        // The parent Engine object used for logging
        private Engine parentEngine;
 
        // Logging service for posting messages
        private EngineLoggingServices loggingServices;
 
        // The settings for this toolset (version name, path, and properties)
        private Toolset toolset;
 
        // these files list all default tasks and task assemblies that do not need to be explicitly declared by projects
        private const string defaultTasksFilePattern = "*.tasks";
 
        // indicates if the default tasks file has already been scanned
        private bool defaultTasksRegistrationAttempted = false;
 
        // holds all the default tasks we know about and the assemblies they exist in
        private ITaskRegistry defaultTaskRegistry = null;
 
        // used for validating the project (file) and its imported files against a designated schema
        private ProjectSchemaValidationHandler schemaValidator;
 
        // private delegates to make unit testing the TaskRegistry support easier
        private GetFiles getFiles = null;
        private LoadXmlFromPath loadXmlFromPath = null;
 
        #endregion
    }
}