@@ -32,11 +32,46 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver
32
32
private readonly Func < string , string , string ? > _getMsbuildRuntime ;
33
33
private readonly NETCoreSdkResolver _netCoreSdkResolver ;
34
34
35
+ private const string DOTNET_HOST = nameof ( DOTNET_HOST ) ;
35
36
private const string DotnetHostExperimentalKey = "DOTNET_EXPERIMENTAL_HOST_PATH" ;
36
37
private const string MSBuildTaskHostRuntimeVersion = "SdkResolverMSBuildTaskHostRuntimeVersion" ;
37
-
38
+ private const string SdkResolverHonoredGlobalJson = "SdkResolverHonoredGlobalJson" ;
39
+ private const string SdkResolverGlobalJsonPath = "SdkResolverGlobalJsonPath" ;
38
40
private static CachingWorkloadResolver _staticWorkloadResolver = new ( ) ;
39
41
42
+ /// <summary>
43
+ /// This is a workaround for MSBuild API compatibility in older VS hosts.
44
+ /// To allow for working with the MSBuild 17.15 APIs while not blowing up our ability to build the SdkResolver in
45
+ /// VS-driven CI pipelines, we probe and invoke only if the expected method is present.
46
+ /// Once we can update our MSBuild API dependency this can go away.
47
+ /// </summary>
48
+ private static UpdatedSdkResultFactorySuccess ? _factorySuccessFunc = TryLocateNewMSBuildFactory ( ) ;
49
+
50
+ /// <summary>
51
+ /// This represents the 'open delegate' form of the updated SdkResultFactory.IndicateSuccess method with environment variable support.
52
+ /// Because it is an open delegate, we can provide an object instance to be called as the first argument.
53
+ /// </summary>
54
+ public delegate SdkResult UpdatedSdkResultFactorySuccess ( SdkResultFactory factory , string sdkPath , string ? sdkVersion , IDictionary < string , string ? > ? propertiesToAdd , IDictionary < string , SdkResultItem > ? itemsToAdd , List < string > ? warnings , IDictionary < string , string ? > ? environmentVariablesToAdd ) ;
55
+
56
+ private static UpdatedSdkResultFactorySuccess ? TryLocateNewMSBuildFactory ( )
57
+ {
58
+ if ( typeof ( SdkResultFactory ) . GetMethod ( "IndicateSuccess" , [
59
+ typeof ( string ) , // path to sdk
60
+ typeof ( string ) , // sdk version
61
+ typeof ( IDictionary < string , string > ) , // properties to add
62
+ typeof ( IDictionary < string , SdkResultItem > ) , // items to add
63
+ typeof ( List < string > ) , // warnings
64
+ typeof ( IDictionary < string , string > ) // environment variables to add
65
+ ] ) is MethodInfo m )
66
+ {
67
+ return Delegate . CreateDelegate (
68
+ typeof ( Func < SdkResultFactory , string , string ? , IDictionary < string , string ? > ? , IDictionary < string , SdkResultItem > ? , List < string > ? , IDictionary < string , string ? > ? , SdkResult > ) ,
69
+ null ,
70
+ m ) as UpdatedSdkResultFactorySuccess ;
71
+ }
72
+ return null ;
73
+ }
74
+
40
75
private bool _shouldLog = false ;
41
76
42
77
public DotNetMSBuildSdkResolver ( )
@@ -68,6 +103,7 @@ private sealed class CachedState
68
103
public string ? GlobalJsonPath ;
69
104
public IDictionary < string , string ? > ? PropertiesToAdd ;
70
105
public CachingWorkloadResolver ? WorkloadResolver ;
106
+ public IDictionary < string , string ? > ? EnvironmentVariablesToAdd ;
71
107
}
72
108
73
109
public override SdkResult ? Resolve ( SdkReference sdkReference , SdkResolverContext context , SdkResultFactory factory )
@@ -78,6 +114,7 @@ private sealed class CachedState
78
114
string ? globalJsonPath = null ;
79
115
IDictionary < string , string ? > ? propertiesToAdd = null ;
80
116
IDictionary < string , SdkResultItem > ? itemsToAdd = null ;
117
+ IDictionary < string , string ? > ? environmentVariablesToAdd = null ;
81
118
List < string > ? warnings = null ;
82
119
CachingWorkloadResolver ? workloadResolver = null ;
83
120
@@ -99,6 +136,7 @@ private sealed class CachedState
99
136
globalJsonPath = priorResult . GlobalJsonPath ;
100
137
propertiesToAdd = priorResult . PropertiesToAdd ;
101
138
workloadResolver = priorResult . WorkloadResolver ;
139
+ environmentVariablesToAdd = priorResult . EnvironmentVariablesToAdd ;
102
140
103
141
logger ? . LogMessage ( $ "\t Dotnet root: { dotnetRoot } ") ;
104
142
logger ? . LogMessage ( $ "\t MSBuild SDKs Dir: { msbuildSdksDir } ") ;
@@ -139,9 +177,9 @@ private sealed class CachedState
139
177
logger ? . LogMessage ( $ "\t Resolved SDK directory: { resolverResult . ResolvedSdkDirectory } ") ;
140
178
logger ? . LogMessage ( $ "\t global.json path: { resolverResult . GlobalJsonPath } ") ;
141
179
logger ? . LogMessage ( $ "\t Failed to resolve SDK from global.json: { resolverResult . FailedToResolveSDKSpecifiedInGlobalJson } ") ;
142
-
143
- msbuildSdksDir = Path . Combine ( resolverResult . ResolvedSdkDirectory , "Sdks" ) ;
144
- netcoreSdkVersion = new DirectoryInfo ( resolverResult . ResolvedSdkDirectory ) . Name ;
180
+ string dotnetSdkDir = resolverResult . ResolvedSdkDirectory ;
181
+ msbuildSdksDir = Path . Combine ( dotnetSdkDir , "Sdks" ) ;
182
+ netcoreSdkVersion = new DirectoryInfo ( dotnetSdkDir ) . Name ;
145
183
globalJsonPath = resolverResult . GlobalJsonPath ;
146
184
147
185
// These are overrides that are used to force the resolved SDK tasks and targets to come from a given
@@ -197,20 +235,26 @@ private sealed class CachedState
197
235
}
198
236
199
237
string ? fullPathToMuxer =
200
- TryResolveMuxerFromSdkResolution ( resolverResult )
238
+ TryResolveMuxerFromSdkResolution ( dotnetSdkDir )
201
239
?? Path . Combine ( dotnetRoot , RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? Constants . DotNetExe : Constants . DotNet ) ;
202
240
if ( File . Exists ( fullPathToMuxer ) )
203
241
{
242
+ // keeping this in until this component no longer needs to handle 17.14.
204
243
propertiesToAdd ??= new Dictionary < string , string ? > ( ) ;
205
244
propertiesToAdd . Add ( DotnetHostExperimentalKey , fullPathToMuxer ) ;
245
+ // this is the future-facing implementation.
246
+ environmentVariablesToAdd ??= new Dictionary < string , string ? > ( 1 )
247
+ {
248
+ [ DOTNET_HOST ] = fullPathToMuxer
249
+ } ;
206
250
}
207
251
else
208
252
{
209
- logger ? . LogMessage ( $ "Could not set '{ DotnetHostExperimentalKey } ' because dotnet executable '{ fullPathToMuxer } ' does not exist.") ;
253
+ logger ? . LogMessage ( $ "Could not set '{ DOTNET_HOST } ' environment variable because dotnet executable '{ fullPathToMuxer } ' does not exist.") ;
210
254
}
211
255
212
256
string ? runtimeVersion = dotnetRoot != null ?
213
- _getMsbuildRuntime ( resolverResult . ResolvedSdkDirectory , dotnetRoot ) :
257
+ _getMsbuildRuntime ( dotnetSdkDir , dotnetRoot ) :
214
258
null ;
215
259
if ( ! string . IsNullOrEmpty ( runtimeVersion ) )
216
260
{
@@ -224,7 +268,7 @@ private sealed class CachedState
224
268
225
269
if ( resolverResult . FailedToResolveSDKSpecifiedInGlobalJson )
226
270
{
227
- logger ? . LogMessage ( $ "Could not resolve SDK specified in '{ resolverResult . GlobalJsonPath } '. Ignoring global.json for this resolution.") ;
271
+ logger ? . LogMessage ( $ "Could not resolve SDK specified in '{ globalJsonPath } '. Ignoring global.json for this resolution.") ;
228
272
229
273
if ( warnings == null )
230
274
{
@@ -241,8 +285,9 @@ private sealed class CachedState
241
285
}
242
286
243
287
propertiesToAdd ??= new Dictionary < string , string ? > ( ) ;
244
- propertiesToAdd . Add ( "SdkResolverHonoredGlobalJson" , "false" ) ;
245
- propertiesToAdd . Add ( "SdkResolverGlobalJsonPath" , resolverResult . GlobalJsonPath ) ;
288
+ propertiesToAdd . Add ( SdkResolverHonoredGlobalJson , "false" ) ;
289
+ // TODO: this would ideally be reported anytime it was non-null - that may cause more imports though?
290
+ propertiesToAdd . Add ( SdkResolverGlobalJsonPath , globalJsonPath ) ;
246
291
247
292
if ( logger != null )
248
293
{
@@ -258,7 +303,8 @@ private sealed class CachedState
258
303
NETCoreSdkVersion = netcoreSdkVersion ,
259
304
GlobalJsonPath = globalJsonPath ,
260
305
PropertiesToAdd = propertiesToAdd ,
261
- WorkloadResolver = workloadResolver
306
+ WorkloadResolver = workloadResolver ,
307
+ EnvironmentVariablesToAdd = environmentVariablesToAdd
262
308
} ;
263
309
264
310
// First check if requested SDK resolves to a workload SDK pack
@@ -284,8 +330,14 @@ private sealed class CachedState
284
330
Strings . MSBuildSDKDirectoryNotFound ,
285
331
msbuildSdkDir ) ;
286
332
}
287
-
288
- return factory . IndicateSuccess ( msbuildSdkDir , netcoreSdkVersion , propertiesToAdd , itemsToAdd , warnings ) ;
333
+ if ( _factorySuccessFunc != null )
334
+ {
335
+ return _factorySuccessFunc ( factory , msbuildSdkDir , netcoreSdkVersion , propertiesToAdd , itemsToAdd , warnings , environmentVariablesToAdd ) ;
336
+ }
337
+ else
338
+ {
339
+ return factory . IndicateSuccess ( msbuildSdkDir , netcoreSdkVersion , propertiesToAdd , itemsToAdd , warnings ) ;
340
+ }
289
341
}
290
342
291
343
/// <summary>
@@ -295,10 +347,10 @@ private sealed class CachedState
295
347
/// SDK layouts always have a defined relationship to the location of the muxer -
296
348
/// the muxer binary should be exactly two directories above the SDK directory.
297
349
/// </remarks>
298
- private static string ? TryResolveMuxerFromSdkResolution ( SdkResolutionResult resolverResult )
350
+ private static string ? TryResolveMuxerFromSdkResolution ( string resolvedSdkDirectory )
299
351
{
300
352
var expectedFileName = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? Constants . DotNetExe : Constants . DotNet ;
301
- var currentDir = resolverResult . ResolvedSdkDirectory ;
353
+ var currentDir = resolvedSdkDirectory ;
302
354
var expectedDotnetRoot = Path . GetDirectoryName ( Path . GetDirectoryName ( currentDir ) ) ;
303
355
var expectedMuxerPath = Path . Combine ( expectedDotnetRoot , expectedFileName ) ;
304
356
if ( File . Exists ( expectedMuxerPath ) )
0 commit comments