File: SdkToolsPathUtility.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.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
 
#nullable disable
 
namespace Microsoft.Build.Tasks
{
    /// <summary>
    /// This class will provide the ability for classes given an SdkToolsPath and their tool name to find that tool.
    /// The tool will be looked for either under the SDKToolPath passed into the task or as fallback to look for the toolname using the toolslocation helper.
    /// </summary>
    internal static class SdkToolsPathUtility
    {
        /// <summary>
        /// Cache the file exists delegate which will determine if a file exists or not but will not eat the CAS exceptions.
        /// </summary>
        private static FileExists s_fileInfoExists;
 
        /// <summary>
        /// Provide a delegate which will do the correct file exists.
        /// </summary>
        internal static FileExists FileInfoExists
        {
            get
            {
                if (s_fileInfoExists == null)
                {
                    s_fileInfoExists = FileExists;
                }
 
                return s_fileInfoExists;
            }
        }
 
        /// <summary>
        /// This method will take a sdkToolsPath and a toolName and return the path to the tool if it is found and exists.
        ///
        /// First the method will try and find the tool under the sdkToolsPath taking into account the current processor architecture
        /// If the tool could not be found the method will try and find the tool under the sdkToolsPath (which should point to the x86 sdk directory).
        ///
        /// Finally if the method has not found the tool yet it will fallback and use the toolslocation helper method to try and find the tool.
        /// </summary>
        /// <returns>Path including the toolName of the tool if found, null if it is not found</returns>
        internal static string GeneratePathToTool(FileExists fileExists, string currentArchitecture, string sdkToolsPath, string toolName, TaskLoggingHelper log, bool logErrorsAndWarnings)
        {
            // Null until we combine the toolname with the path.
            string pathToTool = null;
            if (!String.IsNullOrEmpty(sdkToolsPath))
            {
                string processorSpecificToolDirectory;
                try
                {
                    processorSpecificToolDirectory = currentArchitecture switch
                    {
                        // There may not be an arm directory so we will fall back to the x86 tool location
                        // but if there is then we should try and use it.
                        ProcessorArchitecture.ARM => Path.Combine(sdkToolsPath, "arm"),
                        ProcessorArchitecture.AMD64 => Path.Combine(sdkToolsPath, "x64"),
                        ProcessorArchitecture.IA64 => Path.Combine(sdkToolsPath, "ia64"),
                        _ => sdkToolsPath,
                    };
                    pathToTool = Path.Combine(processorSpecificToolDirectory, toolName);
 
                    if (!fileExists(pathToTool))
                    {
                        // Try falling back to the x86 location
                        if (currentArchitecture != ProcessorArchitecture.X86)
                        {
                            pathToTool = Path.Combine(sdkToolsPath, toolName);
                        }
                    }
                    else
                    {
                        return pathToTool;
                    }
                }
                catch (ArgumentException e)
                {
                    // Catch exceptions from path.combine
                    log.LogErrorWithCodeFromResources("General.SdkToolsPathError", toolName, e.Message);
                    return null;
                }
 
                if (fileExists(pathToTool))
                {
                    return pathToTool;
                }
                else
                {
                    if (logErrorsAndWarnings)
                    {
                        // Log an error indicating we could not find it in the processor specific architecture or x86 locations.
                        // We could not find the tool at all, lot a error.
                        log.LogWarningWithCodeFromResources("General.PlatformSDKFileNotFoundSdkToolsPath", toolName, processorSpecificToolDirectory, sdkToolsPath);
                    }
                }
            }
            else
            {
                if (logErrorsAndWarnings)
                {
                    log.LogMessageFromResources(MessageImportance.Low, "General.SdkToolsPathNotSpecifiedOrToolDoesNotExist", toolName, sdkToolsPath);
                }
            }
 
            // Fall back and see if we can find it with the toolsLocation helper methods. This is not optimal because
            // the location they are looking at is based on when the Microsoft.Build.Utilities.dll was compiled
            // but it is better than nothing.
            if (pathToTool == null || !fileExists(pathToTool))
            {
                pathToTool = FindSDKToolUsingToolsLocationHelper(toolName);
 
                if (pathToTool == null && logErrorsAndWarnings)
                {
                    log.LogErrorWithCodeFromResources("General.SdkToolsPathToolDoesNotExist", toolName, sdkToolsPath, ToolLocationHelper.GetDotNetFrameworkSdkRootRegistryKey(TargetDotNetFrameworkVersion.Latest, VisualStudioVersion.VersionLatest));
                }
            }
 
            return pathToTool;
        }
 
        /// <summary>
        /// This method will take the toolName and use the Legacy ToolLocation helper methods to try and find the tool.
        /// This is a last ditch effort to find the tool when we cannot find it using the passed in SDKToolsPath (in either the x86 or processor specific directories).
        /// </summary>
        /// <param name="toolName">Name of the tool to find the sdk path for</param>
        /// <returns>A path to the tool or null if the path does not exist.</returns>
        internal static string FindSDKToolUsingToolsLocationHelper(string toolName)
        {
            // If it isn't there, we should find it in the SDK based on the version compiled into the utilities
            string pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkSdkFile(toolName, TargetDotNetFrameworkVersion.Latest, VisualStudioVersion.VersionLatest);
            return pathToTool;
        }
 
        /// <summary>
        /// Provide a method which can be used with a delegate to provide a specific FileExists behavior.
        ///
        /// Use FileInfo instead of FileSystems.Default.FileExists(...) because the latter fails silently (by design) if CAS
        /// doesn't grant access. We want the security exception if there is going to be one.
        /// </summary>
        /// <returns>True if the file exists. False if it does not</returns>
        private static bool FileExists(string filePath)
        {
            return new FileInfo(filePath).Exists;
        }
    }
}