|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NuGet.Packaging.Core;
using NuGet.Versioning;
namespace NuGet.Resolver
{
public class ResolverComparer : IComparer<ResolverPackage>
{
private readonly DependencyBehavior _dependencyBehavior;
private readonly HashSet<PackageIdentity> _preferredVersions;
private readonly HashSet<string> _targetIds;
private readonly IVersionComparer _versionComparer;
private readonly PackageIdentityComparer _identityComparer;
private readonly Dictionary<string, NuGetVersion> _installedVersions;
public ResolverComparer(DependencyBehavior dependencyBehavior,
HashSet<PackageIdentity> preferredVersions,
HashSet<string> targetIds)
{
_dependencyBehavior = dependencyBehavior;
_preferredVersions = preferredVersions;
_targetIds = targetIds;
_versionComparer = VersionComparer.Default;
_identityComparer = PackageIdentity.Comparer;
_installedVersions = new Dictionary<string, NuGetVersion>();
if (_preferredVersions != null)
{
foreach (var package in _preferredVersions)
{
if (package.Version != null)
{
_installedVersions.Add(package.Id, package.Version);
}
}
}
}
public int Compare(ResolverPackage x, ResolverPackage y)
{
if (Object.ReferenceEquals(x, y))
{
return 0;
}
Debug.Assert(string.Equals(x.Id, y.Id, StringComparison.OrdinalIgnoreCase));
// The absent package comes first in the sort order
var isXAbsent = x.Absent;
var isYAbsent = y.Absent;
if (isXAbsent && !isYAbsent)
{
return -1;
}
if (!isXAbsent && isYAbsent)
{
return 1;
}
if (isXAbsent && isYAbsent)
{
return 0;
}
if (_preferredVersions != null)
{
//Already installed packages come next in the sort order.
var xInstalled = _preferredVersions.Contains(x, _identityComparer);
var yInstalled = _preferredVersions.Contains(y, _identityComparer);
if (xInstalled && !yInstalled)
{
return -1;
}
if (!xInstalled && yInstalled)
{
return 1;
}
}
//Prefer listed packages over unlisted
if (x.Listed && !y.Listed)
{
return -1;
}
if (!x.Listed && y.Listed)
{
return 1;
}
var xv = x.Version;
var yv = y.Version;
var packageBehavior = _dependencyBehavior;
// for new packages use the highest version
if (_targetIds.Contains(x.Id, StringComparer.OrdinalIgnoreCase))
{
packageBehavior = DependencyBehavior.Highest;
}
// stay as close to the installed version as possible
// Choose upgrades over downgrades
// For downgrades choose the highest version
//
// Example:
// 1.0.0
// 1.1.0
// 2.0.0 - installed
// 2.1.0
// 3.0.0
// Order: 2.0.0, 2.1.0, 3.0.0, 1.1.0, 1.0.0
if (packageBehavior != DependencyBehavior.Highest
&& packageBehavior != DependencyBehavior.Ignore)
{
NuGetVersion installedVersion = null;
if (_installedVersions.TryGetValue(x.Id, out installedVersion))
{
var xvDowngrade = _versionComparer.Compare(xv, installedVersion) < 0;
var yvDowngrade = _versionComparer.Compare(yv, installedVersion) < 0;
// the upgrade is preferred over the downgrade
if (xvDowngrade && !yvDowngrade)
{
return 1;
}
else if (!xvDowngrade && yvDowngrade)
{
return -1;
}
else if (xvDowngrade && yvDowngrade)
{
// when both are downgrades prefer the highest
return -1 * _versionComparer.Compare(xv, yv);
}
}
}
// Normal
switch (packageBehavior)
{
case DependencyBehavior.Lowest:
{
return _versionComparer.Compare(xv, yv);
}
case DependencyBehavior.Ignore:
case DependencyBehavior.Highest:
return -1 * _versionComparer.Compare(xv, yv);
case DependencyBehavior.HighestMinor:
{
if (_versionComparer.Equals(xv, yv))
{
return 0;
}
// Take the lowest Major, then the Highest Minor and Patch
return new[] { x, y }.OrderBy(p => p.Version.Major)
.ThenByDescending(p => p.Version.Minor)
.ThenByDescending(p => p.Version.Patch).FirstOrDefault() == x ? -1 : 1;
}
case DependencyBehavior.HighestPatch:
{
if (_versionComparer.Equals(xv, yv))
{
return 0;
}
// Take the lowest Major and Minor, then the Highest Patch
return new[] { x, y }.OrderBy(p => p.Version.Major)
.ThenBy(p => p.Version.Minor)
.ThenByDescending(p => p.Version.Patch).FirstOrDefault() == x ? -1 : 1;
}
default:
return _versionComparer.Compare(xv, yv);
}
}
}
}
|