File: Commands\Tool\Restore\ToolRestoreCommand.cs
Web Access
Project: ..\..\..\src\Cli\dotnet\dotnet.csproj (dotnet)
// 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 Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.ToolManifest;
using Microsoft.DotNet.Cli.ToolPackage;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.Frameworks;
using NuGet.Versioning;
 
namespace Microsoft.DotNet.Cli.Commands.Tool.Restore;
 
internal class ToolRestoreCommand : CommandBase
{
    private readonly string _configFilePath;
    private readonly IReporter _errorReporter;
    private readonly ILocalToolsResolverCache _localToolsResolverCache;
    private readonly IToolManifestFinder _toolManifestFinder;
    private readonly IFileSystem _fileSystem;
    private readonly IReporter _reporter;
    private readonly string[] _sources;
    private readonly IToolPackageDownloader _toolPackageDownloader;
    private readonly VerbosityOptions _verbosity;
    private readonly RestoreActionConfig _restoreActionConfig;
 
    public ToolRestoreCommand(
        ParseResult result,
        IToolPackageDownloader toolPackageDownloader = null,
        IToolManifestFinder toolManifestFinder = null,
        ILocalToolsResolverCache localToolsResolverCache = null,
        IFileSystem fileSystem = null,
        IReporter reporter = null)
        : base(result)
    {
        if (toolPackageDownloader == null)
        {
            (IToolPackageStore,
                IToolPackageStoreQuery,
                IToolPackageDownloader downloader) toolPackageStoresAndInstaller
                    = ToolPackageFactory.CreateToolPackageStoresAndDownloader();
            _toolPackageDownloader = toolPackageStoresAndInstaller.downloader;
        }
        else
        {
            _toolPackageDownloader = toolPackageDownloader;
        }
 
        _toolManifestFinder
            = toolManifestFinder
              ?? new ToolManifestFinder(new DirectoryPath(Directory.GetCurrentDirectory()));
 
        _localToolsResolverCache = localToolsResolverCache ?? new LocalToolsResolverCache();
        _fileSystem = fileSystem ?? new FileSystemWrapper();
 
        _reporter = reporter ?? Reporter.Output;
        _errorReporter = reporter ?? Reporter.Error;
 
        _configFilePath = result.GetValue(ToolRestoreCommandParser.ConfigOption);
        _sources = result.GetValue(ToolRestoreCommandParser.AddSourceOption);
        _verbosity = result.GetValue(ToolRestoreCommandParser.VerbosityOption);
        if (!result.HasOption(ToolRestoreCommandParser.VerbosityOption) && result.GetValue(ToolCommandRestorePassThroughOptions.InteractiveRestoreOption))
        {
            _verbosity = VerbosityOptions.minimal;
        }
 
        _restoreActionConfig = new RestoreActionConfig(DisableParallel: result.GetValue(ToolCommandRestorePassThroughOptions.DisableParallelOption),
            NoCache: result.GetValue(ToolCommandRestorePassThroughOptions.NoCacheOption) || result.GetValue(ToolCommandRestorePassThroughOptions.NoHttpCacheOption),
            IgnoreFailedSources: result.GetValue(ToolCommandRestorePassThroughOptions.IgnoreFailedSourcesOption),
            Interactive: result.GetValue(ToolCommandRestorePassThroughOptions.InteractiveRestoreOption));
    }
 
    public override int Execute()
    {
        FilePath? customManifestFileLocation = GetCustomManifestFileLocation();
 
        FilePath? configFile = null;
        if (!string.IsNullOrEmpty(_configFilePath))
        {
            configFile = new FilePath(_configFilePath);
        }
 
        IReadOnlyCollection<ToolManifestPackage> packagesFromManifest;
        try
        {
            packagesFromManifest = _toolManifestFinder.Find(customManifestFileLocation);
        }
        catch (ToolManifestCannotBeFoundException e)
        {
            if (CommandLoggingContext.IsVerbose)
            {
                _reporter.WriteLine(string.Join(Environment.NewLine, e.VerboseMessage).Yellow());
            }
 
            _reporter.WriteLine(e.Message.Yellow());
            _reporter.WriteLine(CliCommandStrings.NoToolsWereRestored.Yellow());
            return 0;
        }
 
        var toolPackageRestorer = new ToolPackageRestorer(
            _toolPackageDownloader,
            _sources,
            overrideSources: [],
            _verbosity,
            _restoreActionConfig,
            _localToolsResolverCache,
            _fileSystem);
 
        ToolRestoreResult[] toolRestoreResults =
            [.. packagesFromManifest
                .AsEnumerable()
                .Select(package => toolPackageRestorer.InstallPackage(package, configFile))];
 
        Dictionary<RestoredCommandIdentifier, ToolCommand> downloaded =
            toolRestoreResults.Select(result => result.SaveToCache)
                .Where(item => item is not null)
                .ToDictionary(pair => pair.Value.restoredCommandIdentifier, pair => pair.Value.toolCommand);
 
        EnsureNoCommandNameCollision(downloaded);
 
        _localToolsResolverCache.Save(downloaded);
 
        return PrintConclusionAndReturn(toolRestoreResults);
    }
 
