Skip to content

Enable NativeAOT CLI command handling by default (DOTNET_CLI_ENABLEAOT)#55144

Merged
baronfel merged 4 commits into
mainfrom
baronfel-enable-naot-command-handling-by-default
Jul 16, 2026
Merged

Enable NativeAOT CLI command handling by default (DOTNET_CLI_ENABLEAOT)#55144
baronfel merged 4 commits into
mainfrom
baronfel-enable-naot-command-handling-by-default

Conversation

@baronfel

@baronfel baronfel commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fixes #55078.

What

Flips the default of DOTNET_CLI_ENABLEAOT in the NativeAOT entry point from false to true, so the AOT command-handling fast path runs by default on all platforms. Users opt out by setting DOTNET_CLI_ENABLEAOT to a falsy value (false/0/no/off), which routes every invocation to the managed CLI exactly as before.

Now that the native path has reached parity with the managed CLI, enabling it by default gives broad real-world testing and improves CLI startup for common commands.

History: macOS + Linux were temporarily gated

Earlier revisions of this PR restricted the default to Windows only, because the AOT path was blocked on macOS and Linux by a command-line parsing crash: dotnet/command-line-api#2812. When the AOT CLI is loaded as a NativeAOT shared library, Environment.GetCommandLineArgs() returned an empty array on those platforms, so System.CommandLine's RootCommand constructor threw an IndexOutOfRangeException. (Although the upstream issue was titled for macOS, it was empirically confirmed to reproduce identically on Linux with a focused NativeAOT shared-library repro.)

That crash is now fixed upstream in dotnet/command-line-api#2820RootCommand.ExecutablePath guards the empty-args case and ExecutableName falls back to the build-injected System.CommandLine.ExecutableName from AppContext for the native-library scenario. The fix has flowed into the SDK via System.CommandLine 3.0.0-preview.7.26359.117, so the platform gate has been removed and the default is now unconditional (ParseBool(..., defaultValue: true)). Explicit DOTNET_CLI_ENABLEAOT values still override on every platform.

Changes

