Repository navigation
Add Native AOT support for tool list --local, tool run, tool uninstall --local, and tool search - #54827
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the Native AOT CLI “fast-path” to cover an AOT-capable subset of dotnet tool by wiring the real command implementations into the shared unified parser and falling back to the managed CLI for unsupported variants.
Changes:
- Wire
ToolCommandParser.ConfigureCommandinto the AOT action configuration so localtool list/tool uninstall,tool run, andtool searchexecute natively while othertoolflows throwCommandNotAvailableInAotExceptionfor managed fallback. - Make
dotnet tool searchservice-index resolution AOT-friendly by replacingNuGet.Protocolusage withHttpClient+System.Text.Jsonsource generation under#if CLI_AOT. - Trim resolver/env-var helper code paths under
#if !CLI_AOT, and link the required tool/manifest/search sources intodotnet-aotviaAotSourceFiles.props, plus add parser coverage indotnet-aot.Tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/dotnet-aot.Tests/AotParserTests.cs | Adds parse + fallback assertions for the AOT-capable tool subcommands and managed-only variants. |
| src/Cli/Microsoft.DotNet.InternalAbstractions/Properties/Properties.cs | Adds InternalsVisibleTo for dotnet-aot and dotnet-aot.Tests. |
| src/Cli/dotnet/Parser.cs | Wires ToolCommandParser.ConfigureCommand into ConfigureAotActions. |
| src/Cli/dotnet/NugetSearch/NugetToolSearchApiRequest.cs | Adds AOT-safe NuGet service-index resolution via HttpClient + STJ source-gen. |
| src/Cli/dotnet/Commands/Tool/ToolCommandParser.cs | Implements AOT-aware tool action wiring with selective managed fallback. |
| src/Cli/dotnet/CommandFactory/CommandSpec.cs | Excludes project-based env-var helper under CLI_AOT. |
| src/Cli/dotnet/CommandFactory/CommandFactoryUsingResolver.cs | Excludes resolver-heavy overloads under CLI_AOT, keeping Create(CommandSpec). |
| src/Cli/dotnet-aot/AotSourceFiles.props | Links the tool-manifest/tool-package infrastructure and AOT-capable tool command sources into the AOT build. |
| [Theory] | ||
| [InlineData("tool list")] | ||
| [InlineData("tool list --local")] | ||
| [InlineData("tool run mytool")] | ||
| [InlineData("tool uninstall mypackage")] | ||
| [InlineData("tool search mysearchterm")] | ||
| public void ParseAotToolCommand_HasNoErrors(string commandLine) | ||
| { | ||
| // The AOT-capable `tool` subcommands (local list/uninstall, run, search) parse cleanly | ||
| // because their real implementations are linked into the AOT CLI. | ||
| var result = Parser.Parse(commandLine.Split(' ')); | ||
| Assert.Empty(result.Errors); | ||
| } |
There was a problem hiding this comment.
Done in fc928d6 — added InvokeAotToolListCommand_ExecutesWithoutManagedFallback which invokes tool list / tool list --local and asserts exit code 0 (no CommandNotAvailableInAotException), so a mis-wired action would now be caught. tool list succeeds with empty output when no manifest is present (ToolManifestFinder.Inspect returns empty rather than throwing).
294e39f to
60d9b04
Compare
…al, and tool search Align the AOT-capable `dotnet tool` subcommands with the unified parser model (PR dotnet#54653) used by the `sln` and `sdk check` commands: link the real command implementations into the AOT CLI instead of maintaining a separate self-contained handler, and wire them via an AOT-aware ToolCommandParser.ConfigureCommand. - ToolCommandParser: under #if CLI_AOT, the local `list`/`uninstall`, `run`, and `search` paths call the real commands. The bare `tool` command, the `--global`/`--tool-path` variants, and `install`/`update`/`restore`/ `execute` throw CommandNotAvailableInAotException so NativeEntryPoint falls back to the managed CLI. - NugetToolSearchApiRequest: swap the NuGet.Protocol service-index lookup for an HttpClient + System.Text.Json source-gen path under #if CLI_AOT; the rest of the search pipeline (STJ deserializer, PrintableTable) is already AOT-friendly. - CommandFactoryUsingResolver / CommandSpec: guard the resolver-policy overloads and the project-based env-var helper under #if !CLI_AOT, keeping only the CommandSpec path that `tool run` needs. - AotSourceFiles.props: link the tool manifest/package infrastructure, the local list/uninstall/run/search implementations, and the NuGet search sources. - Grant InternalsVisibleTo to dotnet-aot(.Tests) from InternalAbstractions for IFileSystem. - AotParserTests: cover parsing of the AOT-capable tool commands and assert the managed-only variants fall back via CommandNotAvailableInAotException. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
60d9b04 to
fc928d6
Compare
baronfel
left a comment
There was a problem hiding this comment.
LGTM with one nit, and we can come back and fully light this up when the AOT NuGEt APIs are available.
One thing that would be great is if you were able to take before/after stats similar to how I did in my PR using the otel spans + dashboard. Helps me have easily-accessible data for product announcements later on.
The recursive default fallback in ConfigureAotActions already sets every tool subcommand (and the bare tool command) to throw CommandNotAvailableInAotException, so re-asserting it for the bare command and install/update/restore/execute was redundant. Keep only the actions that differ from the default (local list/uninstall guards, run, search). Addresses PR review feedback from @baronfel. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Will provide data soon. |
| CliStrings.NonRetriableNugetSearchFailure, | ||
| serviceIndexUrl, $"{searchQueryServiceType} not found in service index", response.StatusCode)); | ||
|
|
||
| return new Uri(resource.Id); |
There was a problem hiding this comment.
nit: I see we are trusting the nuget server to return a valid uri. If we want to be more defensive, we should catch an exception that could be thrown if the uri is malformed
There was a problem hiding this comment.
Done in 2c4c1a8. new Uri(resource.Id) is now wrapped in a try/catch for UriFormatException/ArgumentException and translated into a NugetSearchApiRequestException (NonRetriableNugetSearchFailure), so a malformed server-supplied URL stays a GracefulException like the rest of the method.
… URL Address review nits from @Nigusu-Allehu on the CLI_AOT DomainAndPath(): - Dispose the HttpResponseMessage via a using block. - Defensively translate a malformed server-supplied service-index URL into a NugetSearchApiRequestException (GracefulException) instead of letting UriFormatException/ArgumentException escape unhandled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Perf data added to the description under Performance (captured with your |
|
/azl run dotnet-sdk-public-ci |
e45383d
into
dotnet:baronfel/tool-resolution-aot
…l --local, and tool search (#54827) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Enables Native AOT support for the AOT-capable subset of
dotnet toolsubcommands: localtool list, localtool uninstall,tool run, andtool search.Following the unified-parser model (#54653),
ToolCommandParser.ConfigureCommandis shared between the managed and AOT paths via inline#if CLI_AOTregions, the AOT CLI links the real command implementations, and commands/options that cannot run under AOT throwCommandNotAvailableInAotExceptionsoNativeEntryPointre-hosts the managed CLI.What runs under AOT vs. falls back
dotnet toolinvocationtool list/tool list --localToolListLocalCommand)tool uninstall <pkg>(local)ToolUninstallLocalCommand)tool run <cmd>ToolRunCommand)tool search <term>tool list --global/--tool-pathtool uninstall --global/--tool-pathtool,install,update,restore,executeChanges
ToolCommandParser.cs— AOT-awareConfigureCommand. Under#if CLI_AOTit only overrides the AOT-capable paths: locallist/uninstallguard onLocationOptions.IsGlobalOrToolPath(global/tool-path throw fallback) andrun/searchcall the real commands. Baretoolandinstall/update/restore/executekeep the recursive default fallback already set byConfigureAotActions. The managed#elsebranch is unchanged from before.NugetToolSearchApiRequest.cs— under#if CLI_AOT, resolve the NuGet service index withHttpClient+ aSystem.Text.Jsonsource-generated context instead of NuGet.Protocol. The rest of the search pipeline (STJ deserializer,PrintableTable) was already AOT-friendly.Parser.cs— wireToolCommandParser.ConfigureCommandintoConfigureAotActions.AotSourceFiles.props— link only the sources unique to running these subcommands in-process (the parser, the four command implementations,PrintableTable, and the NuGet search sources); the shared manifest/package and command-resolution infrastructure is already linked by Make tool-command resolution and invocation AOT-able #54810.AotParserTests.cs— parse coverage for the AOT-capable tool commands plus fallback assertions for the managed-only variants.Validation
dotnet-aot.csprojand manageddotnet.csproj: build with 0 warnings / 0 errors.-r win-x64): ILC generates native code with zero AOT/trim warnings.AotParserTests: 38/38 pass (parse + managed-fallback theories, plus an invoke-level assertion thattool listexecutes on the AOT path without falling back).Performance
Measured with
eng/gather-otel.ps1against the locally-built redistdotnet.exe, togglingDOTNET_CLI_ENABLEAOT(Debug, win-x64). End-to-end wall-clock per invocation:dotnet tool list— fully handled on the AOT fast path (no network):→ −309.8 ms, 5.5× faster, 81.9% lower latency (12 timed runs/path, 2 warmups discarded).
dotnet tool searchexercises the newHttpClient+ STJ service-index path and returns identical results to the managed path. Its end-to-end time is dominated by NuGet network I/O (~700 ms of service-index + query round-trips), so the ~300 ms startup win is present but not the dominant cost (managed ≈ 875 ms vs AOT ≈ 922 ms mean, within network variance).