Enable 'dotnet sdk check' command in AOT native binary - #54391
Conversation
There was a problem hiding this comment.
Pull request overview
This PR enables the dotnet sdk check command to run via the NativeAOT-compiled CLI fast-path, threading the host-provided DOTNET_ROOT into the command and hardening the unmanaged entry point so managed exceptions cannot escape across the boundary.
Changes:
- Registers
sdk checkin the AOT-only parser and wires it toSdkCheckCommand.Run. - Extends the
dotnet-aotproject to compile/link thesdk checkimplementation and embed required.resxresources plus theMicrosoft.Deployment.DotNet.Releasesdependency. - Refactors the AOT native entry point to wrap the full
[UnmanagedCallersOnly]method body in a top-level try/catch, and introducesProgram.DotnetRoot(AOT-only) for passing the dotnet root into commands.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Cli/dotnet/Program.cs | Adds AOT-only DotnetRoot and wraps AOT Main in error handling. |
| src/Cli/dotnet/Parser.cs | Registers sdk check under the AOT parser. |
| src/Cli/dotnet/Commands/Sdk/Check/SdkCheckCommand.cs | Adds AOT conditional wiring to pass Program.DotnetRoot and adjusts aliasing for AOT builds. |
| src/Cli/dotnet/CommandBase.cs | Guards help/error extension usage for AOT builds. |
| src/Cli/dotnet-aot/NativeEntryPoint.cs | Extracts ExecuteCore and wraps the unmanaged entry point body with a top-level try/catch; sets Program.DotnetRoot for the AOT fast-path. |
| src/Cli/dotnet-aot/dotnet-aot.csproj | Includes sdk check sources/resources and adds Microsoft.Deployment.DotNet.Releases package reference. |
Comments suppressed due to low confidence (1)
src/Cli/dotnet/Parser.cs:37
- This error message is hard-coded even though a localized resource exists (
CliStrings.RequiredCommandNotPassed). Please use the resource string here to keep messages consistent and localizable (and sinceCliStrings.resxis embedded for AOT in this PR).
sdkCommand.SetAction(parseResult =>
{
parseResult.InvocationConfiguration.Error.WriteLine("Required command was not provided.");
return 1;
Add the sdk check command to the AOT-compiled CLI binary (dotnet-aot.csproj). This command lists installed SDKs and runtimes and checks for updates, performing all work in-process without MSBuild or NuGet dependencies. Changes: - Register 'sdk check' command in the AOT Parser.cs section - Add exception handling in both Program.Main and NativeEntryPoint for GracefulException and unexpected errors - Thread dotnetRoot from native host into SdkCheckCommand to ensure the correct .NET installation is inspected - Guard CommandBase.ShowHelpOrErrorIfAppropriate with #if !CLI_AOT (extension method references types unavailable in AOT) - Handle extern alias for EnvironmentProvider conditionally in SdkCheckCommand (#if CLI_AOT uses direct reference) - Add Compile items, EmbeddedResource items (CliStrings.resx, CliCommandStrings.resx), and Microsoft.Deployment.DotNet.Releases package reference to dotnet-aot.csproj Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On macOS, NativeAOT statically links libSystem.Security.Cryptography.Native.Apple.a into every shared library output. The Swift binding classes in this archive conflict with the host process's copy, causing duplicate ObjC class warnings on stderr. Since the AOT CLI does not use cryptographic APIs, safely remove this library. Port of fix from PR dotnet#54384. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The merge with main (PR dotnet#54297) introduced AotSourceFiles.props, a shared source list imported by both dotnet-aot.csproj and the new dotnet-aot.Tests.csproj. The merge resolution left duplicate Compile items in dotnet-aot.csproj for files that are now provided by the props import (CommandLineInfo.cs, Parser.cs, EnvironmentVariableNames.cs), which would cause duplicate-item build failures. Since the shared Parser.cs references the 'sdk check' command, the test project (which compiles Parser.cs via the props) also needs the command's sources, resources, and package dependency. Move those into AotSourceFiles.props so both consumers stay in sync, and remove the now-duplicated entries from dotnet-aot.csproj. Also move the host-provided DotnetRoot state from Program.cs into the shared NativeEntryPoint.cs, because NativeEntryPoint.cs is compiled by the test project but Program.cs is not. This keeps the test project building without pulling in Program.cs (which defines a Main entry point). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reconcile the aot3 branch merge with main's PR dotnet#54384 (sln AOT support): - AotSourceFiles.props: remove duplicate CommandBase.cs and duplicate CliStrings/CliCommandStrings EmbeddedResource items left by the merge; adopt main's grouped layout with a dedicated 'sdk check' command group. - NativeEntryPoint.cs: fix garbled ExecuteCore (out-of-scope parseResult) by using main's CommandNotAvailableInAotException fall-through structure plus the DotnetRoot assignment needed by sdk check. - Resolve CS0433 EnvironmentProvider ambiguity (now that the sln command references Microsoft.DotNet.Cli.Definitions, which also compiles EnvironmentProvider.cs): add Aliases=DotNetNativeWrapper,global to the NativeWrapper references in dotnet-aot.csproj and dotnet-aot.Tests.csproj, and make SdkCheckCommand.cs use the extern alias unconditionally. All three projects build clean (0 warnings/0 errors); 44 AOT tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merging main's dotnet#54544 (OTel for AOT) renamed the dotnet-aot NativeWrapper ProjectReference alias to 'global,NativeWrapper' (for ManagedHost.cs) and the conflict resolution dropped the 'Aliases' metadata from the test project's NativeWrapper reference entirely. SdkCheckCommand.cs (shared by dotnet.csproj, dotnet-aot.csproj and dotnet-aot.Tests.csproj) uses 'extern alias DotNetNativeWrapper', so both AOT projects failed to compile it (CS0430) and the test project also hit the EnvironmentProvider CS0433 ambiguity again. Expose both alias names on the AOT projects' NativeWrapper reference (global,NativeWrapper,DotNetNativeWrapper) so ManagedHost.cs keeps using NativeWrapper while SdkCheckCommand.cs keeps using DotNetNativeWrapper, matching the non-AOT dotnet.csproj convention without touching shared code. dotnet-aot.csproj and dotnet-aot.Tests.csproj build clean; 46 AOT tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@JeremyKuhne @baronfel this is now passing all checks and is ready for review. I think I'll need to adjust the implementation if we merge #54653 first. |
# Conflicts: # src/Cli/dotnet/Parser.cs
|
Updated the changes to account for Parser PR that was merged yesterday: #54653 |
|
All checks are passing with new changes to address Parser code to align with changes made by @baronfel This is now ready for review - @marcpopMSFT @JeremyKuhne @baronfel |
baronfel
left a comment
There was a problem hiding this comment.
I have a question about the bare dotnet SDK command, but the rest looks great 👍
| { | ||
| public static void ConfigureCommand(SdkCommandDefinition command) | ||
| { | ||
| #if CLI_AOT |
There was a problem hiding this comment.
Is this still true? The entire command tree should be usable from AOT now so we may not need this special case anymore.
There was a problem hiding this comment.
Let me check - thanks.
| // for `sln list`/`migrate`/`remove` and falls back for `sln` and `sln add`. | ||
| SolutionCommandParser.ConfigureCommand(rootCommand.SolutionCommand); | ||
|
|
||
| // SdkCommandParser is AOT-aware: `sdk check` runs natively; bare `dotnet sdk` falls back. |
There was a problem hiding this comment.
I think bare 'dotnet sdk' should be served from the AOT side too - there's no reason to startup a coreclr just to display help IMO.
There was a problem hiding this comment.
Looking into this - thanks.
JeremyKuhne
left a comment
There was a problem hiding this comment.
Nothing to add over @baronfel
Per review feedback, the unified parser (dotnet#54653) renders the full command tree and help from AOT, so falling back to the managed CLI just to print a missing-command error + usage is unnecessary overhead. Bare 'dotnet sdk' and 'dotnet sln' now render from AOT; only 'sln add' still falls back since it requires MSBuild. Moved HandleMissingCommand out of the !CLI_AOT block so it compiles into AOT. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@baronfel good catch, thanks! You're right — since #54653 the full command tree and help render from AOT, so falling back to coreclr just to print the missing-command error + usage was wasteful. I verified empirically that bare I applied the same reasoning to bare Pushed in a4378b3. |
# Conflicts: # src/Cli/dotnet/Extensions/ParseResultExtensions.cs # src/Cli/dotnet/Parser.cs # test/dotnet-aot.Tests/AotParserTests.cs
…STest - Remove duplicate 'using Microsoft.DotNet.Cli.Commands.Sdk' and 'using Microsoft.DotNet.Cli.Commands.Tool' in Parser.cs - Convert TransientSdkResolutionErrorDetectorTests from xUnit ([Fact]) to MSTest ([TestMethod]) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # src/Cli/dotnet/Extensions/ParseResultExtensions.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dotnet-aot.Tests compiles the entire shared CLI parser into its test assembly via AotSourceFiles.props. The method-based Helix scheduler counts those hundreds of non-test types and over-shards the assembly, producing shards that contain only non-test types (e.g. CliSchema's nested records). Such shards run zero tests and fail under MTP with exit code 8. Add a MethodLimitMultiplier so the assembly runs as a single work item. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Enables the
dotnet sdk checkcommand to run in the AOT-compiled native CLI binary, improving cold-start performance for this command.Changes
sdk checkcommand in the#if CLI_AOTsection withSdkCheckCommand.RunactionMicrosoft.Deployment.DotNet.Releasespackage referenceExecuteCoremethod so the entire[UnmanagedCallersOnly]entry point body is wrapped in a top-level try/catch — no managed exception can cross the unmanaged boundary. Also setsProgram.DotnetRootand adds inner exception handling for AOT invoke path.Program.DotnetRootstatic property (AOT-only) to thread host-provided dotnet root to commands; add try/catch inMainParseResultExtensionsimport andShowHelpOrErrorIfAppropriatecall with#if !CLI_AOT(these depend on types not available in AOT build)extern alias, conditionalEnvironmentProviderusing, threadProgram.DotnetRootin AOT pathDesign Decisions
sdk checkwas chosen as the first new AOT command because it has zero MSBuild/NuGet dependencies and does all work in-process (HTTP fetch + JSON parsing + table formatting)Microsoft.Deployment.DotNet.ReleasesusesJsonDocumentDOM API (no reflection) — verified AOT-safeCliStrings.resxandCliCommandStrings.resxare embedded (size tradeoff for simplicity over splitting ~20 needed strings)NativeEntryPoint.Executewraps the entire method body in try/catch since[UnmanagedCallersOnly]methods must never let managed exceptions escapeKnown Compromises
dotnet sdk(bare) shows minimal System.CommandLine error vs full help in non-AOTTesting