|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System.CommandLine;
using System.Text.Json;
using Microsoft.Deployment.DotNet.Releases;
using Microsoft.DotNet.Cli.Commands.Workload.Install;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using Microsoft.TemplateEngine.Cli.Commands;
namespace Microsoft.DotNet.Cli.Commands.Workload.Search;
internal class WorkloadSearchVersionsCommand : WorkloadCommandBase
{
private readonly ReleaseVersion _sdkVersion;
private readonly int _numberOfWorkloadSetsToTake;
private readonly string _workloadSetOutputFormat;
private readonly IInstaller _installer;
private readonly IEnumerable<string> _workloadVersion;
private readonly bool _includePreviews;
private readonly IWorkloadResolver _resolver;
public WorkloadSearchVersionsCommand(
ParseResult result,
IReporter reporter = null,
IWorkloadResolverFactory workloadResolverFactory = null,
IInstaller installer = null,
INuGetPackageDownloader nugetPackageDownloader = null,
IWorkloadResolver resolver = null,
ReleaseVersion sdkVersion = null) : base(result, CommonOptions.HiddenVerbosityOption, reporter, nugetPackageDownloader: nugetPackageDownloader)
{
workloadResolverFactory ??= new WorkloadResolverFactory();
if (!string.IsNullOrEmpty(result.GetValue(WorkloadSearchCommandParser.VersionOption)))
{
throw new GracefulException(CliCommandStrings.SdkVersionOptionNotSupported);
}
var creationResult = workloadResolverFactory.Create();
_sdkVersion = sdkVersion ?? creationResult.SdkVersion;
_resolver = resolver ?? creationResult.WorkloadResolver;
_numberOfWorkloadSetsToTake = result.GetValue(WorkloadSearchVersionsCommandParser.TakeOption);
_workloadSetOutputFormat = result.GetValue(WorkloadSearchVersionsCommandParser.FormatOption);
// For these operations, we don't have to respect 'msi' because they're equivalent between the two workload
// install types, and FileBased is much easier to work with.
_installer = installer ?? GenerateInstaller(Reporter, new SdkFeatureBand(_sdkVersion), _resolver, Verbosity, result.HasOption(SharedOptions.InteractiveOption));
_workloadVersion = result.GetValue(WorkloadSearchVersionsCommandParser.WorkloadVersionArgument);
_includePreviews = result.HasOption(WorkloadSearchVersionsCommandParser.IncludePreviewsOption) ?
result.GetValue(WorkloadSearchVersionsCommandParser.IncludePreviewsOption) :
new SdkFeatureBand(_sdkVersion).IsPrerelease;
}
private static IInstaller GenerateInstaller(IReporter reporter, SdkFeatureBand sdkFeatureBand, IWorkloadResolver workloadResolver, VerbosityOptions verbosity, bool interactive)
{
return new FileBasedInstaller(
reporter,
sdkFeatureBand,
workloadResolver,
CliFolderPathCalculator.DotnetUserProfileFolderPath,
nugetPackageDownloader: null,
dotnetDir: Path.GetDirectoryName(Environment.ProcessPath),
tempDirPath: null,
verbosity: verbosity,
packageSourceLocation: null,
restoreActionConfig: new RestoreActionConfig(interactive),
nugetPackageDownloaderVerbosity: VerbosityOptions.quiet
);
}
public override int Execute()
{
if (_workloadVersion.Count() == 0)
{
List<string> versions;
try
{
versions = GetVersions(_numberOfWorkloadSetsToTake);
}
catch (NuGetPackageNotFoundException)
{
Utils.Reporter.Error.WriteLine(string.Format(CliCommandStrings.NoWorkloadVersionsFound, new SdkFeatureBand(_sdkVersion)));
return 0;
}
if (_workloadSetOutputFormat?.Equals("json", StringComparison.OrdinalIgnoreCase) == true)
{
Reporter.WriteLine(JsonSerializer.Serialize(versions.Select(version => new Dictionary<string, string>()
{
{ "workloadVersion", version }
})));
}
else
{
Reporter.WriteLine(string.Join('\n', versions));
}
}
else if (_workloadVersion.Any(v => v.Contains('@')))
{
var versions = FindBestWorkloadSetsFromComponents()?.Take(_numberOfWorkloadSetsToTake);
if (versions is null)
{
return 0;
}
if (!versions.Any())
{
Reporter.WriteLine(string.Format(CliCommandStrings.WorkloadVersionWithSpecifiedManifestNotFound, string.Join(' ', _workloadVersion)));
}
else if (_workloadSetOutputFormat?.Equals("json", StringComparison.OrdinalIgnoreCase) == true)
{
Reporter.WriteLine(JsonSerializer.Serialize(versions.Select(version => version.ToDictionary(_ => "workloadVersion", v => v))));
}
else
{
Reporter.WriteLine(string.Join('\n', versions));
}
}
else
{
var workloadSet = _installer.GetWorkloadSetContents(_workloadVersion.Single());
if (_workloadSetOutputFormat?.Equals("json", StringComparison.OrdinalIgnoreCase) == true)
{
var set = new WorkloadSet() { ManifestVersions = workloadSet.ManifestVersions };
Reporter.WriteLine(JsonSerializer.Serialize(new Dictionary<string, Dictionary<string, string>>()
{
{ "manifestVersions", set.ToDictionaryForJson() }
}, new JsonSerializerOptions { WriteIndented = true }));
}
else
{
PrintableTable<KeyValuePair<ManifestId, (ManifestVersion Version, SdkFeatureBand FeatureBand)>> table = new();
table.AddColumn(CliCommandStrings.WorkloadManifestIdColumn, manifest => manifest.Key.ToString());
table.AddColumn(CliCommandStrings.WorkloadManifestFeatureBandColumn, manifest => manifest.Value.FeatureBand.ToString());
table.AddColumn(CliCommandStrings.WorkloadManifestVersionColumn, manifest => manifest.Value.Version.ToString());
table.PrintRows(workloadSet.ManifestVersions, l => Reporter.WriteLine(l));
}
}
return 0;
}
private List<string> GetVersions(int numberOfWorkloadSetsToTake)
{
return GetVersions(numberOfWorkloadSetsToTake, new SdkFeatureBand(_sdkVersion), _installer, _includePreviews, PackageDownloader, _resolver);
}
private static List<string> GetVersions(int numberOfWorkloadSetsToTake, SdkFeatureBand featureBand, IInstaller installer, bool includePreviews, INuGetPackageDownloader packageDownloader, IWorkloadResolver resolver)
{
installer ??= GenerateInstaller(Utils.Reporter.NullReporter, featureBand, resolver, VerbosityOptions.d, interactive: false);
var packageId = installer.GetManifestPackageId(new ManifestId("Microsoft.NET.Workloads"), featureBand);
return [.. packageDownloader.GetLatestPackageVersions(packageId, numberOfWorkloadSetsToTake, packageSourceLocation: null, includePreview: includePreviews)
.GetAwaiter().GetResult()
.Select(version => WorkloadSetVersion.FromWorkloadSetPackageVersion(featureBand, version.ToString()))];
}
private IEnumerable<string> FindBestWorkloadSetsFromComponents()
{
return FindBestWorkloadSetsFromComponents(new SdkFeatureBand(_sdkVersion), _installer, _includePreviews, PackageDownloader, _workloadVersion, _resolver, _numberOfWorkloadSetsToTake);
}
public static IEnumerable<string> FindBestWorkloadSetsFromComponents(SdkFeatureBand featureBand, IInstaller installer, bool includePreviews, INuGetPackageDownloader packageDownloader, IEnumerable<string> workloadVersions, IWorkloadResolver resolver, int numberOfWorkloadSetsToTake)
{
installer ??= GenerateInstaller(Utils.Reporter.NullReporter, featureBand, resolver, VerbosityOptions.d, interactive: false);
List<string> versions;
try
{
// 0 indicates 'give all versions'. Not all will match, so we don't know how many we will need
versions = GetVersions(0, featureBand, installer, includePreviews, packageDownloader, resolver);
}
catch (NuGetPackageNotFoundException)
{
Utils.Reporter.Error.WriteLine(string.Format(CliCommandStrings.NoWorkloadVersionsFound, featureBand));
return null;
}
var manifestIdsAndVersions = workloadVersions.Select(version =>
{
var split = version.Split('@');
return (new ManifestId(resolver.GetManifestFromWorkload(new WorkloadId(split[0])).Id), new ManifestVersion(split[1]));
});
// Since these are ordered by version (descending), the first is the highest version
return versions.Where(version =>
{
var manifestVersions = installer.GetWorkloadSetContents(version).ManifestVersions;
return manifestIdsAndVersions.All(tuple => manifestVersions.ContainsKey(tuple.Item1) && manifestVersions[tuple.Item1].Version.Equals(tuple.Item2));
}).Take(numberOfWorkloadSetsToTake);
}
}
|