    private int PrintConclusionAndReturn(ToolRestoreResult[] toolRestoreResults)
    {
        if (toolRestoreResults.Any(r => !r.IsSuccess))
        {
            _reporter.WriteLine();
            _errorReporter.WriteLine(string.Join(
                Environment.NewLine,
                toolRestoreResults.Where(r => !r.IsSuccess).Select(r => r.Message)).Red());
 
            var successMessage = toolRestoreResults.Where(r => r.IsSuccess).Select(r => r.Message);
            if (successMessage.Any())
            {
                _reporter.WriteLine();
                _reporter.WriteLine(string.Join(Environment.NewLine, successMessage));
 
            }
 
            _errorReporter.WriteLine(Environment.NewLine +
                                     (toolRestoreResults.Any(r => r.IsSuccess)
                                         ? CliCommandStrings.RestorePartiallyFailed
                                         : CliCommandStrings.RestoreFailed).Red());
 
            return 1;
        }
        else
        {
            _reporter.WriteLine(string.Join(Environment.NewLine,
                toolRestoreResults.Where(r => r.IsSuccess).Select(r => r.Message)));
            _reporter.WriteLine();
            _reporter.WriteLine(CliCommandStrings.LocalToolsRestoreWasSuccessful.Green());
 
            return 0;
        }
    }
 
    private FilePath? GetCustomManifestFileLocation()
    {
        string customFile = _parseResult.GetValue(ToolRestoreCommandParser.ToolManifestOption);
        FilePath? customManifestFileLocation;
        if (!string.IsNullOrEmpty(customFile))
        {
            customManifestFileLocation = new FilePath(customFile);
        }
        else
        {
            customManifestFileLocation = null;
        }
 
        return customManifestFileLocation;
    }
 
    private static void EnsureNoCommandNameCollision(Dictionary<RestoredCommandIdentifier, ToolCommand> dictionary)
    {
        string[] errors = [.. dictionary
            .Select(pair => (pair.Key.PackageId, pair.Key.CommandName))
            .GroupBy(packageIdAndCommandName => packageIdAndCommandName.CommandName)
            .Where(grouped => grouped.Count() > 1)
            .Select(nonUniquePackageIdAndCommandNames =>
                string.Format(CliCommandStrings.PackagesCommandNameCollisionConclusion,
                    string.Join(Environment.NewLine,
                        nonUniquePackageIdAndCommandNames.Select(
                            p => "\t" + string.Format(
                                CliCommandStrings.PackagesCommandNameCollisionForOnePackage,
                                p.CommandName.Value,
                                p.PackageId.ToString())))))];
 
        if (errors.Any())
        {
            throw new ToolPackageException(string.Join(Environment.NewLine, errors));
        }
    }
 
    public struct ToolRestoreResult
    {
        public (RestoredCommandIdentifier restoredCommandIdentifier, ToolCommand toolCommand)? SaveToCache { get; }
        public bool IsSuccess { get; }
        public string Message { get; }
 
        private ToolRestoreResult(
            (RestoredCommandIdentifier, ToolCommand)? saveToCache,
            bool isSuccess, string message)
        {
            if (string.IsNullOrWhiteSpace(message))
            {
                throw new ArgumentException("message", nameof(message));
            }
 
            SaveToCache = saveToCache;
            IsSuccess = isSuccess;
            Message = message;
        }
 
        public static ToolRestoreResult Success(
            (RestoredCommandIdentifier, ToolCommand)? saveToCache,
            string message)
        {
            return new ToolRestoreResult(saveToCache, true, message);
        }
 
        public static ToolRestoreResult Failure(string message)
        {
            return new ToolRestoreResult(null, false, message);
        }
 
        public static ToolRestoreResult Failure(
            PackageId packageId,
            ToolPackageException toolPackageException)
        {
            return new ToolRestoreResult(null, false,
                string.Format(CliCommandStrings.PackageFailedToRestore,
                    packageId.ToString(), toolPackageException.ToString()));
        }
    }
}