1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
4 // </copyright>
5 // <summary>Provide a helper class for tasks to find their tools if they are in the SDK</summary>
6 //-----------------------------------------------------------------------
7 
8 using System;
9 using System.Collections.Generic;
10 using System.Text;
11 using System.IO;
12 using Microsoft.Build.Utilities;
13 using Microsoft.Build.Shared;
14 using Microsoft.Build.Framework;
15 
16 namespace Microsoft.Build.Tasks
17 {
18     /// <summary>
19     /// This class will provide the ability for classes given an SdkToolsPath and their tool name to find that tool.
20     /// 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.
21     /// </summary>
22     internal static class SdkToolsPathUtility
23     {
24         /// <summary>
25         /// Cache the file exists delegate which will determine if a file exists or not but will not eat the CAS exceptions.
26         /// </summary>
27         private static FileExists s_fileInfoExists;
28 
29         /// <summary>
30         /// Provide a delegate which will do the correct file exists.
31         /// </summary>
32         internal static FileExists FileInfoExists
33         {
34             get
35             {
36                 if (s_fileInfoExists == null)
37                 {
38                     s_fileInfoExists = new FileExists(FileExists);
39                 }
40 
41                 return s_fileInfoExists;
42             }
43         }
44 
45         /// <summary>
46         /// This method will take a sdkToolsPath and a toolName and return the path to the tool if it is found and exists.
47         ///
48         /// First the method will try and find the tool under the sdkToolsPath taking into account the current processor architecture
49         /// 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).
50         ///
51         /// 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.
52         /// </summary>
53         /// <returns>Path including the toolName of the tool if found, null if it is not found</returns>
GeneratePathToTool(FileExists fileExists, string currentArchitecture, string sdkToolsPath, string toolName, TaskLoggingHelper log, bool logErrorsAndWarnings)54         internal static string GeneratePathToTool(FileExists fileExists, string currentArchitecture, string sdkToolsPath, string toolName, TaskLoggingHelper log, bool logErrorsAndWarnings)
55         {
56             // Null until we combine the toolname with the path.
57             string pathToTool = null;
58             if (!String.IsNullOrEmpty(sdkToolsPath))
59             {
60                 string processorSpecificToolDirectory = String.Empty;
61                 try
62                 {
63                     switch (currentArchitecture)
64                     {
65                         // There may not be an arm directory so we will fall back to the x86 tool location
66                         // but if there is then we should try and use it.
67                         case ProcessorArchitecture.ARM:
68                             processorSpecificToolDirectory = Path.Combine(sdkToolsPath, "arm");
69                             break;
70                         case ProcessorArchitecture.AMD64:
71                             processorSpecificToolDirectory = Path.Combine(sdkToolsPath, "x64");
72                             break;
73                         case ProcessorArchitecture.IA64:
74                             processorSpecificToolDirectory = Path.Combine(sdkToolsPath, "ia64");
75                             break;
76                         case ProcessorArchitecture.X86:
77                         default:
78                             processorSpecificToolDirectory = sdkToolsPath;
79                             break;
80                     }
81 
82                     pathToTool = Path.Combine(processorSpecificToolDirectory, toolName);
83 
84                     if (!fileExists(pathToTool))
85                     {
86                         // Try falling back to the x86 location
87                         if (currentArchitecture != ProcessorArchitecture.X86)
88                         {
89                             pathToTool = Path.Combine(sdkToolsPath, toolName);
90                         }
91                     }
92                     else
93                     {
94                         return pathToTool;
95                     }
96                 }
97                 catch (ArgumentException e)
98                 {
99                     // Catch exceptions from path.combine
100                     log.LogErrorWithCodeFromResources("General.SdkToolsPathError", toolName, e.Message);
101                     return null;
102                 }
103 
104                 if (fileExists(pathToTool))
105                 {
106                     return pathToTool;
107                 }
108                 else
109                 {
110                     if (logErrorsAndWarnings)
111                     {
112                         // Log an error indicating we could not find it in the processor specific architecture or x86 locations.
113                         // We could not find the tool at all, lot a error.
114                         log.LogWarningWithCodeFromResources("General.PlatformSDKFileNotFoundSdkToolsPath", toolName, processorSpecificToolDirectory, sdkToolsPath);
115                     }
116                 }
117             }
118             else
119             {
120                 if (logErrorsAndWarnings)
121                 {
122                     log.LogMessageFromResources(MessageImportance.Low, "General.SdkToolsPathNotSpecifiedOrToolDoesNotExist", toolName, sdkToolsPath);
123                 }
124             }
125 
126             // Fall back and see if we can find it with the toolsLocation helper methods. This is not optimal because
127             // the location they are looking at is based on when the Microsoft.Build.Utilities.dll was compiled
128             // but it is better than nothing.
129             if (null == pathToTool || !fileExists(pathToTool))
130             {
131                 pathToTool = FindSDKToolUsingToolsLocationHelper(toolName);
132 
133                 if (pathToTool == null && logErrorsAndWarnings)
134                 {
135                     log.LogErrorWithCodeFromResources("General.SdkToolsPathToolDoesNotExist", toolName, sdkToolsPath, ToolLocationHelper.GetDotNetFrameworkSdkRootRegistryKey(TargetDotNetFrameworkVersion.Latest, VisualStudioVersion.VersionLatest));
136                 }
137             }
138 
139             return pathToTool;
140         }
141 
142         /// <summary>
143         /// This method will take the toolName and use the Legacy ToolLocation helper methods to try and find the tool.
144         /// 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).
145         /// </summary>
146         /// <param name="toolName">Name of the tool to find the sdk path for</param>
147         /// <returns>A path to the tool or null if the path does not exist.</returns>
FindSDKToolUsingToolsLocationHelper(string toolName)148         internal static string FindSDKToolUsingToolsLocationHelper(string toolName)
149         {
150             // If it isn't there, we should find it in the SDK based on the version compiled into the utilities
151             string pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkSdkFile(toolName, TargetDotNetFrameworkVersion.Latest, VisualStudioVersion.VersionLatest);
152             return pathToTool;
153         }
154 
155         /// <summary>
156         /// Provide a method which can be used with a delegate to provide a specific FileExists behavior.
157         ///
158         /// Use FileInfo instead of File.Exists(...) because the latter fails silently (by design) if CAS
159         /// doesn't grant access. We want the security exception if there is going to be one.
160         /// </summary>
161         /// <returns>True if the file exists. False if it does not</returns>
FileExists(string filePath)162         private static bool FileExists(string filePath)
163         {
164             return new FileInfo(filePath).Exists;
165         }
166     }
167 }
168