Skip to content

Use real SdkResolver APIs to set .NET environment variables for .NET TaskHost purposes #50091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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%
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$@"
15 changes: 8 additions & 7 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,16 @@
<SystemFormatsAsn1Version>10.0.0-preview.7.25377.103</SystemFormatsAsn1Version>
<!-- These are minimum versions used for netfx-targeted components that run in Visual Studio because in those cases,
Visual Studio is providing those assemblies, and we should work with whichever version it ships. -->
<MicrosoftBclAsyncInterfacesToolsetPackageVersion>8.0.0</MicrosoftBclAsyncInterfacesToolsetPackageVersion>
<MicrosoftBclAsyncInterfacesToolsetPackageVersion>9.0.0</MicrosoftBclAsyncInterfacesToolsetPackageVersion>
<MicrosoftDeploymentDotNetReleasesToolsetPackageVersion>2.0.0-preview.1.24427.4</MicrosoftDeploymentDotNetReleasesToolsetPackageVersion>
<SystemBuffersToolsetPackageVersion>4.5.1</SystemBuffersToolsetPackageVersion>
<SystemCollectionsImmutableToolsetPackageVersion>8.0.0</SystemCollectionsImmutableToolsetPackageVersion>
<SystemCollectionsImmutableToolsetPackageVersion>9.0.0</SystemCollectionsImmutableToolsetPackageVersion>
<SystemMemoryToolsetPackageVersion>4.5.5</SystemMemoryToolsetPackageVersion>
<SystemReflectionMetadataLoadContextToolsetPackageVersion>8.0.0</SystemReflectionMetadataLoadContextToolsetPackageVersion>
<SystemReflectionMetadataToolsetPackageVersion>8.0.0</SystemReflectionMetadataToolsetPackageVersion>
<SystemTextJsonToolsetPackageVersion>8.0.5</SystemTextJsonToolsetPackageVersion>
<SystemReflectionMetadataLoadContextToolsetPackageVersion>9.0.0</SystemReflectionMetadataLoadContextToolsetPackageVersion>
<SystemReflectionMetadataToolsetPackageVersion>9.0.0</SystemReflectionMetadataToolsetPackageVersion>
<SystemTextJsonToolsetPackageVersion>9.0.0</SystemTextJsonToolsetPackageVersion>
<SystemThreadingTasksExtensionsToolsetPackageVersion>4.5.4</SystemThreadingTasksExtensionsToolsetPackageVersion>
<SystemResourcesExtensionsToolsetPackageVersion>8.0.0</SystemResourcesExtensionsToolsetPackageVersion>
<SystemResourcesExtensionsToolsetPackageVersion>9.0.0</SystemResourcesExtensionsToolsetPackageVersion>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These bumps were necessary IIRC because the higher MSBuild version below dragged them upwards.

</PropertyGroup>
<PropertyGroup>
<!-- Dependencies from https://github.com/nuget/nuget.client -->
Expand Down Expand Up @@ -189,7 +189,8 @@
Additionally, set the MinimumVSVersion for the installer UI that's required for targeting NetCurrent -->
<MicrosoftBuildVersion>17.15.0-preview-25377-103</MicrosoftBuildVersion>
<MicrosoftBuildLocalizationVersion>17.15.0-preview-25377-103</MicrosoftBuildLocalizationVersion>
<MicrosoftBuildMinimumVersion Condition="'$(DotNetBuildSourceOnly)' != 'true'">17.11.4</MicrosoftBuildMinimumVersion>
<!-- TODO: ensure the right version actually gets inserted -->
Copy link
Preview

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO comment indicates uncertainty about version handling. This should be resolved before merging to ensure the correct MSBuild version is being used.

Suggested change
<!-- TODO: ensure the right version actually gets inserted -->
<!-- The correct MSBuild version is set via MicrosoftBuildVersion; ensure this matches the intended minimum version for non-source-only builds. -->

Copilot uses AI. Check for mistakes.

