in src/NuGet.Core/NuGet.Frameworks/FrameworkReducer.cs [69:226]
private NuGetFramework? GetNearestInternal(NuGetFramework framework, IEnumerable<NuGetFramework> possibleFrameworks)
{
NuGetFramework? nearest = null;
// Unsupported frameworks always lose, throw them out unless it's all we were given
if (possibleFrameworks.Any(e => e != NuGetFramework.UnsupportedFramework))
{
possibleFrameworks = possibleFrameworks.Where(e => e != NuGetFramework.UnsupportedFramework);
}
// Try exact matches first
nearest = possibleFrameworks.FirstOrDefault(f => NuGetFrameworkFullComparer.Instance.Equals(framework, f));
if (nearest == null)
{
// Elimate non-compatible frameworks
var compatible = possibleFrameworks.Where(f => _compat.IsCompatible(framework, f));
// Remove lower versions of compatible frameworks
var reduced = ReduceUpwards(compatible);
bool isNet6Era = framework.IsNet5Era && framework.Version.Major >= 6;
// Reduce to the same framework name if possible, with an exception for Xamarin, MonoAndroid and Tizen when net6.0+
if (reduced.Count() > 1 && reduced.Any(f => NuGetFrameworkNameComparer.Instance.Equals(f, framework)))
{
reduced = reduced.Where(f =>
{
if (isNet6Era && framework.HasPlatform && (
f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase)
|| f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase)
))
{
return true;
}
else
{
return NuGetFrameworkNameComparer.Instance.Equals(f, framework);
}
});
}
// PCL reduce
if (reduced.Count() > 1)
{
// if we have a pcl and non-pcl mix, throw out the pcls
if (reduced.Any(f => f.IsPCL)
&& reduced.Any(f => !f.IsPCL))
{
reduced = reduced.Where(f => !f.IsPCL);
}
else if (reduced.All(f => f.IsPCL))
{
// decide between PCLs
if (framework.IsPCL)
{
reduced = GetNearestPCLtoPCL(framework, reduced);
}
else
{
reduced = GetNearestNonPCLtoPCL(framework, reduced);
}
if (reduced.Count() > 1)
{
// For scenarios where we are unable to decide between PCLs, choose the PCL with the
// least frameworks. Less frameworks means less compatibility which means it is nearer to the target.
reduced = new NuGetFramework[] { GetBestPCL(reduced)! };
}
}
}
// Packages based framework reduce, only if the project is not packages based
if (reduced.Count() > 1
&& !framework.IsPackageBased
&& reduced.Any(f => f.IsPackageBased)
&& reduced.Any(f => !f.IsPackageBased))
{
// If we have a packages based and non-packages based mix, throw out the packages based frameworks.
// This situation is unlikely but it could happen with framework mappings that do not provide
// a relationship between the frameworks and the compatible packages based frameworks.
// Ex: net46, dotnet -> net46
reduced = reduced.Where(f => !f.IsPackageBased);
}
// Profile reduce
if (reduced.Count() > 1
&& !reduced.Any(f => f.IsPCL))
{
// Prefer the same framework and profile
if (framework.HasProfile)
{
var sameProfile = reduced.Where(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f)
&& StringComparer.OrdinalIgnoreCase.Equals(framework.Profile, f.Profile));
if (sameProfile.Any())
{
reduced = sameProfile;
}
}
// Prefer frameworks without profiles
if (reduced.Count() > 1
&& reduced.Any(f => f.HasProfile)
&& reduced.Any(f => !f.HasProfile))
{
reduced = reduced.Where(f => !f.HasProfile);
}
}
// Platforms reduce
if (reduced.Count() > 1
&& framework.HasPlatform)
{
if (!isNet6Era || reduced.Any(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f) && f.Version.Major >= 6))
{
// Prefer the highest framework version, likely to be the non-platform specific option.
reduced = reduced.Where(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f)).GroupBy(f => f.Version).OrderByDescending(f => f.Key).First();
}
else if (isNet6Era && reduced.Any(f =>
{
return f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase)
|| f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase);
}))
{
// We have a special case for *some* Xamarin-related frameworks here. For specific precedence rules, please see:
// https://github.com/dotnet/designs/blob/main/accepted/2021/net6.0-tfms/net6.0-tfms.md#compatibility-rules
reduced = reduced.GroupBy(f => f.Framework).OrderByDescending(f => f.Key).First(f =>
{
NuGetFramework first = f.First();
return first.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase)
|| first.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase);
});
}
}
// if we have reduced down to a single framework, use that
if (reduced.Count() == 1)
{
nearest = reduced.Single();
}
// this should be a very rare occurrence
// at this point we are unable to decide between the remaining frameworks in any useful way
// just take the first one by rev alphabetical order if we can't narrow it down at all
if (nearest == null && reduced.Any())
{
// Sort by precedence rules, then by name in the case of a tie
nearest = reduced
.OrderBy(f => f, new FrameworkPrecedenceSorter(_mappings, false))
.ThenByDescending(f => f, NuGetFrameworkSorter.Instance)
.ThenBy(f => f.GetHashCode())
.First();
}
}
return nearest;
}