diff --git a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs index 99b65f04b4a..9ed276e5bfe 100644 --- a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs +++ b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs @@ -90,7 +90,7 @@ internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration conf } // Check if this version meets the minimum requirement - if (SemVersion.ComparePrecedence(sdkVersion, minVersion) >= 0) + if (MeetsMinimumRequirement(sdkVersion, minVersion, minimumVersion)) { meetsMinimum = true; } @@ -152,4 +152,25 @@ public string GetEffectiveMinimumSdkVersion() return MinimumSdkVersion; } } + + /// + /// Checks if an installed SDK version meets the minimum requirement. + /// For .NET 10.x requirements, allows any .NET 10.x version including prereleases. + /// + /// The installed SDK version. + /// The required minimum version (parsed). + /// The required version string. + /// True if the installed version meets the requirement. + private static bool MeetsMinimumRequirement(SemVersion installedVersion, SemVersion requiredVersion, string requiredVersionString) + { + // Special handling for .NET 10.0.100 requirement - allow any .NET 10.x version + if (requiredVersionString == MinimumSdkVersionSingleFileAppHost) + { + // If we require 10.0.100, accept any version that is >= 10.0.0 + return installedVersion.Major >= 10; + } + + // For all other requirements, use strict version comparison + return SemVersion.ComparePrecedence(installedVersion, requiredVersion) >= 0; + } } \ No newline at end of file diff --git a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs index c33f5a61d15..bc7211d699b 100644 --- a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs +++ b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs @@ -6,6 +6,7 @@ using Aspire.Cli.DotNet; using Aspire.Cli.Resources; using Microsoft.Extensions.Configuration; +using Semver; namespace Aspire.Cli.Tests; @@ -238,6 +239,70 @@ public void ErrorMessage_Format_IsCorrect() Assert.Equal("The Aspire CLI requires .NET SDK version 9.0.302 or later. Detected: (not found).", message); } + [Fact] + public void MeetsMinimumRequirement_AllowsDotNet10Prereleases_ForSingleFileAppHost() + { + // Test the logic we added for allowing .NET 10 prereleases + var installedVersion = SemVersion.Parse("10.0.100-preview.1.25463.5", SemVersionStyles.Strict); + var requiredVersion = SemVersion.Parse("10.0.100", SemVersionStyles.Strict); + var requiredVersionString = "10.0.100"; + + // Use reflection to access the private method + var method = typeof(DotNetSdkInstaller).GetMethod("MeetsMinimumRequirement", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var result = (bool)method!.Invoke(null, new object[] { installedVersion, requiredVersion, requiredVersionString })!; + + Assert.True(result); + } + + [Fact] + public void MeetsMinimumRequirement_AllowsDotNet10LatestPrerelease_ForSingleFileAppHost() + { + // Test with a more recent .NET 10 prerelease + var installedVersion = SemVersion.Parse("10.1.0-preview.2.25999.99", SemVersionStyles.Strict); + var requiredVersion = SemVersion.Parse("10.0.100", SemVersionStyles.Strict); + var requiredVersionString = "10.0.100"; + + // Use reflection to access the private method + var method = typeof(DotNetSdkInstaller).GetMethod("MeetsMinimumRequirement", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var result = (bool)method!.Invoke(null, new object[] { installedVersion, requiredVersion, requiredVersionString })!; + + Assert.True(result); + } + + [Fact] + public void MeetsMinimumRequirement_RejectsDotNet9_ForSingleFileAppHost() + { + // Test that .NET 9 is still rejected for single file apphost requirements + var installedVersion = SemVersion.Parse("9.0.999", SemVersionStyles.Strict); + var requiredVersion = SemVersion.Parse("10.0.100", SemVersionStyles.Strict); + var requiredVersionString = "10.0.100"; + + // Use reflection to access the private method + var method = typeof(DotNetSdkInstaller).GetMethod("MeetsMinimumRequirement", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var result = (bool)method!.Invoke(null, new object[] { installedVersion, requiredVersion, requiredVersionString })!; + + Assert.False(result); + } + + [Fact] + public void MeetsMinimumRequirement_UsesStrictComparison_ForNonSingleFileAppHost() + { + // Test that other version requirements still use strict comparison + var installedVersion = SemVersion.Parse("9.0.301", SemVersionStyles.Strict); + var requiredVersion = SemVersion.Parse("9.0.302", SemVersionStyles.Strict); + var requiredVersionString = "9.0.302"; + + // Use reflection to access the private method + var method = typeof(DotNetSdkInstaller).GetMethod("MeetsMinimumRequirement", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var result = (bool)method!.Invoke(null, new object[] { installedVersion, requiredVersion, requiredVersionString })!; + + Assert.False(result); + } + private static IConfiguration CreateEmptyConfiguration() { return new ConfigurationBuilder().Build();