From 47acf598fd8ba4dc6d1597defd0a2c06cc9b4db2 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sat, 19 Jul 2025 10:24:59 -0500 Subject: [PATCH 1/7] use TL summary all the time beacuse we have a big build --- build.cmd | 4 +--- build.sh | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/build.cmd b/build.cmd index d86f2fb8b5cb..3d0115980b65 100644 --- a/build.cmd +++ b/build.cmd @@ -10,7 +10,5 @@ if %errorlevel%==0 ( set skipFlags="/p:SkipUsingCrossgen=true /p:SkipBuildingInstallers=true" ) set DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT=true -powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -command "& """%~dp0eng\common\build.ps1""" -restore -build -msbuildEngine dotnet %skipFlags% %*" - -endlocal +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -command "& """%~dp0eng\common\build.ps1""" -restore -build -msbuildEngine dotnet %skipFlags% /tlp:summary %*" exit /b %ErrorLevel% diff --git a/build.sh b/build.sh index 96189c91eadb..49b1e0e55773 100755 --- a/build.sh +++ b/build.sh @@ -14,4 +14,4 @@ if [[ "$@" != *"-pack"* ]]; then fi export DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT="true" -. "$ScriptRoot/eng/common/build.sh" --build --restore $skipFlags "$@" +. "$ScriptRoot/eng/common/build.sh" --build --restore $skipFlags /tlp:summary "$@" From 61825e3041a43e7b057b8fd741d7383762fb3494 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sat, 19 Jul 2025 10:25:39 -0500 Subject: [PATCH 2/7] bump MSBuild API versions and dependencies, hack through VS-env validations, report all VS-env validation errors in one batch --- eng/Versions.props | 15 ++++++----- ...Microsoft.DotNet.MSBuildSdkResolver.csproj | 27 ++++++++++--------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index acbfa1cdbc18..dcef755c7200 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -132,16 +132,16 @@ 10.0.0-preview.7.25377.103 - 8.0.0 + 9.0.0 2.0.0-preview.1.24427.4 4.5.1 - 8.0.0 + 9.0.0 4.5.5 - 8.0.0 - 8.0.0 - 8.0.5 + 9.0.0 + 9.0.0 + 9.0.0 4.5.4 - 8.0.0 + 9.0.0 @@ -189,7 +189,8 @@ Additionally, set the MinimumVSVersion for the installer UI that's required for targeting NetCurrent --> 17.15.0-preview-25377-103 17.15.0-preview-25377-103 - 17.11.4 + + $(MicrosoftBuildVersion) 17.13 diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj index 58403f591e60..f214ae97c297 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj @@ -105,18 +105,19 @@ - - - + - - - - - - + + + + + + + + + + - @@ -127,9 +128,11 @@ + Condition="!($([System.String]::Copy('$(ResolvedDependenciesList)').Contains('%(ExpectedDependencies.Identity)')))" + ContinueOnError="true" /> + Condition="!($([System.String]::Copy('$(ExpectedDependenciesList)').Contains('%(ResolvedDependencies.FusionName)')))" + ContinueOnError="true" /> From ea55de9edd81639ba874b714c0a96e59eab98148 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sat, 19 Jul 2025 10:26:03 -0500 Subject: [PATCH 3/7] set env vars from resolver APIs --- .../MSBuildSdkResolver.cs | 41 +++++++++++-------- .../GivenAnMSBuildSdkResolver.cs | 33 ++++++++++----- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs index 5bf7a44cb716..8a0c1f559650 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs @@ -32,9 +32,11 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver private readonly Func _getMsbuildRuntime; private readonly NETCoreSdkResolver _netCoreSdkResolver; + private const string DOTNET_HOST = nameof(DOTNET_HOST); private const string DotnetHostExperimentalKey = "DOTNET_EXPERIMENTAL_HOST_PATH"; private const string MSBuildTaskHostRuntimeVersion = "SdkResolverMSBuildTaskHostRuntimeVersion"; - + private const string SdkResolverHonoredGlobalJson = "SdkResolverHonoredGlobalJson"; + private const string SdkResolverGlobalJsonPath = "SdkResolverGlobalJsonPath"; private static CachingWorkloadResolver _staticWorkloadResolver = new(); private bool _shouldLog = false; @@ -68,6 +70,7 @@ private sealed class CachedState public string? GlobalJsonPath; public IDictionary? PropertiesToAdd; public CachingWorkloadResolver? WorkloadResolver; + public IDictionary? EnvironmentVariablesToAdd; } public override SdkResult? Resolve(SdkReference sdkReference, SdkResolverContext context, SdkResultFactory factory) @@ -78,6 +81,7 @@ private sealed class CachedState string? globalJsonPath = null; IDictionary? propertiesToAdd = null; IDictionary? itemsToAdd = null; + IDictionary? environmentVariablesToAdd = null; List? warnings = null; CachingWorkloadResolver? workloadResolver = null; @@ -99,6 +103,7 @@ private sealed class CachedState globalJsonPath = priorResult.GlobalJsonPath; propertiesToAdd = priorResult.PropertiesToAdd; workloadResolver = priorResult.WorkloadResolver; + environmentVariablesToAdd = priorResult.EnvironmentVariablesToAdd; logger?.LogMessage($"\tDotnet root: {dotnetRoot}"); logger?.LogMessage($"\tMSBuild SDKs Dir: {msbuildSdksDir}"); @@ -139,9 +144,9 @@ private sealed class CachedState logger?.LogMessage($"\tResolved SDK directory: {resolverResult.ResolvedSdkDirectory}"); logger?.LogMessage($"\tglobal.json path: {resolverResult.GlobalJsonPath}"); logger?.LogMessage($"\tFailed to resolve SDK from global.json: {resolverResult.FailedToResolveSDKSpecifiedInGlobalJson}"); - - msbuildSdksDir = Path.Combine(resolverResult.ResolvedSdkDirectory, "Sdks"); - netcoreSdkVersion = new DirectoryInfo(resolverResult.ResolvedSdkDirectory).Name; + string dotnetSdkDir = resolverResult.ResolvedSdkDirectory; + msbuildSdksDir = Path.Combine(dotnetSdkDir, "Sdks"); + netcoreSdkVersion = new DirectoryInfo(dotnetSdkDir).Name; globalJsonPath = resolverResult.GlobalJsonPath; // These are overrides that are used to force the resolved SDK tasks and targets to come from a given @@ -197,20 +202,22 @@ private sealed class CachedState } string? fullPathToMuxer = - TryResolveMuxerFromSdkResolution(resolverResult) + TryResolveMuxerFromSdkResolution(dotnetSdkDir) ?? Path.Combine(dotnetRoot, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Constants.DotNetExe : Constants.DotNet); if (File.Exists(fullPathToMuxer)) { - propertiesToAdd ??= new Dictionary(); - propertiesToAdd.Add(DotnetHostExperimentalKey, fullPathToMuxer); + environmentVariablesToAdd ??= new Dictionary(1) + { + [DOTNET_HOST] = fullPathToMuxer + }; } else { - logger?.LogMessage($"Could not set '{DotnetHostExperimentalKey}' because dotnet executable '{fullPathToMuxer}' does not exist."); + logger?.LogMessage($"Could not set '{DOTNET_HOST}' environment variable because dotnet executable '{fullPathToMuxer}' does not exist."); } string? runtimeVersion = dotnetRoot != null ? - _getMsbuildRuntime(resolverResult.ResolvedSdkDirectory, dotnetRoot) : + _getMsbuildRuntime(dotnetSdkDir, dotnetRoot) : null; if (!string.IsNullOrEmpty(runtimeVersion)) { @@ -224,7 +231,7 @@ private sealed class CachedState if (resolverResult.FailedToResolveSDKSpecifiedInGlobalJson) { - logger?.LogMessage($"Could not resolve SDK specified in '{resolverResult.GlobalJsonPath}'. Ignoring global.json for this resolution."); + logger?.LogMessage($"Could not resolve SDK specified in '{globalJsonPath}'. Ignoring global.json for this resolution."); if (warnings == null) { @@ -241,8 +248,9 @@ private sealed class CachedState } propertiesToAdd ??= new Dictionary(); - propertiesToAdd.Add("SdkResolverHonoredGlobalJson", "false"); - propertiesToAdd.Add("SdkResolverGlobalJsonPath", resolverResult.GlobalJsonPath); + propertiesToAdd.Add(SdkResolverHonoredGlobalJson, "false"); + // TODO: this would ideally be reported anytime it was non-null - that may cause more imports though? + propertiesToAdd.Add(SdkResolverGlobalJsonPath, globalJsonPath); if (logger != null) { @@ -258,7 +266,8 @@ private sealed class CachedState NETCoreSdkVersion = netcoreSdkVersion, GlobalJsonPath = globalJsonPath, PropertiesToAdd = propertiesToAdd, - WorkloadResolver = workloadResolver + WorkloadResolver = workloadResolver, + EnvironmentVariablesToAdd = environmentVariablesToAdd }; // First check if requested SDK resolves to a workload SDK pack @@ -285,7 +294,7 @@ private sealed class CachedState msbuildSdkDir); } - return factory.IndicateSuccess(msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings); + return factory.IndicateSuccess(msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings, environmentVariablesToAdd: environmentVariablesToAdd); } /// @@ -295,10 +304,10 @@ private sealed class CachedState /// SDK layouts always have a defined relationship to the location of the muxer - /// the muxer binary should be exactly two directories above the SDK directory. /// - private static string? TryResolveMuxerFromSdkResolution(SdkResolutionResult resolverResult) + private static string? TryResolveMuxerFromSdkResolution(string resolvedSdkDirectory) { var expectedFileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Constants.DotNetExe : Constants.DotNet; - var currentDir = resolverResult.ResolvedSdkDirectory; + var currentDir = resolvedSdkDirectory; var expectedDotnetRoot = Path.GetDirectoryName(Path.GetDirectoryName(currentDir)); var expectedMuxerPath = Path.Combine(expectedDotnetRoot, expectedFileName); if (File.Exists(expectedMuxerPath)) diff --git a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs index cebe1a8152b8..13fcda205ddc 100644 --- a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs +++ b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs @@ -205,8 +205,14 @@ public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuild(bool disallo if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // DotnetHost is the path to dotnet.exe. Can be only on Windows. - result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(2); - result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey); + result.PropertiesToAdd.Should().NotBeNull() + .And.HaveCount(1) + .And.NotContainKey(DotnetHostExperimentalKey); + result.EnvironmentVariablesToAdd.Should().NotBeNull() + .And.BeEquivalentTo(new Dictionary + { + ["DOTNET_HOST"] = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet", "dotnet.exe") + }); } else { @@ -231,9 +237,9 @@ public void WhenALocalSdkIsResolvedItReturnsHostFromThatSDKInsteadOfAmbientGloba var localSdkRoot = Path.Combine("some", "local", "dir"); var localSdkDotnetRoot = Path.Combine(environment.TestDirectory.FullName, localSdkRoot, "dotnet"); var ambientSdkDotnetRoot = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet"); - var ambientMSBuildSkRoot = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "1.2.3"); - var localPathMSBuildSdkRoot = environment.CreateSdkDirectory(localSdkRoot, "Some.Test.Sdk", "1.2.4"); - var ambientDotnetBinary = environment.CreateMuxerAndAddToPath(ProgramFiles.X64); + var _ambientMSBuildSkRoot = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "1.2.3"); + var _localPathMSBuildSdkRoot = environment.CreateSdkDirectory(localSdkRoot, "Some.Test.Sdk", "1.2.4"); + var _ambientDotnetBinary = environment.CreateMuxerAndAddToPath(ProgramFiles.X64); var localDotnetBinary = environment.CreateMuxer(localSdkRoot); environment.CreateGlobalJson(environment.TestDirectory, "1.2.3", [localSdkDotnetRoot, ambientSdkDotnetRoot]); @@ -249,9 +255,11 @@ public void WhenALocalSdkIsResolvedItReturnsHostFromThatSDKInsteadOfAmbientGloba context, new MockFactory()); result.Success.Should().BeTrue(); - result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(2); - result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey); - result.PropertiesToAdd[DotnetHostExperimentalKey].Should().Be(localDotnetBinary); + result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(1).And.ContainKey(MSBuildTaskHostRuntimeVersion); + result.EnvironmentVariablesToAdd.Should().NotBeNull().And.BeEquivalentTo(new Dictionary + { + [DotnetHostExperimentalKey] = localDotnetBinary + }); } [Theory] @@ -325,8 +333,13 @@ public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuildWhenVersionIn if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // DotnetHost is the path to dotnet.exe. Can be only on Windows. - result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(4); - result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey); + result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(3); + result.PropertiesToAdd.Should().NotContainKey(DotnetHostExperimentalKey); + result.EnvironmentVariablesToAdd.Should().NotBeNull() + .And.BeEquivalentTo(new Dictionary + { + ["DOTNET_HOST"] = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet", "dotnet.exe") + }); } else { From 52f3db2b30d41b9185f7af1b1411d5bc5d651ea4 Mon Sep 17 00:00:00 2001 From: --get Date: Wed, 6 Aug 2025 13:07:32 -0500 Subject: [PATCH 4/7] align versions - this needs review --- .../Microsoft.DotNet.MSBuildSdkResolver.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj index f214ae97c297..e445b0beb335 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj @@ -108,13 +108,13 @@ - + - + - - - + + + From ea7eee9d577e815d035122f570a4a55b91b29b03 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sat, 9 Aug 2025 13:58:31 -0500 Subject: [PATCH 5/7] use reflection to find capable API if present --- eng/Versions.props | 15 +++--- .../MSBuildSdkResolver.cs | 51 ++++++++++++++++++- ...Microsoft.DotNet.MSBuildSdkResolver.csproj | 21 ++++---- .../GivenAnMSBuildSdkResolver.cs | 33 ++++-------- 4 files changed, 76 insertions(+), 44 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index dcef755c7200..acbfa1cdbc18 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -132,16 +132,16 @@ 10.0.0-preview.7.25377.103 - 9.0.0 + 8.0.0 2.0.0-preview.1.24427.4 4.5.1 - 9.0.0 + 8.0.0 4.5.5 - 9.0.0 - 9.0.0 - 9.0.0 + 8.0.0 + 8.0.0 + 8.0.5 4.5.4 - 9.0.0 + 8.0.0 @@ -189,8 +189,7 @@ Additionally, set the MinimumVSVersion for the installer UI that's required for targeting NetCurrent --> 17.15.0-preview-25377-103 17.15.0-preview-25377-103 - - $(MicrosoftBuildVersion) + 17.11.4 17.13 diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs index 8a0c1f559650..df46615835cd 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs @@ -39,6 +39,47 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver private const string SdkResolverGlobalJsonPath = "SdkResolverGlobalJsonPath"; private static CachingWorkloadResolver _staticWorkloadResolver = new(); + /// + /// This is a workaround for MSBuild API compatibility in older VS hosts. + /// To allow for working with the MSBuild 17.15 APIs while not blowing up our ability to build the SdkResolver in + /// VS-driven CI pipelines, we probe and invoke only if the expected method is present. + /// Once we can update our MSBuild API dependency this can go away. + /// + private static Func< + SdkResultFactory, + // path to sdk + string, + // sdk version + string?, + // properties to add + IDictionary?, + // items to add + IDictionary?, + // warnings + List?, + // environment variables to add + IDictionary?, + SdkResult>? _factorySuccessFunc = TryLocateNewMSBuildFactory(); + + private static Func?, IDictionary?, List?, IDictionary?, SdkResult>? TryLocateNewMSBuildFactory() + { + if (typeof(SdkResultFactory).GetMethod("IndicateSuccess", [ + typeof(string), // path to sdk + typeof(string), // sdk version + typeof(IDictionary), // properties to add + typeof(IDictionary), // items to add + typeof(List), // warnings + typeof(IDictionary) // environment variables to add + ]) is MethodInfo m) + { + return (factory, path, version, properties, items, warnings, environmentVariables) => + { + return (SdkResult)m.Invoke(factory, [path, version, properties, items, warnings, environmentVariables]); + }; + } + return null; + } + private bool _shouldLog = false; public DotNetMSBuildSdkResolver() @@ -293,8 +334,14 @@ private sealed class CachedState Strings.MSBuildSDKDirectoryNotFound, msbuildSdkDir); } - - return factory.IndicateSuccess(msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings, environmentVariablesToAdd: environmentVariablesToAdd); + if (_factorySuccessFunc != null) + { + return _factorySuccessFunc.Invoke(factory, msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings, environmentVariablesToAdd); + } + else + { + return factory.IndicateSuccess(msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings); + } } /// diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj index e445b0beb335..592fea73c335 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj @@ -105,19 +105,18 @@ - - - - - - - - - - - + + + + + + + + + + diff --git a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs index 13fcda205ddc..cebe1a8152b8 100644 --- a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs +++ b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs @@ -205,14 +205,8 @@ public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuild(bool disallo if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // DotnetHost is the path to dotnet.exe. Can be only on Windows. - result.PropertiesToAdd.Should().NotBeNull() - .And.HaveCount(1) - .And.NotContainKey(DotnetHostExperimentalKey); - result.EnvironmentVariablesToAdd.Should().NotBeNull() - .And.BeEquivalentTo(new Dictionary - { - ["DOTNET_HOST"] = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet", "dotnet.exe") - }); + result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(2); + result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey); } else { @@ -237,9 +231,9 @@ public void WhenALocalSdkIsResolvedItReturnsHostFromThatSDKInsteadOfAmbientGloba var localSdkRoot = Path.Combine("some", "local", "dir"); var localSdkDotnetRoot = Path.Combine(environment.TestDirectory.FullName, localSdkRoot, "dotnet"); var ambientSdkDotnetRoot = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet"); - var _ambientMSBuildSkRoot = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "1.2.3"); - var _localPathMSBuildSdkRoot = environment.CreateSdkDirectory(localSdkRoot, "Some.Test.Sdk", "1.2.4"); - var _ambientDotnetBinary = environment.CreateMuxerAndAddToPath(ProgramFiles.X64); + var ambientMSBuildSkRoot = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "1.2.3"); + var localPathMSBuildSdkRoot = environment.CreateSdkDirectory(localSdkRoot, "Some.Test.Sdk", "1.2.4"); + var ambientDotnetBinary = environment.CreateMuxerAndAddToPath(ProgramFiles.X64); var localDotnetBinary = environment.CreateMuxer(localSdkRoot); environment.CreateGlobalJson(environment.TestDirectory, "1.2.3", [localSdkDotnetRoot, ambientSdkDotnetRoot]); @@ -255,11 +249,9 @@ public void WhenALocalSdkIsResolvedItReturnsHostFromThatSDKInsteadOfAmbientGloba context, new MockFactory()); result.Success.Should().BeTrue(); - result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(1).And.ContainKey(MSBuildTaskHostRuntimeVersion); - result.EnvironmentVariablesToAdd.Should().NotBeNull().And.BeEquivalentTo(new Dictionary - { - [DotnetHostExperimentalKey] = localDotnetBinary - }); + result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(2); + result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey); + result.PropertiesToAdd[DotnetHostExperimentalKey].Should().Be(localDotnetBinary); } [Theory] @@ -333,13 +325,8 @@ public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuildWhenVersionIn if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // DotnetHost is the path to dotnet.exe. Can be only on Windows. - result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(3); - result.PropertiesToAdd.Should().NotContainKey(DotnetHostExperimentalKey); - result.EnvironmentVariablesToAdd.Should().NotBeNull() - .And.BeEquivalentTo(new Dictionary - { - ["DOTNET_HOST"] = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet", "dotnet.exe") - }); + result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(4); + result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey); } else { From 9e64a2c310a0bf41491a359ebad69665fa8e84c2 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sat, 9 Aug 2025 18:07:53 -0500 Subject: [PATCH 6/7] keep older fallback for VS backcompat --- .../Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs index df46615835cd..0bae87899837 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs @@ -247,6 +247,10 @@ private sealed class CachedState ?? Path.Combine(dotnetRoot, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Constants.DotNetExe : Constants.DotNet); if (File.Exists(fullPathToMuxer)) { + // keeping this in until this component no longer needs to handle 17.14. + propertiesToAdd ??= new Dictionary(); + propertiesToAdd.Add(DotnetHostExperimentalKey, fullPathToMuxer); + // this is the future-facing implementation. environmentVariablesToAdd ??= new Dictionary(1) { [DOTNET_HOST] = fullPathToMuxer From cb45b92a86d875389bad0e79b2c9cd03176a0d5c Mon Sep 17 00:00:00 2001 From: --get Date: Sat, 9 Aug 2025 21:04:29 -0500 Subject: [PATCH 7/7] use a delegate and doc it! --- .../MSBuildSdkResolver.cs | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs index 0bae87899837..6b891c586a23 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs @@ -45,23 +45,15 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver /// VS-driven CI pipelines, we probe and invoke only if the expected method is present. /// Once we can update our MSBuild API dependency this can go away. /// - private static Func< - SdkResultFactory, - // path to sdk - string, - // sdk version - string?, - // properties to add - IDictionary?, - // items to add - IDictionary?, - // warnings - List?, - // environment variables to add - IDictionary?, - SdkResult>? _factorySuccessFunc = TryLocateNewMSBuildFactory(); - - private static Func?, IDictionary?, List?, IDictionary?, SdkResult>? TryLocateNewMSBuildFactory() + private static UpdatedSdkResultFactorySuccess? _factorySuccessFunc = TryLocateNewMSBuildFactory(); + + /// + /// This represents the 'open delegate' form of the updated SdkResultFactory.IndicateSuccess method with environment variable support. + /// Because it is an open delegate, we can provide an object instance to be called as the first argument. + /// + public delegate SdkResult UpdatedSdkResultFactorySuccess(SdkResultFactory factory, string sdkPath, string? sdkVersion, IDictionary? propertiesToAdd, IDictionary? itemsToAdd, List? warnings, IDictionary? environmentVariablesToAdd); + + private static UpdatedSdkResultFactorySuccess? TryLocateNewMSBuildFactory() { if (typeof(SdkResultFactory).GetMethod("IndicateSuccess", [ typeof(string), // path to sdk @@ -72,10 +64,10 @@ private static Func< typeof(IDictionary) // environment variables to add ]) is MethodInfo m) { - return (factory, path, version, properties, items, warnings, environmentVariables) => - { - return (SdkResult)m.Invoke(factory, [path, version, properties, items, warnings, environmentVariables]); - }; + return Delegate.CreateDelegate( + typeof(Func?, IDictionary?, List?, IDictionary?, SdkResult>), + null, + m) as UpdatedSdkResultFactorySuccess; } return null; } @@ -340,7 +332,7 @@ private sealed class CachedState } if (_factorySuccessFunc != null) { - return _factorySuccessFunc.Invoke(factory, msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings, environmentVariablesToAdd); + return _factorySuccessFunc(factory, msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings, environmentVariablesToAdd); } else {