File: Commands\Tool\Execute\ToolExecuteCommand.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.
 
using System.CommandLine;
using Microsoft.DotNet.Cli.CommandFactory;
using Microsoft.DotNet.Cli.CommandFactory.CommandResolution;
using Microsoft.DotNet.Cli.Commands.Tool.Install;
using Microsoft.DotNet.Cli.Commands.Tool.Restore;
using Microsoft.DotNet.Cli.Commands.Tool.Run;
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.Configuration;
using NuGet.Versioning;
 
namespace Microsoft.DotNet.Cli.Commands.Tool.Execute;
 
internal class ToolExecuteCommand(ParseResult result, ToolManifestFinder? toolManifestFinder = null, string? currentWorkingDirectory = null) : CommandBase(result)
{
    const int ERROR_CANCELLED = 1223; //  Windows error code for "Operation canceled by user"
 
    private readonly PackageIdentityWithRange _packageToolIdentityArgument = result.GetValue(ToolExecuteCommandParser.PackageIdentityArgument);
    private readonly IEnumerable<string> _forwardArguments = result.GetValue(ToolExecuteCommandParser.CommandArgument) ?? Enumerable.Empty<string>();
    private readonly bool _allowRollForward = result.GetValue(ToolExecuteCommandParser.RollForwardOption);
    private readonly string? _configFile = result.GetValue(ToolExecuteCommandParser.ConfigOption);
    private readonly string[] _sources = result.GetValue(ToolExecuteCommandParser.SourceOption) ?? [];
    private readonly string[] _addSource = result.GetValue(ToolExecuteCommandParser.AddSourceOption) ?? [];
    private readonly bool _interactive = result.GetValue(ToolExecuteCommandParser.InteractiveOption);
    private readonly VerbosityOptions _verbosity = result.GetValue(ToolExecuteCommandParser.VerbosityOption);
    private readonly IToolPackageDownloader _toolPackageDownloader = ToolPackageFactory.CreateToolPackageStoresAndDownloader().downloader;
 
    private readonly RestoreActionConfig _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(ToolExecuteCommandParser.InteractiveOption));
 
    private readonly ToolManifestFinder _toolManifestFinder = toolManifestFinder ?? new ToolManifestFinder(new DirectoryPath(currentWorkingDirectory ?? Directory.GetCurrentDirectory()));
 
    public override int Execute()
    {
        VersionRange? versionRange = _parseResult.GetVersionRange();
        PackageId packageId = new PackageId(_packageToolIdentityArgument.Id);
 
        var toolLocationActivity = Activities.Source.StartActivity("find-tool");
        toolLocationActivity?.SetTag("tool.package.id", packageId.ToString());
        toolLocationActivity?.SetTag("tool.package.version", versionRange?.ToString() ?? "latest");
 
        //  Look in local tools manifest first, but only if version is not specified
        if (versionRange == null)
        {
            var localToolsResolverCache = new LocalToolsResolverCache();
 
            if (_toolManifestFinder.TryFindPackageId(packageId, out var toolManifestPackage))
            {
                toolLocationActivity?.SetTag("tool.exec.kind", "local");
                toolLocationActivity?.Stop();
 
                var toolPackageRestorer = new ToolPackageRestorer(
                    _toolPackageDownloader,
                    _sources,
                    overrideSources: [],
                    _verbosity,
                    _restoreActionConfig,
                    localToolsResolverCache,
                    new FileSystemWrapper());
 
                var restoreResult = toolPackageRestorer.InstallPackage(toolManifestPackage, _configFile == null ? null : new FilePath(_configFile));
 
                if (!restoreResult.IsSuccess)
                {
                    Reporter.Error.WriteLine(restoreResult.Message.Red());
                    return 1;
                }
 
                var localToolsCommandResolver = new LocalToolsCommandResolver(
                    _toolManifestFinder,
                    localToolsResolverCache);
 
                return ToolRunCommand.ExecuteCommand(localToolsCommandResolver, toolManifestPackage.CommandNames.Single().Value, _forwardArguments, _allowRollForward);
            }
        }
 
        var packageLocation = new PackageLocation(
                nugetConfig: _configFile != null ? new(_configFile) : null,
                sourceFeedOverrides: _sources,
                additionalFeeds: _addSource);
 
        (var bestVersion, var packageSource) = _toolPackageDownloader.GetNuGetVersion(packageLocation, packageId, _verbosity, versionRange, _restoreActionConfig);
        toolLocationActivity?.SetTag("tool.exec.kind", "one-shot");
        toolLocationActivity?.Stop();
 
        //  TargetFramework is null, which means to use the current framework.  Global tools can override the target framework to use (or select assets for),
        //  but we don't support this for local or one-shot tools.
        if (!_toolPackageDownloader.TryGetDownloadedTool(packageId, bestVersion, targetFramework: null, verbosity: _verbosity, out var toolPackage))
        {
            if (!UserAgreedToRunFromSource(packageId, bestVersion, packageSource))
            {
                if (_interactive)
                {
                    Reporter.Error.WriteLine(CliCommandStrings.ToolDownloadCanceled.Red().Bold());
                    return ERROR_CANCELLED;
                }
                else
                {
                    Reporter.Error.WriteLine(CliCommandStrings.ToolDownloadNeedsConfirmation.Red().Bold());
                    return 1;
                }
            }
 
            //  We've already determined which source we will use and displayed that in a confirmation message to the user.
            //  So set the package location here to override the source feeds to just the source we already resolved to.
            //  This does mean that we won't work with feeds that have a primary package but where the RID-specific packages are on
            //  other feeds, but this is probably OK.
            var downloadPackageLocation = new PackageLocation(
                nugetConfig: _configFile != null ? new(_configFile) : null,
                sourceFeedOverrides: [packageSource.Source],
                additionalFeeds: _addSource);
 
            toolPackage = _toolPackageDownloader.InstallPackage(
                downloadPackageLocation,
                packageId: packageId,
                verbosity: _verbosity,
                versionRange: new VersionRange(bestVersion, true, bestVersion, true),
                isGlobalToolRollForward: false,
                restoreActionConfig: _restoreActionConfig);
        }
 
        using var toolExecuteActivity = Activities.Source.StartActivity("execute-tool");
        toolExecuteActivity?.SetTag("tool.package.id", packageId.ToString());
        toolExecuteActivity?.SetTag("tool.package.version", toolPackage.Version.ToString());
        toolExecuteActivity?.SetTag("tool.runner", toolPackage.Command.Runner);
        var commandSpec = ToolCommandSpecCreator.CreateToolCommandSpec(toolPackage.Command.Name.Value, toolPackage.Command.Executable.Value, toolPackage.Command.Runner, _allowRollForward, _forwardArguments);
        var command = CommandFactoryUsingResolver.Create(commandSpec);
        var result = command.Execute();
        return result.ExitCode;
    }
 
    private bool UserAgreedToRunFromSource(PackageId packageId, NuGetVersion version, PackageSource source)
    {
        string promptMessage = string.Format(CliCommandStrings.ToolDownloadConfirmationPrompt, packageId, version.ToString(), source.Source);
        return InteractiveConsole.Confirm(promptMessage, _parseResult, acceptEscapeForFalse: true) == true;
    }
}