@@ -56,8 +56,8 @@ internal static class DotNetSdkLocationHelper
56
56
// 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,
57
57
// this ensures that we don't choose an SDK that doesn't work with the runtime of the chosen application. This is not guaranteed
58
58
// to always work but should work for now.
59
- if ( ! allowQueryAllRuntimeVersions &&
60
- ( major > Environment . Version . Major ||
59
+ if ( ! allowQueryAllRuntimeVersions &&
60
+ ( major > Environment . Version . Major ||
61
61
( major == Environment . Version . Major && minor > Environment . Version . Minor ) ) )
62
62
{
63
63
return null ;
@@ -70,46 +70,104 @@ internal static class DotNetSdkLocationHelper
70
70
discoveryType : DiscoveryType . DotNetSdk ) ;
71
71
}
72
72
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 )
76
91
{
77
92
var dotnetSdk = GetInstance ( basePath , allowQueryAllRuntimes ) ;
78
93
if ( dotnetSdk != null )
79
94
{
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 ) ;
81
105
}
82
106
}
83
- }
84
107
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 )
88
111
{
89
- AddUnmanagedDllResolver ( ) ;
112
+ yield return instance ;
113
+ }
90
114
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 )
93
121
{
94
- yield return bestSDK ;
95
- }
122
+ int rc = NativeMethods . hostfxr_get_available_sdks ( exe_dir : dotnetPath , result : ( key , value ) => resolvedPaths = value ) ;
96
123
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 )
105
125
{
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
+ }
107
137
}
108
138
}
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
+ }
109
145
}
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 )
111
149
{
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 ;
113
171
}
114
172
}
115
173
@@ -158,7 +216,7 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)
158
216
} ;
159
217
160
218
var orderedVersions = fileEnumerable . Where ( v => v != null ) . Select ( v => v ! ) . OrderByDescending ( f => f ) . ToList ( ) ;
161
-
219
+
162
220
foreach ( SemanticVersion hostFxrVersion in orderedVersions )
163
221
{
164
222
string hostFxrAssembly = Path . Combine ( hostFxrRoot , hostFxrVersion . OriginalValue , hostFxrLibName ) ;
@@ -178,35 +236,6 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)
178
236
}
179
237
180
238
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
- }
210
239
211
240
private static IList < string > ResolveDotnetPathCandidates ( )
212
241
{
@@ -256,7 +285,7 @@ void AddIfValid(string? path)
256
285
// 32-bit architecture has (x86) suffix
257
286
string envVarName = ( IntPtr . Size == 4 ) ? "DOTNET_ROOT(x86)" : "DOTNET_ROOT" ;
258
287
var dotnetPath = FindDotnetPathFromEnvVariable ( envVarName ) ;
259
-
288
+
260
289
return dotnetPath ;
261
290
}
262
291
@@ -285,26 +314,6 @@ void AddIfValid(string? path)
285
314
return dotnetPath ;
286
315
}
287
316
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
-
308
317
/// <summary>
309
318
/// This native method call determines the actual location of path, including
310
319
/// resolving symbolic links.
@@ -321,7 +330,7 @@ private static string[] GetAllAvailableSDKs()
321
330
private static string ? FindDotnetPathFromEnvVariable ( string environmentVariable )
322
331
{
323
332
string ? dotnetPath = Environment . GetEnvironmentVariable ( environmentVariable ) ;
324
-
333
+
325
334
return string . IsNullOrEmpty ( dotnetPath ) ? null : ValidatePath ( dotnetPath ) ;
326
335
}
327
336
0 commit comments