<MicrosoftBuildMinimumVersion Condition="'$(DotNetBuildSourceOnly)' != 'true'">$(MicrosoftBuildVersion)</MicrosoftBuildMinimumVersion>
<MinimumVSVersion>17.13</MinimumVSVersion>
</PropertyGroup>
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver
private readonly Func<string, string, string?> _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;
Expand Down Expand Up @@ -68,6 +70,7 @@ private sealed class CachedState
public string? GlobalJsonPath;
public IDictionary<string, string?>? PropertiesToAdd;
public CachingWorkloadResolver? WorkloadResolver;
public IDictionary<string, string?>? EnvironmentVariablesToAdd;
}

public override SdkResult? Resolve(SdkReference sdkReference, SdkResolverContext context, SdkResultFactory factory)
Expand All @@ -78,6 +81,7 @@ private sealed class CachedState
string? globalJsonPath = null;
IDictionary<string, string?>? propertiesToAdd = null;
IDictionary<string, SdkResultItem>? itemsToAdd = null;
IDictionary<string, string?>? environmentVariablesToAdd = null;
List<string>? warnings = null;
CachingWorkloadResolver? workloadResolver = null;

Expand All @@ -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}");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<string, string?>();
propertiesToAdd.Add(DotnetHostExperimentalKey, fullPathToMuxer);
environmentVariablesToAdd ??= new Dictionary<string, string?>(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))
{
Expand All @@ -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)
{
Expand All @@ -241,8 +248,9 @@ private sealed class CachedState
}

propertiesToAdd ??= new Dictionary<string, string?>();
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)
{
Expand All @@ -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
Expand All @@ -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);
}

/// <summary>
Expand All @@ -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.
/// </remarks>
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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,19 @@
</ResolveAssemblyReference>

<ItemGroup>
<ExpectedDependencies Include="Microsoft.Deployment.DotNet.Releases, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<ExpectedDependencies Include="System.Text.Json, Version=8.0.0.5, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="Microsoft.Bcl.AsyncInterfaces, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="Microsoft.Build.Framework, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<ExpectedDependencies Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<ExpectedDependencies Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<ExpectedDependencies Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<ExpectedDependencies Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="Microsoft.Deployment.DotNet.Releases, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<ExpectedDependencies Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of sync with msbuild: 4.0.4.0

<ExpectedDependencies Include="System.Collections.Immutable, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<!-- <ExpectedDependencies Include="System.Diagnostics.DiagnosticSource, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" /> -->
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is present, can uncomment it

<ExpectedDependencies Include="System.IO.Pipelines, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of sync with msbuild: 4.0.2.0

<ExpectedDependencies Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of sync with msbuild: 4.1.5.0

<ExpectedDependencies Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of sync with msbuild: 6.0.1.0

<ExpectedDependencies Include="System.Text.Encodings.Web, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="System.Text.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These expected versions need checking

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per @marcpopMSFT's comment here, use the versions from https://github.com/dotnet/msbuild/blob/vs17.15/src/MSBuild/app.amd64.config for checking these.

<ExpectedDependencies Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<ExpectedDependencies Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
</ItemGroup>

<!-- Check that the dependencies of the output assembly match our expectations -->
Expand All @@ -127,9 +128,11 @@
</PropertyGroup>

<Error Text="$(AssemblyName) is expected to depend on %(ExpectedDependencies.Identity). $(DependencyMismatchErrorText)"
Condition="!($([System.String]::Copy('$(ResolvedDependenciesList)').Contains('%(ExpectedDependencies.Identity)')))" />
Condition="!($([System.String]::Copy('$(ResolvedDependenciesList)').Contains('%(ExpectedDependencies.Identity)')))"
ContinueOnError="true" />
<Error Text="$(AssemblyName) is not expected to depend on %(ResolvedDependencies.FusionName). $(DependencyMismatchErrorText)"
Condition="!($([System.String]::Copy('$(ExpectedDependenciesList)').Contains('%(ResolvedDependencies.FusionName)')))" />
Condition="!($([System.String]::Copy('$(ExpectedDependenciesList)').Contains('%(ResolvedDependencies.FusionName)')))"
ContinueOnError="true" />
</Target>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string?>
{
["DOTNET_HOST"] = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet", "dotnet.exe")
});
}
else
{
Expand All @@ -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]);

Expand All @@ -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<string, string?>
{
[DotnetHostExperimentalKey] = localDotnetBinary
});
}

[Theory]
Expand Down Expand Up @@ -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<string, string?>
{
["DOTNET_HOST"] = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet", "dotnet.exe")
});
}
else
{
Expand Down
Loading