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
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ void UpdatePackageReference(
ILogger logger,
CancellationToken cancellationToken);

/// <summary>Gets the highest package version matching the allowed update range.</summary>
/// <param name="packageId">The package name to check.</param>
/// <param name="includePrerelease">Whether prerelease packages should be considered.</param>
/// <param name="allowedSources">Package source mapping sources configured for this package name.
/// <see langword="null"/> if package source mapping is not configured.</param>
/// <param name="allowedVersions">The version range allowed by the project reference.</param>
/// <param name="logger">Output logger</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The <see cref="NuGetVersion"/> of the highest version of the package available in the allowed range.
/// <see langword="null"/> if no versions of the package are found.</returns>
Task<NuGetVersion?> GetLatestVersionAsync(
string packageId,
bool includePrerelease,
IReadOnlyList<string>? allowedSources,
VersionRange? allowedVersions,
ILogger logger,
CancellationToken cancellationToken);

/// <summary>Gets the vulnerability database from the source(s) vulnerability info resource. Uses
/// audit sources if the settings have any configured, otherwise uses package sources, just like restore.</summary>
/// <param name="logger">The output logger.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,14 @@ await Parallel.ForEachAsync(dgSpec.Restore, parallelOptions, async (projectPath,
VersionRange upgradeVersion;
if (package.VersionRange is not null)
{
if (!IsVersionRangeAllowed(package.VersionRange, existingVersion))
{
var message = $"Package '{package.Id}' version '{package.VersionRange}' is outside the allowed update range '{existingVersion}'.";
logger.LogMinimal(message, ConsoleColor.Red);
hasErrors = true;
continue;
}

upgradeVersion = package.VersionRange;
if (upgradeVersion == existingVersion)
{
Expand All @@ -440,15 +448,19 @@ await Parallel.ForEachAsync(dgSpec.Restore, parallelOptions, async (projectPath,
else
{
bool usePrerelease = existingVersion.HasLowerBound && existingVersion.MinVersion.IsPrerelease;
var latestVersion = await packageUpdateIO.GetLatestVersionAsync(package.Id, usePrerelease, mappedSources, NullLogger.Instance, cancellationToken);
var allowedUpdateRange = GetAllowedUpdateRange(existingVersion);
var latestVersion = allowedUpdateRange == null
? await packageUpdateIO.GetLatestVersionAsync(package.Id, usePrerelease, mappedSources, NullLogger.Instance, cancellationToken)
: await packageUpdateIO.GetLatestVersionAsync(package.Id, usePrerelease, mappedSources, allowedUpdateRange, NullLogger.Instance, cancellationToken);

if (latestVersion is null)
{
logger.LogMinimal(Messages.Error_NoVersionsAvailable(package.Id), ConsoleColor.Red);
hasErrors = true;
continue;
}

upgradeVersion = VersionRange.Parse(latestVersion.OriginalVersion!);
upgradeVersion = GetVersionRangeForUpdate(existingVersion, latestVersion);
if (upgradeVersion == existingVersion)
{
logger.LogMinimal(Messages.Warning_AlreadyHighestVersion(package.Id, latestVersion.OriginalVersion!, project.FilePath), ConsoleColor.Yellow);
Expand Down Expand Up @@ -508,17 +520,10 @@ private static (VersionRange? version, List<string> targetFrameworks)
}

VersionRange tfmVersionRange;
if (project.RestoreMetadata.CentralPackageFloatingVersionsEnabled)
if (tfm.CentralPackageVersions.TryGetValue(
packageId,
out CentralPackageVersion? centralVersion))
{
if (!tfm.CentralPackageVersions.TryGetValue(
packageId,
out CentralPackageVersion? centralVersion))
{
logger.LogMinimal(
Messages.Error_CouldNotFindPackageVersionForCpmPackage(packageId),
ConsoleColor.Red);
return (null, []);
}
tfmVersionRange = centralVersion.VersionRange;
}
else
Expand Down Expand Up @@ -547,6 +552,42 @@ private static (VersionRange? version, List<string> targetFrameworks)
return (existingVersion, frameworks);
}

private static VersionRange? GetAllowedUpdateRange(VersionRange currentVersionRange)
{
return currentVersionRange.HasUpperBound || currentVersionRange.IsFloating
? currentVersionRange
: null;
}

private static VersionRange GetVersionRangeForUpdate(VersionRange currentVersionRange, NuGetVersion selectedVersion)
{
if (currentVersionRange.IsFloating)
{
return currentVersionRange;
}

if (currentVersionRange.HasUpperBound)
{
return new VersionRange(
minVersion: selectedVersion,
includeMinVersion: true,
maxVersion: currentVersionRange.MaxVersion,
includeMaxVersion: currentVersionRange.IsMaxInclusive);
}

return VersionRange.Parse(selectedVersion.OriginalVersion!);
}

private static bool IsVersionRangeAllowed(VersionRange requestedVersionRange, VersionRange currentVersionRange)
{
if (!currentVersionRange.HasUpperBound && !currentVersionRange.IsFloating)
{
return true;
}

return requestedVersionRange.IsSubSetOrEqualTo(currentVersionRange);
}

internal static async Task<(List<PackageUpdateResult>? packagesToUpdate, HashSet<string> scannedPackages)> SelectAllPackagesWithUpdatesAsync(
PackageSpec project,
ILoggerWithColor logger,
Expand Down Expand Up @@ -577,7 +618,10 @@ private static (VersionRange? version, List<string> targetFrameworks)
// package.identity.VersionRange is the project's referenced version.
Debug.Assert(package.identity.VersionRange != null);
bool usePrerelease = package.identity.VersionRange.HasLowerBound && package.identity.VersionRange.MinVersion.IsPrerelease;
var latestVersion = await packageUpdateIO.GetLatestVersionAsync(package.identity.Id, usePrerelease, mappedSources, NullLogger.Instance, cancellationToken);
var allowedUpdateRange = GetAllowedUpdateRange(package.identity.VersionRange);
var latestVersion = allowedUpdateRange == null
? await packageUpdateIO.GetLatestVersionAsync(package.identity.Id, usePrerelease, mappedSources, NullLogger.Instance, cancellationToken)
: await packageUpdateIO.GetLatestVersionAsync(package.identity.Id, usePrerelease, mappedSources, allowedUpdateRange, NullLogger.Instance, cancellationToken);

if (latestVersion is null)
{
Expand All @@ -586,7 +630,7 @@ private static (VersionRange? version, List<string> targetFrameworks)
continue;
}

var upgradeVersion = VersionRange.Parse(latestVersion.OriginalVersion!);
var upgradeVersion = GetVersionRangeForUpdate(package.identity.VersionRange, latestVersion);
if (upgradeVersion.ToString() == package.identity.VersionRange.ToString())
{
// Already using the highest version.
Expand Down Expand Up @@ -627,7 +671,9 @@ private static (VersionRange? version, List<string> targetFrameworks)
continue;
}

if (existing.Item1 != dependency.LibraryRange.VersionRange)
VersionRange versionRange = GetDependencyVersionRange(tfm, dependency);

if (existing.Item1 != versionRange)
{
logger.LogMinimal(
Messages.Unsupported_UpdatePackageWithDifferentPerTfmVersions(dependency.Name, project.FilePath),
Expand All @@ -640,7 +686,7 @@ private static (VersionRange? version, List<string> targetFrameworks)
}
else
{
VersionRange version = dependency.LibraryRange.VersionRange ?? VersionRange.All;
VersionRange version = GetDependencyVersionRange(tfm, dependency);
List<string> tfms = [tfm.TargetAlias];
allPackages[dependency.Name] = (version, tfms, hasError: false);
}
Expand All @@ -663,6 +709,16 @@ private static (VersionRange? version, List<string> targetFrameworks)
return result;
}

private static VersionRange GetDependencyVersionRange(TargetFrameworkInformation tfm, LibraryDependency dependency)
{
if (tfm.CentralPackageVersions.TryGetValue(dependency.Name, out CentralPackageVersion? centralVersion))
{
return centralVersion.VersionRange;
}

return dependency.LibraryRange.VersionRange ?? VersionRange.All;
}

private static DependencyGraphSpec GetUpdatedDependencyGraphSpec(DependencyGraphSpec currentDgSpec, Dictionary<string, List<PackageUpdateResult>> projectPackageUpdates)
{
var updatedDgSpec = new DependencyGraphSpec();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,32 @@ public void UpdatePackageReference(PackageSpec updatedPackageSpec, IPackageUpdat
IReadOnlyList<string>? allowedSources,
ILogger logger,
CancellationToken cancellationToken)
{
return await GetLatestVersionAsync(
packageId,
includePrerelease,
allowedSources,
allowedVersions: null,
logger,
cancellationToken);
}

/// <inheritdoc cref="IPackageUpdateIO.GetLatestVersionAsync(string, bool, IReadOnlyList{string}?, VersionRange?, ILogger, CancellationToken)"/>
public async Task<NuGetVersion?> GetLatestVersionAsync(
string packageId,
bool includePrerelease,
IReadOnlyList<string>? allowedSources,
VersionRange? allowedVersions,
ILogger logger,
CancellationToken cancellationToken)
{
var sources = GetSourcesForPackage(packageId, allowedSources);
var lookups = new Task<NuGetVersion?>[sources.Count];
for (int source = 0; source < sources.Count; source++)
{
SourceRepository sourceRepository = sources[source];
// If package source is a local folder feed, it might not actually be async
lookups[source] = Task.Run(() => FindHighestPackageVersionAsync(sourceRepository, packageId, includePrerelease, logger, cancellationToken));
lookups[source] = Task.Run(() => FindHighestPackageVersionAsync(sourceRepository, packageId, includePrerelease, allowedVersions, logger, cancellationToken));
}

await Task.WhenAll(lookups);
Expand Down Expand Up @@ -424,6 +442,7 @@ bool PackageHasKnownVulnerability(PackageIdentity package)
SourceRepository source,
string packageId,
bool includePrerelease,
VersionRange? allowedVersions,
ILogger logger,
CancellationToken cancellationToken)
{
Expand All @@ -442,7 +461,11 @@ bool PackageHasKnownVulnerability(PackageIdentity package)
return null;
}

NuGetVersion highestVersion = packageDetails.Max(p => p.Identity.Version)!;
NuGetVersion? highestVersion = packageDetails
.Select(p => p.Identity.Version)
.Where(version => allowedVersions == null || allowedVersions.Satisfies(version))
.Max();

return highestVersion;
}

Expand Down
52 changes: 48 additions & 4 deletions src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -863,10 +863,14 @@ private async Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesForBuil

if (resolvedPackage != null && resolvedPackage.LatestVersion != null && resolvedPackage.LatestVersion > installedPackage.PackageIdentity.Version)
{
var packageIdentity = new PackageIdentity(installedPackage.PackageIdentity.Id, resolvedPackage.LatestVersion);
TryGetVersionRangeForUpdate(installedPackage, packageIdentity, out VersionRange versionRange);

lowLevelActions.Add(NuGetProjectAction.CreateInstallProjectAction(
new PackageIdentity(installedPackage.PackageIdentity.Id, resolvedPackage.LatestVersion),
packageIdentity,
primarySources.FirstOrDefault(),
nuGetProject));
nuGetProject,
versionRange));
}
}
}
Expand Down Expand Up @@ -949,8 +953,11 @@ private async Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesForBuil
// if the package is not currently installed, or the installed one is auto referenced ignore it
if (installed != null && !autoReferenced)
{
lowLevelActions.Add(NuGetProjectAction.CreateInstallProjectAction(packageIdentity,
primarySources.FirstOrDefault(), nuGetProject));
if (TryGetVersionRangeForUpdate(installed, packageIdentity, out VersionRange versionRange))
{
lowLevelActions.Add(NuGetProjectAction.CreateInstallProjectAction(packageIdentity,
primarySources.FirstOrDefault(), nuGetProject, versionRange));
}
}
}

Expand Down Expand Up @@ -992,6 +999,43 @@ private static bool IsPackageReferenceAutoReferenced(PackageReference package)
return buildPackageReference?.Dependency?.AutoReferenced == true;
}

private static bool TryGetVersionRangeForUpdate(PackageReference installedPackage, PackageIdentity packageIdentity, out VersionRange versionRange)
{
versionRange = null;

if (installedPackage?.AllowedVersions == null)
{
return true;
}

var allowedVersions = installedPackage.AllowedVersions;

if (allowedVersions.IsFloating)
{
if (!allowedVersions.Satisfies(packageIdentity.Version))
{
return false;
}

versionRange = allowedVersions;
}
else if (allowedVersions.HasUpperBound)
{
if (!allowedVersions.Satisfies(packageIdentity.Version))
{
return false;
}

versionRange = new VersionRange(
minVersion: packageIdentity.Version,
includeMinVersion: true,
maxVersion: allowedVersions.MaxVersion,
includeMaxVersion: allowedVersions.IsMaxInclusive);
}

return true;
}

/// <summary>
/// Update Package logic specific to classic style NuGet projects
/// </summary>
Expand Down
Loading