Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 16 additions & 2 deletions src/Resolvers/Microsoft.DotNet.SdkResolver/NETCoreSdkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,24 @@ public SdkResolutionResult ResolveNETCoreSdkDirectory(string? globalJsonStartDir
mostCompatible = GetMostCompatibleSdk(dotnetExeDir, msbuildVersion, 5);
}
else if (result.ResolvedSdkDirectory != null
&& result.GlobalJsonPath == null
&& msbuildVersion < GetMinimumMSBuildVersion(result.ResolvedSdkDirectory))
{
mostCompatible = GetMostCompatibleSdk(dotnetExeDir, msbuildVersion);
// Fall back to the most compatible SDK when the resolved SDK requires a higher MSBuild version
// than the one currently available. When global.json uses a rollforward policy (e.g. latestMajor),
// the native resolver may pick a newer SDK that is incompatible with the current MSBuild version.
// MSBuild compatibility should be part of what "compatible" means in a rollforward scenario.
//
// However, when the user has explicitly pinned an exact SDK version in global.json and that exact
// version was successfully resolved, respect the user's intent and let the final MSBuild version
// check produce a clear error message rather than silently falling back to a different SDK.
bool wasExactVersionPinned = result.GlobalJsonPath != null
&& result.RequestedVersion != null
&& string.Equals(new DirectoryInfo(result.ResolvedSdkDirectory).Name, result.RequestedVersion, StringComparison.OrdinalIgnoreCase);

if (!wasExactVersionPinned)
{
mostCompatible = GetMostCompatibleSdk(dotnetExeDir, msbuildVersion);
}
}

if (mostCompatible != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,57 @@ public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuild(bool disallo
result.Errors.Should().BeNullOrEmpty();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuildWhenGlobalJsonRollForwardPicksIncompatibleSdk(bool disallowPreviews)
{
var environment = new TestEnvironment(TestAssetsManager, identifier: disallowPreviews.ToString())
{
DisallowPrereleaseByDefault = disallowPreviews
};

var compatibleRtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "98.98.98", new Version(19, 0, 0, 0));
var compatiblePreview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99-preview", new Version(20, 0, 0, 0));
var incompatible = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "100.100.100", new Version(21, 0, 0, 0));

environment.CreateMuxerAndAddToPath(ProgramFiles.X64);

// global.json with latestMajor rollforward: the native resolver will pick 100.100.100 as the
// highest available SDK, but 100.100.100 requires MSBuild 21.0 which is higher than 20.0.
// The resolver should fall back to the highest SDK compatible with the current MSBuild version.
environment.CreateGlobalJson(environment.TestDirectory, "98.98.98", rollForward: "latestMajor");

var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext
{
MSBuildVersion = new Version(20, 0, 0, 0),
ProjectFileDirectory = environment.TestDirectory,
},
new MockFactory());

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();
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);
}
else
{
result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(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();
}

[Fact]
public void WhenALocalSdkIsResolvedItReturnsHostFromThatSDKInsteadOfAmbientGlobalSdk()
{
Expand All @@ -235,7 +286,7 @@ public void WhenALocalSdkIsResolvedItReturnsHostFromThatSDKInsteadOfAmbientGloba
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]);
environment.CreateGlobalJson(environment.TestDirectory, "1.2.3", paths: [localSdkDotnetRoot, ambientSdkDotnetRoot]);

var resolver = environment.CreateResolver();
var context = new MockContext(Log)
Expand Down Expand Up @@ -801,12 +852,17 @@ private void CreateMSBuildRequiredVersionFile(
minimumMSBuildVersion.ToString());
}

public void CreateGlobalJson(DirectoryInfo directory, string version, string[]? paths = null)
public void CreateGlobalJson(DirectoryInfo directory, string version, string? rollForward = null, string[]? paths = null)
{
var builder = new StringBuilder();
builder.AppendLine("{");
builder.AppendLine("\t\"sdk\": {");
builder.Append($"\t\"version\": \"{version}\"");
if (rollForward is not null)
{
builder.AppendLine(",");
builder.Append($"\t\"rollForward\": \"{rollForward}\"");
}
if (paths is not null)
{
builder.Append(',');
Expand All @@ -827,6 +883,7 @@ public void CreateGlobalJson(DirectoryInfo directory, string version, string[]?
}
builder.AppendLine("\t]");
}
builder.AppendLine();
builder.AppendLine("\t}");
builder.AppendLine("}");
var globalJsonContent = builder.ToString();
Expand Down