@@ -32,11 +32,46 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver
3232 private readonly Func < string , string , string ? > _getMsbuildRuntime ;
3333 private readonly NETCoreSdkResolver _netCoreSdkResolver ;
3434
35+ private const string DOTNET_HOST = nameof ( DOTNET_HOST ) ;
3536 private const string DotnetHostExperimentalKey = "DOTNET_EXPERIMENTAL_HOST_PATH" ;
3637 private const string MSBuildTaskHostRuntimeVersion = "SdkResolverMSBuildTaskHostRuntimeVersion" ;
37-
38+ private const string SdkResolverHonoredGlobalJson = "SdkResolverHonoredGlobalJson" ;
39+ private const string SdkResolverGlobalJsonPath = "SdkResolverGlobalJsonPath" ;
3840 private static CachingWorkloadResolver _staticWorkloadResolver = new ( ) ;
3941
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+
4075 private bool _shouldLog = false ;
4176
4277 public DotNetMSBuildSdkResolver ( )
@@ -68,6 +103,7 @@ private sealed class CachedState
68103 public string ? GlobalJsonPath ;
69104 public IDictionary < string , string ? > ? PropertiesToAdd ;
70105 public CachingWorkloadResolver ? WorkloadResolver ;
106+ public IDictionary < string , string ? > ? EnvironmentVariablesToAdd ;
71107 }
72108
73109 public override SdkResult ? Resolve ( SdkReference sdkReference , SdkResolverContext context , SdkResultFactory factory )
@@ -78,6 +114,7 @@ private sealed class CachedState
78114 string ? globalJsonPath = null ;
79115 IDictionary < string , string ? > ? propertiesToAdd = null ;
80116 IDictionary < string , SdkResultItem > ? itemsToAdd = null ;
117+ IDictionary < string , string ? > ? environmentVariablesToAdd = null ;
81118 List < string > ? warnings = null ;
82119 CachingWorkloadResolver ? workloadResolver = null ;
83120
@@ -99,6 +136,7 @@ private sealed class CachedState
99136 globalJsonPath = priorResult . GlobalJsonPath ;
100137 propertiesToAdd = priorResult . PropertiesToAdd ;
101138 workloadResolver = priorResult . WorkloadResolver ;
139+ environmentVariablesToAdd = priorResult . EnvironmentVariablesToAdd ;
102140
103141 logger ? . LogMessage ( $ "\t Dotnet root: { dotnetRoot } ") ;
104142 logger ? . LogMessage ( $ "\t MSBuild SDKs Dir: { msbuildSdksDir } ") ;
@@ -139,9 +177,9 @@ private sealed class CachedState
139177 logger ? . LogMessage ( $ "\t Resolved SDK directory: { resolverResult . ResolvedSdkDirectory } ") ;
140178 logger ? . LogMessage ( $ "\t global.json path: { resolverResult . GlobalJsonPath } ") ;
141179 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 ;
145183 globalJsonPath = resolverResult . GlobalJsonPath ;
146184
147185 // 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
197235 }
198236
199237 string ? fullPathToMuxer =
200- TryResolveMuxerFromSdkResolution ( resolverResult )
238+ TryResolveMuxerFromSdkResolution ( dotnetSdkDir )
201239 ?? Path . Combine ( dotnetRoot , RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? Constants . DotNetExe : Constants . DotNet ) ;
202240 if ( File . Exists ( fullPathToMuxer ) )
203241 {
242+ // keeping this in until this component no longer needs to handle 17.14.
204243 propertiesToAdd ??= new Dictionary < string , string ? > ( ) ;
205244 propertiesToAdd . Add ( DotnetHostExperimentalKey , fullPathToMuxer ) ;
245+ // this is the future-facing implementation.
246+ environmentVariablesToAdd ??= new Dictionary < string , string ? > ( 1 )
247+ {
248+ [ DOTNET_HOST ] = fullPathToMuxer
249+ } ;
206250 }
207251 else
208252 {
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.") ;
210254 }
211255
212256 string ? runtimeVersion = dotnetRoot != null ?
213- _getMsbuildRuntime ( resolverResult . ResolvedSdkDirectory , dotnetRoot ) :
257+ _getMsbuildRuntime ( dotnetSdkDir , dotnetRoot ) :
214258 null ;
215259 if ( ! string . IsNullOrEmpty ( runtimeVersion ) )
216260 {
@@ -224,7 +268,7 @@ private sealed class CachedState
224268
225269 if ( resolverResult . FailedToResolveSDKSpecifiedInGlobalJson )
226270 {
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.") ;
228272
229273 if ( warnings == null )
230274 {
@@ -241,8 +285,9 @@ private sealed class CachedState
241285 }
242286
243287 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 ) ;
246291
247292 if ( logger != null )
248293 {
@@ -258,7 +303,8 @@ private sealed class CachedState
258303 NETCoreSdkVersion = netcoreSdkVersion ,
259304 GlobalJsonPath = globalJsonPath ,
260305 PropertiesToAdd = propertiesToAdd ,
261- WorkloadResolver = workloadResolver
306+ WorkloadResolver = workloadResolver ,
307+ EnvironmentVariablesToAdd = environmentVariablesToAdd
262308 } ;
263309
264310 // First check if requested SDK resolves to a workload SDK pack
@@ -284,8 +330,14 @@ private sealed class CachedState
284330 Strings . MSBuildSDKDirectoryNotFound ,
285331 msbuildSdkDir ) ;
286332 }
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+ }
289341 }
290342
291343 /// <summary>
@@ -295,10 +347,10 @@ private sealed class CachedState
295347 /// SDK layouts always have a defined relationship to the location of the muxer -
296348 /// the muxer binary should be exactly two directories above the SDK directory.
297349 /// </remarks>
298- private static string ? TryResolveMuxerFromSdkResolution ( SdkResolutionResult resolverResult )
350+ private static string ? TryResolveMuxerFromSdkResolution ( string resolvedSdkDirectory )
299351 {
300352 var expectedFileName = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? Constants . DotNetExe : Constants . DotNet ;
301- var currentDir = resolverResult . ResolvedSdkDirectory ;
353+ var currentDir = resolvedSdkDirectory ;
302354 var expectedDotnetRoot = Path . GetDirectoryName ( Path . GetDirectoryName ( currentDir ) ) ;
303355 var expectedMuxerPath = Path . Combine ( expectedDotnetRoot , expectedFileName ) ;
304356 if ( File . Exists ( expectedMuxerPath ) )
0 commit comments