Skip to content

Commit

Permalink
[release/9.0.2xx] Expand MSBuildSdkResolver (#45459)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcpopMSFT authored Dec 17, 2024
2 parents 80bbcd6 + 058b325 commit abd7d1c
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using System.Text.Json;
using Microsoft.Build.Framework;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Configurer;
Expand All @@ -27,23 +28,28 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver

private readonly Func<string, string?> _getEnvironmentVariable;
private readonly Func<string>? _getCurrentProcessPath;
private readonly Func<string, string, string?> _getMsbuildRuntime;
private readonly NETCoreSdkResolver _netCoreSdkResolver;

private const string DotnetHost = "DOTNET_HOST_PATH";
private const string MSBuildTaskHostRuntimeVersion = "SdkResolverMSBuildTaskHostRuntimeVersion";

private static CachingWorkloadResolver _staticWorkloadResolver = new();

private bool _shouldLog = false;

public DotNetMSBuildSdkResolver()
: this(Environment.GetEnvironmentVariable, null, VSSettings.Ambient)
: this(Environment.GetEnvironmentVariable, null, GetMSbuildRuntimeVersion, VSSettings.Ambient)
{
}

// Test constructor
public DotNetMSBuildSdkResolver(Func<string, string?> getEnvironmentVariable, Func<string>? getCurrentProcessPath, VSSettings vsSettings)
public DotNetMSBuildSdkResolver(Func<string, string?> getEnvironmentVariable, Func<string>? getCurrentProcessPath, Func<string, string, string?> getMsbuildRuntime, VSSettings vsSettings)
{
_getEnvironmentVariable = getEnvironmentVariable;
_getCurrentProcessPath = getCurrentProcessPath;
_netCoreSdkResolver = new NETCoreSdkResolver(getEnvironmentVariable, vsSettings);
_getMsbuildRuntime = getMsbuildRuntime;

if (_getEnvironmentVariable(EnvironmentVariableNames.DOTNET_MSBUILD_SDK_RESOLVER_ENABLE_LOG) is string val &&
(string.Equals(val, "true", StringComparison.OrdinalIgnoreCase) ||
Expand Down Expand Up @@ -189,6 +195,32 @@ private sealed class CachedState
minimumVSDefinedSDKVersion);
}

string? dotnetExe = dotnetRoot != null ?
Path.Combine(dotnetRoot, Constants.DotNetExe) :
null;
if (File.Exists(dotnetExe))
{
propertiesToAdd ??= new Dictionary<string, string?>();
propertiesToAdd.Add(DotnetHost, dotnetExe);
}
else
{
logger?.LogMessage($"Could not set '{DotnetHost}' because dotnet executable '{dotnetExe}' does not exist.");
}

string? runtimeVersion = dotnetRoot != null ?
_getMsbuildRuntime(resolverResult.ResolvedSdkDirectory, dotnetRoot) :
null;
if (!string.IsNullOrEmpty(runtimeVersion))
{
propertiesToAdd ??= new Dictionary<string, string?>();
propertiesToAdd.Add(MSBuildTaskHostRuntimeVersion, runtimeVersion);
}
else
{
logger?.LogMessage($"Could not set '{MSBuildTaskHostRuntimeVersion}' because runtime version could not be determined.");
}

if (resolverResult.FailedToResolveSDKSpecifiedInGlobalJson)
{
logger?.LogMessage($"Could not resolve SDK specified in '{resolverResult.GlobalJsonPath}'. Ignoring global.json for this resolution.");
Expand All @@ -207,10 +239,7 @@ private sealed class CachedState
warnings.Add(Strings.GlobalJsonResolutionFailed);
}

if (propertiesToAdd == null)
{
propertiesToAdd = new Dictionary<string, string?>();
}
propertiesToAdd ??= new Dictionary<string, string?>();
propertiesToAdd.Add("SdkResolverHonoredGlobalJson", "false");
propertiesToAdd.Add("SdkResolverGlobalJsonPath", resolverResult.GlobalJsonPath);

Expand Down Expand Up @@ -254,6 +283,28 @@ private sealed class CachedState
return factory.IndicateSuccess(msbuildSdkDir, netcoreSdkVersion, propertiesToAdd, itemsToAdd, warnings);
}

private static string? GetMSbuildRuntimeVersion(string sdkDirectory, string dotnetRoot)
{
// 1. Get the runtime version from the MSBuild.runtimeconfig.json file
string runtimeConfigPath = Path.Combine(sdkDirectory, "MSBuild.runtimeconfig.json");
if (!File.Exists(runtimeConfigPath)) return null;

using var stream = File.OpenRead(runtimeConfigPath);
using var jsonDoc = JsonDocument.Parse(stream);

JsonElement root = jsonDoc.RootElement;
if (!root.TryGetProperty("runtimeOptions", out JsonElement runtimeOptions) ||
!runtimeOptions.TryGetProperty("framework", out JsonElement framework)) return null;

string? runtimeName = framework.GetProperty("name").GetString();
string? runtimeVersion = framework.GetProperty("version").GetString();

// 2. Check that the runtime version is installed (in shared folder)
return (!string.IsNullOrEmpty(runtimeName) && !string.IsNullOrEmpty(runtimeVersion) &&
Directory.Exists(Path.Combine(dotnetRoot, "shared", runtimeName, runtimeVersion)))
? runtimeVersion : null;
}

private static SdkResult Failure(SdkResultFactory factory, ResolverLogger? logger, SdkLogger sdkLogger, string format, params object?[] args)
{
string error = string.Format(format, args);
Expand Down
1 change: 1 addition & 0 deletions src/Resolvers/Microsoft.DotNet.NativeWrapper/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ internal static class Constants
{
public const string HostFxr = "hostfxr";
public const string DotNet = "dotnet";
public const string DotNetExe = "dotnet.exe";
public const string PATH = "PATH";
public const string DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR = "DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
{
public class GivenAnMSBuildSdkResolver : SdkTest
{
private const string DotnetHost = "DOTNET_HOST_PATH";
private const string MSBuildTaskHostRuntimeVersion = "SdkResolverMSBuildTaskHostRuntimeVersion";

public GivenAnMSBuildSdkResolver(ITestOutputHelper logger) : base(logger)
{
Expand Down Expand Up @@ -200,7 +202,18 @@ public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuild(bool disallo
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be((disallowPreviews ? compatibleRtm : compatiblePreview).FullName);
result.AdditionalPaths.Should().BeNull();
result.PropertiesToAdd.Should().BeNull();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// DotnetHost is the path to dotnet.exe. Can be only on Windows.
result.PropertiesToAdd.Count.Should().Be(2);
result.PropertiesToAdd.Should().ContainKey(DotnetHost);
}
else
{
result.PropertiesToAdd.Count.Should().Be(1);
}
result.PropertiesToAdd.Should().ContainKey(MSBuildTaskHostRuntimeVersion);
result.PropertiesToAdd[MSBuildTaskHostRuntimeVersion].Should().Be("mockRuntimeVersion");
result.Version.Should().Be(disallowPreviews ? "98.98.98" : "99.99.99-preview");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
Expand Down Expand Up @@ -274,9 +287,20 @@ public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuildWhenVersionIn
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be((disallowPreviews ? compatibleRtm : compatiblePreview).FullName);
result.AdditionalPaths.Should().BeNull();
result.PropertiesToAdd.Count.Should().Be(2);
result.PropertiesToAdd.ContainsKey("SdkResolverHonoredGlobalJson");
result.PropertiesToAdd.ContainsKey("SdkResolverGlobalJsonPath");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// DotnetHost is the path to dotnet.exe. Can be only on Windows.
result.PropertiesToAdd.Count.Should().Be(4);
result.PropertiesToAdd.Should().ContainKey(DotnetHost);
}
else
{
result.PropertiesToAdd.Count.Should().Be(3);
}
result.PropertiesToAdd.Should().ContainKey(MSBuildTaskHostRuntimeVersion);
result.PropertiesToAdd[MSBuildTaskHostRuntimeVersion].Should().Be("mockRuntimeVersion");
result.PropertiesToAdd.Should().ContainKey("SdkResolverHonoredGlobalJson");
result.PropertiesToAdd.Should().ContainKey("SdkResolverGlobalJsonPath");
result.PropertiesToAdd["SdkResolverHonoredGlobalJson"].Should().Be("false");
result.Version.Should().Be(disallowPreviews ? "98.98.98" : "99.99.99-preview");
result.Warnings.Should().BeEquivalentTo(new[] { "Unable to locate the .NET SDK version '1.2.3' as specified by global.json, please check that the specified version is installed." });
Expand Down Expand Up @@ -584,6 +608,7 @@ public SdkResolver CreateResolver(bool useAmbientSettings = false)
GetEnvironmentVariable,
// force current executable location to be the mocked dotnet executable location
() => ProcessPath,
(x, y) => "mockRuntimeVersion",
useAmbientSettings
? VSSettings.Ambient
: new VSSettings(VSSettingsFile?.FullName, DisallowPrereleaseByDefault));
Expand Down

0 comments on commit abd7d1c

Please sign in to comment.