@@ -56,8 +56,8 @@ internal static class DotNetSdkLocationHelper
5656 // in the .NET 5 SDK rely on the .NET 5.0 runtime. Assuming the runtime that shipped with a particular SDK has the same version,
5757 // this ensures that we don't choose an SDK that doesn't work with the runtime of the chosen application. This is not guaranteed
5858 // to always work but should work for now.
59- if ( ! allowQueryAllRuntimeVersions &&
60- ( major > Environment . Version . Major ||
59+ if ( ! allowQueryAllRuntimeVersions &&
60+ ( major > Environment . Version . Major ||
6161 ( major == Environment . Version . Major && minor > Environment . Version . Minor ) ) )
6262 {
6363 return null ;
@@ -70,46 +70,104 @@ internal static class DotNetSdkLocationHelper
7070 discoveryType : DiscoveryType . DotNetSdk ) ;
7171 }
7272
73- public static IEnumerable < VisualStudioInstance > GetInstances ( string workingDirectory , bool allowQueryAllRuntimes )
74- {
75- foreach ( var basePath in GetDotNetBasePaths ( workingDirectory ) )
73+ public static IEnumerable < VisualStudioInstance > GetInstances ( string workingDirectory , bool allowQueryAllRuntimes , bool allowAllDotnetLocations )
74+ {
75+ string ? bestSdkPath ;
76+ string [ ] allAvailableSdks ;
77+ try
78+ {
79+ AddUnmanagedDllResolver ( ) ;
80+
81+ bestSdkPath = GetSdkFromGlobalSettings ( workingDirectory ) ;
82+ allAvailableSdks = GetAllAvailableSDKs ( allowAllDotnetLocations ) . ToArray ( ) ;
83+ }
84+ finally
85+ {
86+ RemoveUnmanagedDllResolver ( ) ;
87+ }
88+
89+ Dictionary < Version , VisualStudioInstance ? > versionInstanceMap = new ( ) ;
90+ foreach ( var basePath in allAvailableSdks )
7691 {
7792 var dotnetSdk = GetInstance ( basePath , allowQueryAllRuntimes ) ;
7893 if ( dotnetSdk != null )
7994 {
80- yield return dotnetSdk ;
95+ // We want to return the best SDK first
96+ if ( dotnetSdk . VisualStudioRootPath == bestSdkPath )
97+ {
98+ // We will add a null entry to the map to ensure we do not add the same SDK from a different location.
99+ versionInstanceMap [ dotnetSdk . Version ] = null ;
100+ yield return dotnetSdk ;
101+ }
102+
103+ // Only add an SDK once, even if it's installed in multiple locations.
104+ versionInstanceMap . TryAdd ( dotnetSdk . Version , dotnetSdk ) ;
81105 }
82106 }
83- }
84107
85- private static IEnumerable < string > GetDotNetBasePaths ( string workingDirectory )
86- {
87- try
108+ // We want to return the newest SDKs first. Using OfType will remove the null entry added if we found the best SDK.
109+ var instances = versionInstanceMap . Values . OfType < VisualStudioInstance > ( ) . OrderByDescending ( i => i . Version ) ;
110+ foreach ( var instance in instances )
88111 {
89- AddUnmanagedDllResolver ( ) ;
112+ yield return instance ;
113+ }
90114
91- string ? bestSDK = GetSdkFromGlobalSettings ( workingDirectory ) ;
92- if ( ! string . IsNullOrEmpty ( bestSDK ) )
115+ // Returns the list of all available SDKs ordered by ascending version.
116+ static IEnumerable < string > GetAllAvailableSDKs ( bool allowAllDotnetLocations )
117+ {
118+ bool foundSdks = false ;
119+ string [ ] ? resolvedPaths = null ;
120+ foreach ( string dotnetPath in s_dotnetPathCandidates . Value )
93121 {
94- yield return bestSDK ;
95- }
122+ int rc = NativeMethods . hostfxr_get_available_sdks ( exe_dir : dotnetPath , result : ( key , value ) => resolvedPaths = value ) ;
96123
97- string [ ] dotnetPaths = GetAllAvailableSDKs ( ) ;
98- // We want to return the newest SDKs first, however, so iterate over the list in reverse order.
99- // If basePath is disqualified because it was later
100- // than the runtime version, this ensures that RegisterDefaults will return the latest valid
101- // SDK instead of the earliest installed.
102- for ( int i = dotnetPaths . Length - 1 ; i >= 0 ; i -- )
103- {
104- if ( dotnetPaths [ i ] != bestSDK )
124+ if ( rc == 0 && resolvedPaths != null )
105125 {
106- yield return dotnetPaths [ i ] ;
126+ foundSdks = true ;
127+
128+ foreach ( string path in resolvedPaths )
129+ {
130+ yield return path ;
131+ }
132+
133+ if ( resolvedPaths . Length > 0 && ! allowAllDotnetLocations )
134+ {
135+ break ;
136+ }
107137 }
108138 }
139+
140+ // Errors are automatically printed to stderr. We should not continue to try to output anything if we failed.
141+ if ( ! foundSdks )
142+ {
143+ throw new InvalidOperationException ( SdkResolutionExceptionMessage ( nameof ( NativeMethods . hostfxr_get_available_sdks ) ) ) ;
144+ }
109145 }
110- finally
146+
147+ // Determines the directory location of the SDK accounting for global.json and multi-level lookup policy.
148+ static string ? GetSdkFromGlobalSettings ( string workingDirectory )
111149 {
112- RemoveUnmanagedDllResolver ( ) ;
150+ string ? resolvedSdk = null ;
151+ foreach ( string dotnetPath in s_dotnetPathCandidates . Value )
152+ {
153+ int rc = NativeMethods . hostfxr_resolve_sdk2 ( exe_dir : dotnetPath , working_dir : workingDirectory , flags : 0 , result : ( key , value ) =>
154+ {
155+ if ( key == NativeMethods . hostfxr_resolve_sdk2_result_key_t . resolved_sdk_dir )
156+ {
157+ resolvedSdk = value ;
158+ }
159+ } ) ;
160+
161+ if ( rc == 0 )
162+ {
163+ SetEnvironmentVariableIfEmpty ( "DOTNET_HOST_PATH" , Path . Combine ( dotnetPath , ExeName ) ) ;
164+ return resolvedSdk ;
165+ }
166+ }
167+
168+ return string . IsNullOrEmpty ( resolvedSdk )
169+ ? throw new InvalidOperationException ( SdkResolutionExceptionMessage ( nameof ( NativeMethods . hostfxr_resolve_sdk2 ) ) )
170+ : resolvedSdk ;
113171 }
114172 }
115173
@@ -158,7 +216,7 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)
158216 } ;
159217
160218 var orderedVersions = fileEnumerable . Where ( v => v != null ) . Select ( v => v ! ) . OrderByDescending ( f => f ) . ToList ( ) ;
161-
219+
162220 foreach ( SemanticVersion hostFxrVersion in orderedVersions )
163221 {
164222 string hostFxrAssembly = Path . Combine ( hostFxrRoot , hostFxrVersion . OriginalValue , hostFxrLibName ) ;
@@ -178,35 +236,6 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)
178236 }
179237
180238 private static string SdkResolutionExceptionMessage ( string methodName ) => $ "Failed to find all versions of .NET Core MSBuild. Call to { methodName } . There may be more details in stderr.";
181-
182- /// <summary>
183- /// Determines the directory location of the SDK accounting for
184- /// global.json and multi-level lookup policy.
185- /// </summary>
186- private static string ? GetSdkFromGlobalSettings ( string workingDirectory )
187- {
188- string ? resolvedSdk = null ;
189- foreach ( string dotnetPath in s_dotnetPathCandidates . Value )
190- {
191- int rc = NativeMethods . hostfxr_resolve_sdk2 ( exe_dir : dotnetPath , working_dir : workingDirectory , flags : 0 , result : ( key , value ) =>
192- {
193- if ( key == NativeMethods . hostfxr_resolve_sdk2_result_key_t . resolved_sdk_dir )
194- {
195- resolvedSdk = value ;
196- }
197- } ) ;
198-
199- if ( rc == 0 )
200- {
201- SetEnvironmentVariableIfEmpty ( "DOTNET_HOST_PATH" , Path . Combine ( dotnetPath , ExeName ) ) ;
202- return resolvedSdk ;
203- }
204- }
205-
206- return string . IsNullOrEmpty ( resolvedSdk )
207- ? throw new InvalidOperationException ( SdkResolutionExceptionMessage ( nameof ( NativeMethods . hostfxr_resolve_sdk2 ) ) )
208- : resolvedSdk ;
209- }
210239
211240 private static IList < string > ResolveDotnetPathCandidates ( )
212241 {
@@ -256,7 +285,7 @@ void AddIfValid(string? path)
256285 // 32-bit architecture has (x86) suffix
257286 string envVarName = ( IntPtr . Size == 4 ) ? "DOTNET_ROOT(x86)" : "DOTNET_ROOT" ;
258287 var dotnetPath = FindDotnetPathFromEnvVariable ( envVarName ) ;
259-
288+
260289 return dotnetPath ;
261290 }
262291
@@ -285,26 +314,6 @@ void AddIfValid(string? path)
285314 return dotnetPath ;
286315 }
287316
288- /// <summary>
289- /// Returns the list of all available SDKs ordered by ascending version.
290- /// </summary>
291- private static string [ ] GetAllAvailableSDKs ( )
292- {
293- string [ ] ? resolvedPaths = null ;
294- foreach ( string dotnetPath in s_dotnetPathCandidates . Value )
295- {
296- int rc = NativeMethods . hostfxr_get_available_sdks ( exe_dir : dotnetPath , result : ( key , value ) => resolvedPaths = value ) ;
297-
298- if ( rc == 0 && resolvedPaths != null && resolvedPaths . Length > 0 )
299- {
300- break ;
301- }
302- }
303-
304- // Errors are automatically printed to stderr. We should not continue to try to output anything if we failed.
305- return resolvedPaths ?? throw new InvalidOperationException ( SdkResolutionExceptionMessage ( nameof ( NativeMethods . hostfxr_get_available_sdks ) ) ) ;
306- }
307-
308317 /// <summary>
309318 /// This native method call determines the actual location of path, including
310319 /// resolving symbolic links.
@@ -321,7 +330,7 @@ private static string[] GetAllAvailableSDKs()
321330 private static string ? FindDotnetPathFromEnvVariable ( string environmentVariable )
322331 {
323332 string ? dotnetPath = Environment . GetEnvironmentVariable ( environmentVariable ) ;
324-
333+
325334 return string . IsNullOrEmpty ( dotnetPath ) ? null : ValidatePath ( dotnetPath ) ;
326335 }
327336
0 commit comments