File: ResolveNonMSBuildProjectOutput.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
 
#nullable disable
 
namespace Microsoft.Build.Tasks
{
    /// <remarks>
    /// This task determines the output files for non-MSBuild project references. We look inside
    /// a special property set by the VS IDE for the list of project guids and their associated outputs.
    /// While there's nothing that would prevent resolution of MSBuild projects in this task, the IDE
    /// only pre-resolves non-MSBuild projects so that we can separate MSBuild project references from
    /// non-MSBuild ones and return the list of MSBuild projects as UnresolvedProjectReferences.
    /// Then we can use more powerful MSBuild mechanisms to manipulate just the MSBuild project
    /// references (i.e. calling into specific targets of references to get the manifest file name)
    /// which would not be possible with a mixed list of MSBuild and non-MSBuild references.
    /// </remarks>
    public class ResolveNonMSBuildProjectOutput : ResolveProjectBase
    {
        #region Constructors
 
        /// <summary>
        /// default public constructor
        /// </summary>
        public ResolveNonMSBuildProjectOutput()
        {
            // do nothing
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// A special XML string containing resolved project outputs - we need to simply match the projects and
        /// return the appropriate paths
        /// </summary>
        public string PreresolvedProjectOutputs { get; set; }
 
        /// <summary>
        /// The list of resolved reference paths (preserving the original project reference attributes)
        /// </summary>
        [Output]
        public ITaskItem[] ResolvedOutputPaths { get; set; }
 
        /// <summary>
        /// The list of project reference items that could not be resolved using the pre-resolved list of outputs.
        /// Since VS only pre-resolves non-MSBuild projects, this means that project references in this list
        /// are in the MSBuild format.
        /// </summary>
        [Output]
        public ITaskItem[] UnresolvedProjectReferences { get; set; }
 
        /// <summary>
        /// A delegate with a signature that matches AssemblyName.GetAssemblyName.
        /// </summary>
        internal delegate AssemblyName GetAssemblyNameDelegate(string path);
 
        /// <summary>
        /// A dependency-injection way of getting an assembly name.
        /// </summary>
        internal GetAssemblyNameDelegate GetAssemblyName { get; set; }
 
        #endregion
 
        #region ITask Members
 
        /// <summary>
        /// Main task method
        /// </summary>
        public override bool Execute()
        {
            // Allow unit tests to inject a non-file system dependent version of this.
            if (GetAssemblyName == null)
            {
                GetAssemblyName = AssemblyName.GetAssemblyName;
            }
 
            try
            {
                if (!VerifyProjectReferenceItems(ProjectReferences, false /* treat problems as warnings */))
                {
                    return false;
                }
 
                var resolvedPaths = new List<ITaskItem>(ProjectReferences.GetLength(0));
                var unresolvedReferences = new List<ITaskItem>(ProjectReferences.GetLength(0));
 
                CacheProjectElementsFromXml(PreresolvedProjectOutputs);
 
                foreach (ITaskItem projectRef in ProjectReferences)
                {
                    Log.LogMessageFromResources(MessageImportance.Low, "ResolveNonMSBuildProjectOutput.ProjectReferenceResolutionStarting", projectRef.ItemSpec);
 
                    bool resolveSuccess = ResolveProject(projectRef, out ITaskItem resolvedPath);
                    if (resolveSuccess)
                    {
                        if (resolvedPath.ItemSpec.Length > 0)
                        {
                            // VC project system does not look like an MSBuild project type yet because VC do not
                            // yet implement IVSProjectBuildSystem.  So project references to VC managed libraries
                            // need to be recognized as such.
                            // Even after VC implements this IVSProjectBuildSystem, other project types (NMake, NAnt, etc.)
                            // still can generate managed assemblies, in which case we still need to perform this managed
                            // assembly check.
                            try
                            {
                                GetAssemblyName(resolvedPath.ItemSpec);
                                resolvedPath.SetMetadata("ManagedAssembly", "true");
                            }
                            catch (BadImageFormatException)
                            {
                            }
 
                            resolvedPaths.Add(resolvedPath);
 
                            Log.LogMessageFromResources(MessageImportance.Low, "ResolveNonMSBuildProjectOutput.ProjectReferenceResolutionSuccess", projectRef.ItemSpec, resolvedPath.ItemSpec);
                        }
                        // If resolved path is empty, log a warning. This means that this reference is not an MSBuild reference,
                        // but could not be resolved in the IDE.
                        else
                        {
                            Log.LogWarningWithCodeFromResources("ResolveNonMSBuildProjectOutput.ProjectReferenceResolutionFailure", projectRef.ItemSpec);
                        }
                    }
                    else
                    {
                        unresolvedReferences.Add(projectRef);
 
                        // This is not an error - we pass unresolved references to UnresolvedProjectReferences for further
                        // processing in the .targets file.
                        Log.LogMessageFromResources(MessageImportance.Low, "ResolveNonMSBuildProjectOutput.ProjectReferenceUnresolved", projectRef.ItemSpec);
                    }
                }
 
                ResolvedOutputPaths = resolvedPaths.ToArray();
                UnresolvedProjectReferences = unresolvedReferences.ToArray();
            }
            catch (XmlException e)
            {
                Log.LogErrorWithCodeFromResources("General.ErrorExecutingTask", this.GetType().Name, e.Message);
                return false;
            }
 
            return true;
        }
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Given a project reference task item and an XML document containing pre-resolved output paths,
        /// find the output path for that task item.
        /// </summary>
        /// <param name="projectRef">project reference to resolve</param>
        /// <param name="resolvedPath">resulting ITaskItem containing the resolved path</param>
        /// <returns>true if resolved successfully</returns>
        internal bool ResolveProject(ITaskItem projectRef, out ITaskItem resolvedPath)
        {
            string projectOutputPath = GetProjectItem(projectRef);
            if (projectOutputPath != null)
            {
                resolvedPath = new TaskItem(projectOutputPath);
                projectRef.CopyMetadataTo(resolvedPath);
                return true;
            }
 
            resolvedPath = null;
            return false;
        }
 
        #endregion
    }
}