From af4b8fa2d98dfe7b5c72f61de927db5606aa4f96 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Wed, 15 Apr 2026 19:30:50 -0700 Subject: [PATCH 1/2] Update nearest TFM version compatibility behavior to correctly handle CsWinRT 3.0 .1 TFMs --- .../NuGet.Frameworks/CompatibilityProvider.cs | 16 +++++++++++ .../CompatibilityTests.cs | 27 +++++++++++++++++++ .../FrameworkReducerTests.cs | 11 ++++++++ 3 files changed, 54 insertions(+) diff --git a/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs b/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs index 1f8c9af22c0..36ca119f66e 100644 --- a/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs +++ b/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs @@ -203,6 +203,22 @@ private static bool IsCompatibleWithTargetCore(NuGetFramework target, NuGetFrame if (target.IsNet5Era && candidate.HasPlatform) { + // If targeting a TFM with .1 revision for the Platform version, then that means + // it is targeting CsWinRT 3.0 and we need to make sure the candidate is also doing + // the same. Similarly, if it is not targeting .1, then we need to make sure the candidate + // is also not targeting it for it to be compatible. The .1 revision was added in .NET 10. + if (result + // Scope to .NET 10 and later for the Windows 10 TFM + && target.Version.Major >= 10 + && StringComparer.OrdinalIgnoreCase.Equals(target.Platform, "windows") + && target.PlatformVersion.Major >= 10 + && candidate.PlatformVersion.Major >= 10 + // Check if both are targeting the same version of CsWinRT support + && (target.PlatformVersion.Revision == 1) != (candidate.PlatformVersion.Revision == 1)) + { + result = false; + } + result = result && StringComparer.OrdinalIgnoreCase.Equals(target.Platform, candidate.Platform) && IsVersionCompatible(target.PlatformVersion, candidate.PlatformVersion); diff --git a/test/NuGet.Core.Tests/NuGet.Frameworks.Test/CompatibilityTests.cs b/test/NuGet.Core.Tests/NuGet.Frameworks.Test/CompatibilityTests.cs index 9aa6afa28da..2fe1efb3573 100644 --- a/test/NuGet.Core.Tests/NuGet.Frameworks.Test/CompatibilityTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Frameworks.Test/CompatibilityTests.cs @@ -106,6 +106,33 @@ public class CompatibilityTests [InlineData("net5.0-madeupname1.1.3.4.5.6", "net5.0", false)] [InlineData("net5.0", "net5.0-madeupname", false)] + // Compatibility based on Windows platform revision (CsWinRT 2.x vs 3.0 TFM) + // .0 project (CsWinRT 2.x) is incompatible with .1 candidate (CsWinRT 3.0) + [InlineData("net10.0-windows10.0.26100.0", "net10.0-windows10.0.17763.1", false)] + [InlineData("net10.0-windows10.0.26100.0", "net10.0-windows10.0.26100.1", false)] + [InlineData("net10.0-windows10.0.17763.0", "net10.0-windows10.0.17763.1", false)] + // .1 project (CsWinRT 3.0) is incompatible with .0 candidate (CsWinRT 2.x) + [InlineData("net10.0-windows10.0.26100.1", "net10.0-windows10.0.17763.0", false)] + [InlineData("net10.0-windows10.0.26100.1", "net10.0-windows10.0.26100.0", false)] + [InlineData("net10.0-windows10.0.17763.1", "net10.0-windows10.0.17763.0", false)] + // Same revision: compatible (standard version rules apply) + [InlineData("net10.0-windows10.0.26100.0", "net10.0-windows10.0.17763.0", true)] + [InlineData("net10.0-windows10.0.26100.1", "net10.0-windows10.0.17763.1", true)] + [InlineData("net10.0-windows10.0.17763.1", "net10.0-windows10.0.17763.1", true)] + [InlineData("net10.0-windows10.0.17763.0", "net10.0-windows10.0.17763.0", true)] + // Higher platform build still incompatible when candidate version > target + [InlineData("net10.0-windows10.0.17763.0", "net10.0-windows10.0.26100.0", false)] + [InlineData("net10.0-windows10.0.17763.1", "net10.0-windows10.0.26100.1", false)] + // Cross .NET version with matching revision is compatible + [InlineData("net10.0-windows10.0.26100.0", "net6.0-windows10.0.17763.0", true)] + // Rule does NOT apply to non-Windows platforms + [InlineData("net10.0-android10.0.26100.0", "net10.0-android10.0.17763.1", true)] + // Candidate without platform OS version is handled normally + [InlineData("net10.0-windows10.0.26100.0", "net10.0", true)] + [InlineData("net10.0-windows10.0.26100.1", "net10.0", true)] + [InlineData("net10.0-windows10.0.26100.0", "net10.0-windows", true)] + [InlineData("net10.0-windows10.0.26100.1", "net10.0-windows", true)] + // dotnet [InlineData("dotnet", "dotnet", true)] [InlineData("dotnet5.1", "dotnet", true)] diff --git a/test/NuGet.Core.Tests/NuGet.Frameworks.Test/FrameworkReducerTests.cs b/test/NuGet.Core.Tests/NuGet.Frameworks.Test/FrameworkReducerTests.cs index 52e77f3fbb9..844f5e8c328 100644 --- a/test/NuGet.Core.Tests/NuGet.Frameworks.Test/FrameworkReducerTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Frameworks.Test/FrameworkReducerTests.cs @@ -140,6 +140,17 @@ public class FrameworkReducerTests [InlineData("net7.0-android", "xamarin.mac,net6.0,net6.0-tizen,monoandroid,net5.0,netcoreapp3.1", "net6.0")] [InlineData("net7.0-android", "xamarin.mac,net7.0,net6.0-android,monoandroid,net5.0,netcoreapp3.1", "net7.0")] [InlineData("net7.0-android", "xamarin.mac,net7.0-tizen,net6.0-macos,", null)] + // Compatibility based on Windows platform revision (CsWinRT 2.x vs 3.0 TFM) + // .0 project skips .1 candidate, falls back to net6.0 + [InlineData("net10.0-windows10.0.26100.0", "net10.0-windows10.0.17763.1,net6.0-windows10.0.17763.0", "net6.0-windows10.0.17763.0")] + // .1 project skips .0 candidate, falls back to net6.0 (no .1 available in net6.0 era) + [InlineData("net10.0-windows10.0.26100.1", "net10.0-windows10.0.17763.0,net6.0-windows10.0.17763.0", null)] + // .1 project picks .1 candidate + [InlineData("net10.0-windows10.0.26100.1", "net10.0-windows10.0.17763.1,net6.0-windows10.0.17763.0", "net10.0-windows10.0.17763.1")] + // Same revision, picks higher .NET version + [InlineData("net10.0-windows10.0.26100.0", "net10.0-windows10.0.17763.0,net6.0-windows10.0.17763.0", "net10.0-windows10.0.17763.0")] + // .1 project picks version with no platform version + [InlineData("net10.0-windows10.0.26100.1", "net10.0-windows", "net10.0-windows")] // Additional tests [InlineData("dotnet5.5", "dotnet6.0,dotnet5.4,portable-net45+win8", "dotnet5.4")] [InlineData("dotnet7", "dotnet6.0,dotnet5.4,portable-net45+win8", "dotnet6.0")] From 44a3348b17bc00917c1eb3467f3bfeb8b0572322 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Fri, 17 Apr 2026 00:06:36 -0700 Subject: [PATCH 2/2] Update src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs Co-authored-by: Andy Zivkovic --- src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs b/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs index 36ca119f66e..9db0c512397 100644 --- a/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs +++ b/src/NuGet.Core/NuGet.Frameworks/CompatibilityProvider.cs @@ -203,7 +203,7 @@ private static bool IsCompatibleWithTargetCore(NuGetFramework target, NuGetFrame if (target.IsNet5Era && candidate.HasPlatform) { - // If targeting a TFM with .1 revision for the Platform version, then that means + // If targeting a TFM with .1 revision for the Windows Platform version, then that means // it is targeting CsWinRT 3.0 and we need to make sure the candidate is also doing // the same. Similarly, if it is not targeting .1, then we need to make sure the candidate // is also not targeting it for it to be compatible. The .1 revision was added in .NET 10.