Skip to content
Merged
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
@@ -0,0 +1,19 @@
// 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 enable

using System.Threading;
using System.Threading.Tasks;
using NuGet.Versioning;

namespace NuGet.CommandLine.XPlat.Commands.Package.Update
{
internal interface IVersionChooser
{
Task<NuGetVersion?> GetLatestVersionAsync(
string packageId,
ILoggerWithColor logger,
CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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 enable

using System.Collections.Generic;
using System.CommandLine.Parsing;
using NuGet.Versioning;

namespace NuGet.CommandLine.XPlat.Commands.Package.Update
{
internal record Package
{
public required string Id { get; init; }
public required VersionRange? VersionRange { get; init; }

internal static IReadOnlyList<Package> Parse(ArgumentResult result)
{
if (result.Tokens.Count == 0)
{
return [];
}

List<Package> packages = new List<Package>(result.Tokens.Count);

foreach (var token in result.Tokens)
{
string? packageId;
VersionRange? newVersion;
int separatorIndex = token.Value.IndexOf('@');
if (separatorIndex < 0)
{
packageId = token.Value;
newVersion = null;
}
else
{
packageId = token.Value.Substring(0, separatorIndex);
string versionString = token.Value.Substring(separatorIndex + 1);
if (string.IsNullOrEmpty(versionString))
{
result.AddError(Messages.Error_MissingVersion(token.Value));
return [];
}
if (!VersionRange.TryParse(versionString, out newVersion))
{
result.AddError(Messages.Error_InvalidVersionRange(versionString));
return [];
}
}

var package = new Package
{
Id = packageId,
VersionRange = newVersion
};
packages.Add(package);
}

return packages;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ internal class PackageUpdateArgs
{
public required string Project { get; init; }

public IReadOnlyList<string>? Packages { get; init; }
public required IReadOnlyList<Package> Packages { get; init; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ internal static void Register(Command packageCommand, Func<ILoggerWithColor> get
{
var command = new DocumentedCommand("update", Strings.PackageUpdateCommand_Description, "https://aka.ms/dotnet/package/update");

var packagesArguments = new Argument<List<string>>("packages")
var packagesArguments = new Argument<IReadOnlyList<Package>>("packages")
{
Description = Strings.PackageUpdate_PackageArgumentDescription,
Arity = ArgumentArity.ZeroOrMore,
CustomParser = Package.Parse
};
command.Arguments.Add(packagesArguments);

Expand All @@ -49,7 +51,7 @@ internal static void Register(Command packageCommand, Func<ILoggerWithColor> get
{
var logger = getLogger();
var project = args.GetValue(projectOption);
var packages = args.GetValue(packagesArguments);
var packages = args.GetValue(packagesArguments) ?? [];

var commandArgs = new PackageUpdateArgs
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ internal static async Task<int> Run(PackageUpdateArgs args, ILoggerWithColor log
// Source provider will be needed to find the package version and to restore, so create it here.
CachingSourceProvider sourceProvider = new CachingSourceProvider(new PackageSourceProvider(settings));
using SourceCacheContext sourceCacheContext = new();
(PackageDependency? packageToUpdate, List<NuGetFramework>? packageTfms) = await GetPackageToUpdateAsync(args.Packages, dgSpec.Projects.Single(), sourceProvider, settings, sourceCacheContext, logger, cancellationToken);
var versionChooser = new VersionChooser(sourceProvider, settings, sourceCacheContext);
(PackageDependency? packageToUpdate, List<NuGetFramework>? packageTfms) = await GetPackageToUpdateAsync(args.Packages, dgSpec.Projects.Single(), versionChooser, settings, logger, cancellationToken);

if (packageToUpdate is null)
{
Expand Down Expand Up @@ -153,12 +154,11 @@ private static async Task<RestoreResultPair> PreviewUpdatePackageReferenceAsync(
return restoreResult.Single();
}

private static async Task<(PackageDependency?, List<NuGetFramework>?)> GetPackageToUpdateAsync(
IReadOnlyList<string>? packages,
internal static async Task<(PackageDependency?, List<NuGetFramework>?)> GetPackageToUpdateAsync(
IReadOnlyList<Package> packages,
PackageSpec project,
CachingSourceProvider sourceProvider,
IVersionChooser versionChooser,
ISettings settings,
SourceCacheContext sourceCacheContext,
ILoggerWithColor logger,
CancellationToken cancellationToken)
{
Expand All @@ -181,15 +181,15 @@ private static async Task<RestoreResultPair> PreviewUpdatePackageReferenceAsync(
return (null, null);
}

var packageId = packages.Single();
var package = packages.Single();

VersionRange? existingVersion = null;
List<NuGetFramework>? frameworks = null;
foreach (var tfm in project.TargetFrameworks)
{
foreach (var dependency in tfm.Dependencies)
{
if (string.Equals(packageId, dependency.Name, StringComparison.OrdinalIgnoreCase))
if (string.Equals(package.Id, dependency.Name, StringComparison.OrdinalIgnoreCase))
{
if (frameworks is null)
{
Expand All @@ -201,11 +201,11 @@ private static async Task<RestoreResultPair> PreviewUpdatePackageReferenceAsync(
if (project.RestoreMetadata.CentralPackageFloatingVersionsEnabled)
{
if (!tfm.CentralPackageVersions.TryGetValue(
packageId,
package.Id,
out CentralPackageVersion? centralVersion))
{
logger.LogMinimal(
Messages.Error_CouldNotFindPackageVersionForCpmPackage(packageId),
Messages.Error_CouldNotFindPackageVersionForCpmPackage(package.Id),
ConsoleColor.Red);
return (null, null);
}
Expand All @@ -225,7 +225,7 @@ private static async Task<RestoreResultPair> PreviewUpdatePackageReferenceAsync(
if (tfmVersionRange != existingVersion)
{
logger.LogMinimal(
Messages.Unsupported_UpdatePackageWithDifferentPerTfmVersions(packageId, project.FilePath),
Messages.Unsupported_UpdatePackageWithDifferentPerTfmVersions(package.Id, project.FilePath),
ConsoleColor.Red);
return (null, null);
}
Expand All @@ -236,31 +236,43 @@ private static async Task<RestoreResultPair> PreviewUpdatePackageReferenceAsync(

if (existingVersion is null)
{
logger.LogMinimal(Messages.Error_PackageNotReferenced(packageId, project.FilePath), ConsoleColor.Red);
logger.LogMinimal(Messages.Error_PackageNotReferenced(package.Id, project.FilePath), ConsoleColor.Red);
return (null, null);
}

NuGetVersion? highestVersion = await VersionChooser.GetLatestVersionAsync(
packageId,
sourceProvider,
settings,
sourceCacheContext,
logger,
cancellationToken);

if (highestVersion is null)
VersionRange newVersion;
if (package.VersionRange is null)
{
logger.LogMinimal(Messages.Error_NoVersionsAvailable(packageId), ConsoleColor.Red);
return (null, null);
}
NuGetVersion? highestVersion = await versionChooser.GetLatestVersionAsync(
package.Id,
logger,
cancellationToken);

if (highestVersion is null)
{
logger.LogMinimal(Messages.Error_NoVersionsAvailable(package.Id), ConsoleColor.Red);
return (null, null);
}

if (existingVersion.MinVersion == highestVersion)
{
logger.LogMinimal(Messages.Warning_AlreadyHighestVersion(package.Id, highestVersion.OriginalVersion, project.FilePath), ConsoleColor.Red);
return (null, null);
}

if (existingVersion.MinVersion == highestVersion)
newVersion = new VersionRange(highestVersion);
}
else
{
logger.LogMinimal(Messages.Warning_AlreadyHighestVersion(packageId, highestVersion.OriginalVersion, project.FilePath), ConsoleColor.Red);
return (null, null);
newVersion = package.VersionRange;
if (newVersion == existingVersion)
{
logger.LogMinimal(Messages.Warning_AlreadyUsingSameVersion(package.Id, newVersion.OriginalString), ConsoleColor.Red);
return (null, null);
}
}

PackageDependency packageToUpdate = new(packageId, new VersionRange(highestVersion));
PackageDependency packageToUpdate = new(package.Id, newVersion);

return (packageToUpdate, frameworks);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,36 @@
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;

namespace NuGet.CommandLine.XPlat.Commands.Package.Update
{
internal static class VersionChooser
internal class VersionChooser : IVersionChooser
{
internal static async Task<NuGetVersion?> GetLatestVersionAsync(
string packageId,
CachingSourceProvider sourceProvider,
private readonly ISourceRepositoryProvider _sourceProvider;
private readonly ISettings _settings;
private readonly SourceCacheContext _sourceCacheContext;

public VersionChooser(
ISourceRepositoryProvider sourceProvider,
ISettings settings,
SourceCacheContext sourceCacheContext,
SourceCacheContext sourceCacheContext)
{
_sourceProvider = sourceProvider;
_settings = settings;
_sourceCacheContext = sourceCacheContext;
}

public async Task<NuGetVersion?> GetLatestVersionAsync(
string packageId,
ILoggerWithColor logger,
CancellationToken cancellationToken)
{
var sources = new List<SourceRepository>();
foreach (PackageSource packageSource in SettingsUtility.GetEnabledSources(settings).NoAllocEnumerate())
foreach (PackageSource packageSource in SettingsUtility.GetEnabledSources(_settings).NoAllocEnumerate())
{
SourceRepository sourceRepository = sourceProvider.CreateRepository(packageSource);
SourceRepository sourceRepository = _sourceProvider.CreateRepository(packageSource);
sources.Add(sourceRepository);
}

Expand All @@ -37,7 +47,7 @@ internal static class VersionChooser
{
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, sourceCacheContext, logger, cancellationToken));
lookups[source] = Task.Run(() => FindHighestPackageVersionAsync(sourceRepository, packageId, _sourceCacheContext, logger, cancellationToken));
}

await Task.WhenAll(lookups);
Expand Down
15 changes: 15 additions & 0 deletions src/NuGet.Core/NuGet.CommandLine.XPlat/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,20 @@ internal static string Warning_AlreadyHighestVersion(string packageId, string ve
{
return string.Format(CultureInfo.CurrentCulture, Strings.Warning_AlreadyHighestVersion, packageId, version, projectPath);
}

internal static string Warning_AlreadyUsingSameVersion(string packageId, string version)
{
return string.Format(CultureInfo.CurrentCulture, Strings.Warning_AlreadyUsingSameVersion, packageId, version);
}

internal static string Error_MissingVersion(string packageId)
{
return string.Format(CultureInfo.CurrentCulture, Strings.Error_MissingVersion, packageId);
}

internal static string Error_InvalidVersionRange(string input)
{
return string.Format(CultureInfo.CurrentCulture, Strings.Error_InvalidVersionRange, input);
}
}
}
36 changes: 36 additions & 0 deletions src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1024,4 +1024,20 @@ Do not translate "PackageVersion"</comment>
<data name="Unsupported_UpgradeAllPackages" xml:space="preserve">
<value>Unsupported: Upgrading all packages in a project is not yet supported</value>
</data>
<data name="Warning_AlreadyUsingSameVersion" xml:space="preserve">
<value>Package {0} is already referencing version {1}</value>
<comment>0 - package id
1 - version string</comment>
</data>
<data name="Error_MissingVersion" xml:space="preserve">
<value>Missing version from {0}</value>
<comment>0 - package id </comment>
</data>
<data name="Error_InvalidVersionRange" xml:space="preserve">
<value>Invalid version range '{0}'</value>
<comment>0 - string provided by customer</comment>
</data>
<data name="PackageUpdate_PackageArgumentDescription" xml:space="preserve">
<value>Package reference in the form of a package identifier like 'Newtonsoft.Json' or package identifier and version separated by '@' like 'Newtonsoft.Json@13.0.3'.</value>
</data>
</root>
Loading