-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[WIP] dotnet tool exec <package[@version]> one-shot execution
#48443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bb4b2bc
dd02918
62cc62c
87d8208
b11e3b8
2902f08
33443f1
e603952
d9ab4d8
786d58b
51e3412
a51efbe
08b1431
66395d5
7e6b546
dfb18b0
875889a
d8a059a
4f30d19
a8dfe19
630744c
ee41f19
f85411f
f14af9a
8d95185
90b197c
761ea9f
6d2066a
2573bad
5de73ef
f4c558c
093f2e6
45339d7
9d07213
8936d4e
fcd119a
76511ee
52d9728
cbbf3da
312f46f
0cb89cc
62d20ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2062,6 +2062,9 @@ If you would like to create a manifest, use the `--create-manifest-if-needed` fl | |||||
| <data name="ToolRunCommandDescription" xml:space="preserve"> | ||||||
| <value>Run a local tool. Note that this command cannot be used to run a global tool. </value> | ||||||
| </data> | ||||||
| <data name="ToolRunArguementsDescription" xml:space="preserve"> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tiny nit:
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in follow-up PR: #49329 |
||||||
| <value>Arguments forwarded to the tool</value> | ||||||
| </data> | ||||||
| <data name="ToolSearchCommandDescription" xml:space="preserve"> | ||||||
| <value>Search dotnet tools in nuget.org</value> | ||||||
| </data> | ||||||
|
|
@@ -2483,6 +2486,18 @@ To display a value, specify the corresponding command-line option without provid | |||||
| <data name="ZeroTestsRan" xml:space="preserve"> | ||||||
| <value>Zero tests ran</value> | ||||||
| </data> | ||||||
| <data name="ToolExecuteCommandDescription" xml:space="preserve"> | ||||||
| <value>Executes a tool from source without permanently installing it.</value> | ||||||
| </data> | ||||||
| <data name="ToolExecuteCommandMissingPackageId" xml:space="preserve"> | ||||||
| <value>Missing package ID</value> | ||||||
| </data> | ||||||
| <data name="ToolRunFromSourceUserConfirmationPrompt" xml:space="preserve"> | ||||||
| <value>Tool not found on the system. Do you want to run it from source? [y/n]</value> | ||||||
| </data> | ||||||
| <data name="ToolRunFromSourceUserConfirmationFailed" xml:space="preserve"> | ||||||
| <value>Run from source approval denied by the user</value> | ||||||
| </data> | ||||||
| <data name="SolutionAddReferencedProjectsOptionDescription" xml:space="preserve"> | ||||||
| <value>Recursively add projects' ReferencedProjects to solution</value> | ||||||
| </data> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // 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.ToolPackage; | ||
| using Microsoft.DotNet.Cli.Utils; | ||
| using NuGet.Common; | ||
| using NuGet.Packaging.Core; | ||
| using NuGet.Versioning; | ||
|
|
||
| namespace Microsoft.DotNet.Cli.Commands.Tool.Execute | ||
| { | ||
| internal class ToolExecuteCommand(ParseResult result) : CommandBase(result) | ||
| { | ||
| private readonly PackageIdentity? _packageToolIdentityArgument = result.GetValue(ToolExecuteCommandParser.PackageIdentityArgument); | ||
| private readonly IEnumerable<string> _forwardArguments = result.GetValue(ToolExecuteCommandParser.CommandArgument) ?? []; | ||
| 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 _ignoreFailedSources = result.GetValue(ToolCommandRestorePassThroughOptions.IgnoreFailedSourcesOption); | ||
| private readonly bool _interactive = result.GetValue(ToolExecuteCommandParser.InteractiveOption); | ||
| private readonly VerbosityOptions _verbosity = result.GetValue(ToolExecuteCommandParser.VerbosityOption); | ||
| private readonly bool _yes = result.GetValue(ToolExecuteCommandParser.YesOption); | ||
| private readonly bool _prerelease = result.GetValue(ToolExecuteCommandParser.PrereleaseOption); | ||
|
|
||
| public override int Execute() | ||
| { | ||
| if (_packageToolIdentityArgument is null) | ||
| { | ||
| // System.CommandLine will throw an error if the argument is not provided, but we can still check here for clarity. | ||
| return 1; | ||
| } | ||
|
|
||
| if (!UserAgreedToRunFromSource()) | ||
| { | ||
| throw new GracefulException(CliCommandStrings.ToolRunFromSourceUserConfirmationFailed, isUserError: true); | ||
| } | ||
|
|
||
| if (_allowRollForward) | ||
| { | ||
| _forwardArguments.Append("--allow-roll-forward"); | ||
| } | ||
|
|
||
| PackageId packageId = new PackageId(_packageToolIdentityArgument.Id); | ||
|
|
||
| VersionRange versionRange = _parseResult.GetVersionRange(); | ||
|
|
||
| string tempDirectory = NuGetEnvironment.GetFolderPath(NuGetFolderPath.Temp); | ||
|
|
||
| ToolPackageStoreAndQuery toolPackageStoreAndQuery = new(new(tempDirectory)); | ||
| ToolPackageDownloader toolPackageDownloader = new(toolPackageStoreAndQuery); | ||
|
|
||
| IToolPackage toolPackage = toolPackageDownloader.InstallPackage( | ||
| new PackageLocation( | ||
| nugetConfig: _configFile != null ? new(_configFile) : null, | ||
| sourceFeedOverrides: _sources, | ||
| additionalFeeds: _addSource), | ||
| packageId: packageId, | ||
| verbosity: _verbosity, | ||
| versionRange: versionRange, | ||
| isGlobalToolRollForward: _allowRollForward, // Needed to update .runtimeconfig.json | ||
| restoreActionConfig: new( | ||
| IgnoreFailedSources: _ignoreFailedSources, | ||
| Interactive: _interactive)); | ||
|
|
||
| CommandSpec commandSpec = MuxerCommandSpecMaker.CreatePackageCommandSpecUsingMuxer(toolPackage.Command.Executable.ToString(), _forwardArguments); | ||
| var command = CommandFactoryUsingResolver.Create(commandSpec); | ||
| var result = command.Execute(); | ||
| return result.ExitCode; | ||
| } | ||
|
|
||
| private bool UserAgreedToRunFromSource() | ||
| { | ||
| if (_yes) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| if (!_interactive) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // TODO: Use a better way to ask for user input | ||
| Console.Write(CliCommandStrings.ToolRunFromSourceUserConfirmationPrompt); | ||
| bool userAccepted = Console.ReadKey().Key == ConsoleKey.Y; | ||
|
|
||
| if (_verbosity >= VerbosityOptions.detailed) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.WriteLine(new String('-', CliCommandStrings.ToolRunFromSourceUserConfirmationPrompt.Length)); | ||
| } | ||
| else | ||
| { | ||
| // Clear the line | ||
| Console.Write("\r" + new string(' ', CliCommandStrings.ToolRunFromSourceUserConfirmationPrompt.Length + 1) + "\r"); | ||
| } | ||
|
|
||
| return userAccepted; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.CommandLine; | ||
| using System.Text; | ||
| using Microsoft.DotNet.Cli.Commands.Tool.Install; | ||
| using NuGet.Packaging.Core; | ||
|
|
||
| namespace Microsoft.DotNet.Cli.Commands.Tool.Execute | ||
| { | ||
| internal static class ToolExecuteCommandParser | ||
|
|
||
| { | ||
| public static readonly Argument<PackageIdentity?> PackageIdentityArgument = ToolInstallCommandParser.PackageIdentityArgument; | ||
|
|
||
| public static readonly Argument<IEnumerable<string>> CommandArgument = new("commandArguments") | ||
| { | ||
| Description = CliCommandStrings.ToolRunArguementsDescription | ||
| }; | ||
|
|
||
| public static readonly Option<string> VersionOption = ToolInstallCommandParser.VersionOption; | ||
| public static readonly Option<bool> RollForwardOption = ToolInstallCommandParser.RollForwardOption; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have to define these here as opposed to just adding ToolInstallCommandParser.RollForwardOption in ToolExecuteCommandParser.ConstructCommand and using that in the Command? |
||
| public static readonly Option<bool> PrereleaseOption = ToolInstallCommandParser.PrereleaseOption; | ||
| public static readonly Option<string> ConfigOption = ToolInstallCommandParser.ConfigOption; | ||
| public static readonly Option<string[]> SourceOption = ToolInstallCommandParser.SourceOption; | ||
| public static readonly Option<string[]> AddSourceOption = ToolInstallCommandParser.AddSourceOption; | ||
| public static readonly Option<bool> IgnoreFailedSourcesOption = ToolCommandRestorePassThroughOptions.IgnoreFailedSourcesOption; | ||
| public static readonly Option<bool> InteractiveOption = CommonOptions.InteractiveOption(); | ||
| public static readonly Option<bool> YesOption = CommonOptions.YesOption; | ||
| public static readonly Option<VerbosityOptions> VerbosityOption = ToolInstallCommandParser.VerbosityOption; | ||
|
|
||
|
|
||
| public static readonly Command Command = ConstructCommand(); | ||
| public static Command GetCommand() | ||
| { | ||
| return Command; | ||
| } | ||
|
|
||
| private static Command ConstructCommand() | ||
| { | ||
| Command command = new("execute", CliCommandStrings.ToolExecuteCommandDescription); | ||
|
|
||
| command.Aliases.Add("exec"); | ||
|
|
||
| command.Arguments.Add(PackageIdentityArgument); | ||
| command.Arguments.Add(CommandArgument); | ||
|
|
||
| command.Options.Add(VersionOption); | ||
| command.Options.Add(RollForwardOption); | ||
| command.Options.Add(PrereleaseOption); | ||
| command.Options.Add(ConfigOption); | ||
| command.Options.Add(SourceOption); | ||
| command.Options.Add(AddSourceOption); | ||
| command.Options.Add(IgnoreFailedSourcesOption); | ||
| command.Options.Add(InteractiveOption); | ||
| command.Options.Add(YesOption); | ||
| command.Options.Add(VerbosityOption); | ||
|
|
||
| command.SetAction((parseResult) => new ToolExecuteCommand(parseResult).Execute()); | ||
|
|
||
| return command; | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still seems unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in follow-up PR: #49329