The branch is organized into five focused commits:

  1. Keep MSTest.Sdk in sync with the MSTest framework (from @dsplaisted, #55302) — global.json + eng/Version.Details.xml. The reflection-free MSTest source generator (required for NativeAOT test discovery) emits zero tests when MSTest.Sdk (in msbuild-sdks) drifts from the darc-flowed MSTest framework version — which is why the "Run NativeAOT CLI Tests" step reported "Zero tests ran". Bumps MSTest.Sdk to match and tracks it as a darc dependency so the two stay in lockstep on every testfx flow. This is the root-cause fix that replaces the earlier CI workarounds.
  2. Disable the win-arm64 NativeAOT cross-build AoT legdisableJob: true in .vsts-pr.yml / .vsts-ci.yml. The win-x64 → win-arm64 ILC cross-link fails persistently; tracked by Windows arm64 NativeAOT cross-build (dotnet-aot) AoT CI leg persistently failing #55298 for the dotnet/illink (ILCompiler) team. Re-enable once fixed.
  3. Decouple the NativeAOT size-analysis publish onto the AoT category — gate the size-analysis Copy/Publish steps on categoryName == 'AoT' instead of runAoTTests (sdk-build.yml), and drop the now-dead runAoTTests: true from the build-only cross-arch legs (they have runTests: false, so the Helix block never consumed it). runAoTTests remains the Blazor WebAssembly AoT test switch.
  4. Enable NativeAOT command handling by default on all platforms:
    • src/Cli/dotnet-aot/NativeEntryPoint.cs: unconditional default — ParseBool(..., defaultValue: true).
    • src/Cli/dotnet/Parser.cs: new AotPrintHelpAction (under CLI_AOT) renders help from the shared command tree but defers the new and test subtrees to the managed CLI, whose help for those is generated dynamically (template-engine short-name/args + per-template options; Microsoft.Testing.Platform "Extension Options:" + per-extension options) and has no static equivalent.
    • src/Cli/dotnet-aot/dotnet-aot.csproj: present the CLI as dotnet in usage output via the System.CommandLine executable-name escape hatch (_SystemCommandLineExecutableName) rather than the default dotnet-aot; and remove the now-unneeded Apple-crypto NativeAOT link workaround (the fixed runtime has flowed to the SDK).
    • src/Cli/dotnet-aot/DESIGN.md: on-by-default gating text/diagrams and the Opting out (DOTNET_CLI_ENABLEAOT) section.
  5. Get the dotnet-aot test suite greentest/dotnet-aot.Tests/*: make the test classes partial (required by the MSTest source generator under NativeAOT), cover the help fall-back-to-managed action, and use a single space-delimited string for the DataRow cases (params string[] cannot be stored into the DataRow object[] under NativeAOT).

Verification

Built dotnet-aot.Tests and exercised the managed test classes locally (NativeEntryPointTests, AotParserTests); integration tests skip without the native dn binary in the layout, as expected. The #2812 fix was verified present in System.CommandLine 3.0.0-preview.7.26359.117 on main before removing the gate. Full NativeAOT test discovery/run is validated by the "Run NativeAOT CLI Tests" step on the AoT CI legs.

Breaking change

This is a behavioral breaking change (label breaking-change). Related documentation work in dotnet/docs:

The opt-out is documented at:
https://learn.microsoft.com/dotnet/core/tools/dotnet-environment-variables#dotnet_cli_enableaot

Copilot AI review requested due to automatic review settings July 6, 2026 14:36
@baronfel baronfel added the breaking-change Using this label will notify dotnet/compat and trigger a request to file a compat bug label Jul 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables the NativeAOT CLI command-handling fast path by default by flipping the default value of DOTNET_CLI_ENABLEAOT from false to true, while preserving the ability to opt out via falsy values (e.g. false, 0, no, off). This aligns the native entry point behavior with the stated parity goals and broadens real-world coverage of the AOT path.

Changes:

  • Enable the AOT fast path by default in the native entry point (opt-out via DOTNET_CLI_ENABLEAOT=false/0/no/off).
  • Update design documentation to describe on-by-default gating and add an opt-out section.
  • Update and extend tests to reflect the new default behavior and validate opt-out formats.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/Cli/dotnet-aot/NativeEntryPoint.cs Flips DOTNET_CLI_ENABLEAOT default to enabled and updates related comments.
src/Cli/dotnet-aot/DESIGN.md Updates gating language/diagrams and documents the opt-out behavior.
test/dotnet-aot.Tests/NativeEntryPointTests.cs Adjusts tests for new default; adds data-driven opt-out format coverage.
test/dotnet-aot.Tests/AotIntegrationTests.cs Ensures “disabled” integration runs explicitly set DOTNET_CLI_ENABLEAOT=false; updates commentary accordingly.

Comment thread src/Cli/dotnet-aot/DESIGN.md Outdated
Comment thread test/dotnet-aot.Tests/AotIntegrationTests.cs Outdated
@baronfel
baronfel requested review from a team and JeremyKuhne July 6, 2026 16:15
@baronfel baronfel self-assigned this Jul 6, 2026
@baronfel baronfel added this to the 11.0.1xx milestone Jul 6, 2026
@baronfel

baronfel commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

The S.CL fix has flowed to the VMR in dotnet/dotnet#7611, so once VMR flows to main we can rebase this and enable NAOT command invocation for all platforms.

@baronfel

Copy link
Copy Markdown
Member Author

Confirmed that with these changes the macOS NAOT binaries no longer crash, and the Apple Crypto library conflicts that the Runtime team fixed for NAOT hosted libraries are also no longer causing errors to be emitted to stdout!

@baronfel
baronfel force-pushed the baronfel-enable-naot-command-handling-by-default branch 2 times, most recently from af5ac99 to d4ec304 Compare July 14, 2026 17:44
@baronfel

Copy link
Copy Markdown
Member Author

@dotnet/illink the Linker on the windows/arm64 AOT leg is complaining about an arm64-specific issue:

dotnet-aot.obj : fatal error LNK1322: cannot avoid potential ARM hazard (Cortex-A53 MPCore processor bug #843419) in section 0x2; please consider using compiler option /Gy if it was not used [D:\a_work\1\s\src\Cli\dotnet-aot\dotnet-aot.csproj]

Is this something that the ILLink Targets should be automatically handling for us, or something that the SDK needs to help detect?

@baronfel

Copy link
Copy Markdown
Member Author

@marcpopMSFT I've disabled the failing windows-arm64 leg and raised #55298 to track fixing and re-enabling that leg.

@nagilson nagilson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the thoughtful PR description. Included some thoughts below.

Normally I would approve but I think we need some changes and my approval won't matter after that point anyways, so waiting on this.

Comment thread src/Cli/dotnet-aot/NativeEntryPoint.cs Outdated
Comment thread src/Cli/dotnet-aot/NativeEntryPoint.cs Outdated
Comment thread src/Cli/dotnet/Parser.cs
Comment thread src/Cli/dotnet-aot/NativeEntryPoint.cs Outdated
Comment thread .vsts-ci.yml Outdated
Comment thread .vsts-ci.yml
@baronfel
baronfel force-pushed the baronfel-enable-naot-command-handling-by-default branch 2 times, most recently from 5f541db to 33f96b4 Compare July 16, 2026 00:49
baronfel and others added 3 commits July 15, 2026 19:50
Gate the size-analysis Copy/Publish steps on categoryName == 'AoT' instead of
runAoTTests. runAoTTests is the Blazor WebAssembly AoT test switch (its only
env-var consumer is Microsoft.NET.Sdk.BlazorWebAssembly.AoT.Tests); the NativeAOT
dll and its mstat/dgml size-analysis output are produced by every AoT leg, so the
AoT category is the honest gate and it now runs on the build-only cross-arch legs
too.

With size-analysis decoupled, the now-dead runAoTTests: true on the build-only
cross-arch legs (win-arm64, linux-arm64; runTests: false) is removed - it drove
nothing there since the Helix test block is gated on runTests. runAoTTests now
appears only on legs that run Blazor WebAssembly AoT tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b7c0dc09-64f3-40b8-9c5f-45b0fce2e461
Turn on the NativeAOT CLI fast path by default across Windows, macOS, and Linux
(#55078). The native entry point handles supported commands directly and
defers unsupported ones (e.g. the 'new' subtree, 'test --help') back to the managed
CLI. Present the AOT CLI as 'dotnet' in help output via the System.CommandLine
executable-name escape hatch set during the build rather than in code.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b7c0dc09-64f3-40b8-9c5f-45b0fce2e461
Update the NativeAOT CLI tests for the on-by-default behavior: make the test
classes partial as required by the MSTest reflection-free source generator (used
for NativeAOT test discovery), cover the help fall-back-to-managed action, and use
a single space-delimited string for the DataRow cases (params string[] cannot be
stored into the DataRow object[] under NativeAOT).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b7c0dc09-64f3-40b8-9c5f-45b0fce2e461
@baronfel
baronfel force-pushed the baronfel-enable-naot-command-handling-by-default branch from 33f96b4 to 9ac6f8d Compare July 16, 2026 00:50
@MichalStrehovsky

Copy link
Copy Markdown
Member

Is this something that the ILLink Targets should be automatically handling for us, or something that the SDK needs to help detect?

This is not ILLink, this is link.exe from MSVC. "IL Linker" term is a constant source of confusion, the tool should have never been named like that.

What Visual Studio version is on this build machine? The branch is getting force pushed so I can't find previous CI results in GitHub UI. The MSVC team claims this check has been removed as of January (https://developercommunity.visualstudio.com/t/Remove-checking-for-and-fixing-Cortex-A/10905134).

There's also an undocumented command line switch that can be passed to link.exe to disable this we could try (<LinkerArg Include="-switch_name" /> items can be used to pass arguments to the link.exe invocation done by native AOT).

Cc @dotnet/ilc-contrib

@jkotas

jkotas commented Jul 16, 2026

Copy link
Copy Markdown
Member

image: windows.vs2022.amd64

This is your problem. You are building with fairly old native toolset in the CI. .NET 11 official product builds and dotnet/runtime repo run on windows.vs2026.amd64 . Is it intentional that you are stuck with vs2022?

@baronfel

Copy link
Copy Markdown
Member Author

image: windows.vs2022.amd64

This is your problem. You are building with fairly old native toolset in the CI. .NET 11 official product builds and dotnet/runtime repo run on windows.vs2026.amd64 . Is it intentional that you are stuck with vs2022?

Well that's interesting. Will see if we can bump as part of fixing the linked follow-up issue.

@baronfel
baronfel merged commit 717a196 into main Jul 16, 2026
26 of 28 checks passed
@baronfel
baronfel deleted the baronfel-enable-naot-command-handling-by-default branch July 16, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Using this label will notify dotnet/compat and trigger a request to file a compat bug needs-breaking-change-doc-created

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable NAOT command handling by default

6 participants