Skip to content

Enable MSBuild-backed commands in the Native AOT CLI - #55497

Open
baronfel wants to merge 36 commits into
mainfrom
baronfel-baronfel-enable-aot-msbuild-commands
Open

baronfel wants to merge 36 commits into
mainfrom
baronfel-baronfel-enable-aot-msbuild-commands

Conversation

@baronfel

@baronfel baronfel commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

  • activate MSBuild SDK resolver registration during Native AOT startup
  • enable pack, publish, build, restore, clean, and msbuild in the Native AOT CLI
  • evaluate directive-free virtual projects through the Native AOT MSBuild path
  • restore the command-enabled Native AOT design and SDK-root documentation
  • keep the temporary IL2104/IL3053 dependency-rollup warning policy in this command-enablement layer only

Parent PR #55441, which contains the infrastructure only, is merged into main; this PR activates the resolvers and commands. The remaining command-layer trim/AOT dependency warnings have a dedicated tracking issue; the merged infrastructure publish is warning-free.

Size and performance tradeoffs

Native image size

The current PR base (e86710e620) and head (fa38bc8af8) were built side-by-side for Windows x64 through the repository build entry point with build.cmd -c Release -a x64.

  • Base: 18,493,952 B (17.637 MiB)
  • PR head: 38,156,288 B (36.389 MiB)
  • Impact: +19,662,336 B (+18.751 MiB, +106.3%)

The size optimization now in main reduced the measured PR growth by about 5.25 MiB compared with the previous current-tip measurement (+23.998 MiB).

SizoScope accounts for 17.6 MB of growth. The largest contributors are shared/transitive closure rather than direct command code: System.Private.CoreLib (+6.3 MB), System.Text.Json (+843.6 kB), NuGet.Protocol (+795.0 kB), Microsoft.Build (+747.8 kB), System.Linq (+705.5 kB), System.Text.Encoding.CodePages (+668.7 kB), System.Data.Common (+504.3 kB), System.Linq.Expressions (+424.7 kB), and Newtonsoft.Json (+411.4 kB). Direct dotnet-aot code grows by about 119.7 kB.

Validation

  • managed src/Cli/dotnet/dotnet.csproj Release build passed
  • src/Cli/dotnet-aot/dotnet-aot.csproj Release build passed
  • full repository Windows x64 Release build passed
  • Native AOT test executable publish passed
  • Native AOT ProjectCompletionsEvaluateCurrentProject: 1 passed
  • Native AOT MSBuildEvaluationTests: 14 passed
  • controlled pack and publish output hashes matched between AOT and managed routes

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch from 57310f0 to 696d06f Compare July 28, 2026 15:44
@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch from 696d06f to 5082e63 Compare July 28, 2026 18:06
@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch from 5082e63 to 2f115e5 Compare July 28, 2026 19:46
@baronfel
baronfel marked this pull request as ready for review July 28, 2026 19:48
Copilot AI review requested due to automatic review settings July 28, 2026 19:48
@baronfel
baronfel requested review from a team as code owners July 28, 2026 19:48
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

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

Enables MSBuild-backed CLI commands when running the Native AOT dotnet entry point by registering SDK resolvers at AOT startup, wiring command parsers/actions into the AOT command tree, and extending the AOT test suite to validate forwarding/evaluation behavior.

Changes:

  • Register MSBuild SDK resolvers during Native AOT startup and configure MSBuild-backed commands (build, clean, msbuild, restore, pack, publish) for the AOT command tree.
  • Add AOT-safe evaluation path for directive-free virtual projects and expand AOT tests to cover command parsing/fallbacks and versioned-SDK forwarding (including PackRelease/PublishRelease evaluation).
  • Update AOT design/docs and scope the temporary IL2104/IL3053 “not as error” policy to the Native AOT CLI project.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/dotnet-aot.Tests/NativeEntryPointTests.cs Adjust fallback test to use an actually-unsupported command under the new AOT command set.
test/dotnet-aot.Tests/MSBuildEvaluationTests.cs Add coverage for MSBuild forwarding, PackRelease/PublishRelease evaluation, project completions, and virtual-project evaluation.
test/dotnet-aot.Tests/AotParserTests.cs Expand parser tests for newly-enabled commands and validate managed fallback for unsupported scenarios.
src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs Add directive-free project instance creation and refactor project-text creation to support AOT-safe evaluation.
src/Cli/dotnet/ReleasePropertyProjectLocator.cs Make release-property evaluation usable in AOT-enabled paths and clean up AOT-related annotations/usings.
src/Cli/dotnet/Parser.cs Configure additional MSBuild-backed commands in the AOT action configuration.
src/Cli/dotnet/NuGetSignatureVerificationEnabler.cs Allow signature-verification environment wiring for MSBuild-forwarded apps in AOT builds.
src/Cli/dotnet/Extensions/ParseResultExtensions.cs Route file-based app detection through VirtualProjectBuilder now that MSBuild is available in AOT.
src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs Introduce AOT-safe evaluation-only behavior (no directives) and ensure execution remains managed-only via fallback.
src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs Remove AOT-blocking annotation to allow wiring in AOT command tree.
src/Cli/dotnet/Commands/Publish/PublishCommand.cs Ensure FromArgs forwards msbuildPath and remove AOT-blocking annotation.
src/Cli/dotnet/Commands/Pack/PackCommandParser.cs Remove AOT-blocking annotation to allow wiring in AOT command tree.
src/Cli/dotnet/Commands/Pack/PackCommand.cs Guard .nuspec in-process NuGet packing under AOT and remove AOT-blocking annotation.
src/Cli/dotnet/Commands/DotNetCommandFactory.cs Enable virtual/physical command creation logic for AOT by removing dynamic-code gating and broadening virtual factory return type.
src/Cli/dotnet/Commands/Clean/CleanCommandParser.cs Keep clean file-based-apps managed-only under AOT via explicit fallback action.
src/Cli/dotnet-aot/SdkRootResolution.md Update documentation to reflect new command coverage and telemetry expectations for managed vs AOT fallbacks.
src/Cli/dotnet-aot/NativeEntryPoint.cs Register SDK resolvers during AOT startup to support MSBuild evaluations/forwarding.
src/Cli/dotnet-aot/dotnet-aot.csproj Scope IL2104/IL3053 “not as error” policy to the AOT CLI project with rationale.
src/Cli/dotnet-aot/DESIGN.md Update design doc to reflect MSBuild-backed command enablement and file-based app evaluation behavior.
src/Cli/dotnet-aot/AotSourceFiles.props Include additional command sources needed for MSBuild-backed commands in the AOT build.

Base automatically changed from baronfel-aot-msbuild-evaluation to main July 28, 2026 21:05
@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch from 2f115e5 to 89a8ded Compare July 28, 2026 21:14
@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch 2 times, most recently from 0071895 to 3df5596 Compare July 30, 2026 15:37
@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch from 3df5596 to fa38bc8 Compare August 12, 2026 16:31
@jjonescz
jjonescz requested a review from a team August 13, 2026 09:07
Comment thread src/Cli/Microsoft.DotNet.FileBasedPrograms/VirtualProjectBuilder.cs Outdated
Comment thread src/Cli/dotnet/Commands/Clean/CleanCommandParser.cs Outdated
Comment thread src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs Outdated
Comment thread src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs Outdated
Comment thread src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs Outdated
@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch from e7eacf0 to 59459b9 Compare August 13, 2026 19:15
@baronfel

Copy link
Copy Markdown
Member Author

@jjonescz ok, I've revamped this a bit after the run-file changes that Jeremy was working on - care to take another look?

Preserve the cached-run implementation's Native AOT guards while retaining directive-free project evaluation for MSBuild-backed commands.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Restore existing member and helper structure where possible, keep the managed-only analyzer suppression scoped to the MSBuild block, and consolidate repetitive parser tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Keep validating that RuntimeIdentifier participates in PublishRelease evaluation without requiring the Windows Desktop SDK on non-Windows CI agents.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 197b8427-6bab-4831-aefa-59ffa3dcab46
@baronfel
baronfel force-pushed the baronfel-baronfel-enable-aot-msbuild-commands branch from 92e3a9a to a18b262 Compare August 18, 2026 21:49

@JeremyKuhne JeremyKuhne 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.

One blocking correctness issue: the Native AOT direct-compilation recovery can replay the full command through managed CLI after observable work has begun.

Note

This review feedback was generated with GitHub Copilot from local code inspection and an executed dn reproduction.


if (IsAotBuild)
{
return ThrowManagedFallback(GetFullMSBuildFallbackReason());

@JeremyKuhne JeremyKuhne Aug 19, 2026

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.

In the AOT build, CSharpCompilerCommand.Execute can return fallbackToNormalBuild=true for:

  • compiler output containing CS0006
  • no compiler-server response
  • an unexpected compiler-server response

By that point this invocation has already crossed the fallback commit point:

  1. BuildStarted may have printed the user-visible “Building...” message.
  2. MarkBuildStart has written build-start.cache.
  3. PrepareAuxiliaryFiles may have written csc.rsp, generated sources, apphost, and runtimeconfig.
  4. The compiler server has been invoked and may have produced diagnostics.

The code then changes the level to BuildLevel.All and, under CLI_AOT, throws CommandNotAvailableInAotException. NativeEntryPoint catches that exception and runs the entire original command again through managed dotnet.dll.

I reproduced this through the real Release dn host using cached CSC arguments containing a missing reference. The AOT invocation printed the CS0006 diagnostic and fallback message, then managed replay observed the AOT-written start marker as newer than the success marker and treated it as a previous failed build. This can duplicate output and work, and the first execution's partial cache state changes the behavior of the replay.

Please split recovery into pre-commit and post-commit cases:

  • Before BuildStarted, MarkBuildStart, or CSharpCompilerCommand.Execute, preflight conditions that can be known in advance, particularly missing path-bearing inputs in replayed CSC arguments. If full MSBuild is required, request managed fallback there.
  • Once direct compilation begins, do not throw CommandNotAvailableInAotException. A compiler-server transport failure should be recovered within the same invocation, such as by invoking the SDK's csc.dll with the prepared response file. If recovery cannot succeed, return that invocation's failure rather than replaying the command.
  • Preserve the existing managed behavior: managed dotnet may still continue from CSC to its in-process full-MSBuild path because that is one invocation, not host-level replay.

Moving MarkBuildStart below Execute, suppressing the marker, or buffering one message is insufficient: auxiliary files, compiler execution, and diagnostics are already observable.

Please add regression coverage that proves:

  1. A missing cached compiler input falls back before output or cache/file mutation.
  2. A compiler-server failure after commit does not enter ManagedHost.RunApp.
  3. The successful direct-compile path remains AOT-handled and writes its success cache exactly once.

If feature-preserving recovery is too large for this PR, the conservative fix is to make AOT request managed fallback whenever the selected build level is Csc, before BuildStarted. That temporarily gives up AOT fresh/changed-file compilation, but it preserves cached launches and the newly enabled physical MSBuild commands without introducing replay-after-commit behavior.

@JeremyKuhne JeremyKuhne 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.

There is a potential issue with the cache getting messed up and replayed, see the inline comment with more details.

There aren't any tests that actually exercise the physical commands end to end in AOT.

Double check the sizes you're reporting, I got larger ones locally.

Note that tests aren't actually running in the CI. #55854 should fix that once that is.

@baronfel baronfel added DO NOT MERGE Area-dotnet AOT Items that are part of the dotnet CLI AOT-ification effort labels Aug 19, 2026
@baronfel

Copy link
Copy Markdown
Member Author

Found another potential blocker: dotnet/msbuild#14699 may lead to us wanting to remove the environment-variables that the CLI sets for the MSBuild SDK root, extensions path, etc - so that the MSBuild engine can handle the location and be safe in nested-dotnet-invocation scenarios.

However, if we do that we suspect that this location is in part based on AppContext.BaseDirectory, which we know is not valid in NAOT-library scenarios like the NAOT dotnet CLI. So we'd need to design an API to allow NAOT-library callers at minimum to specify these toolset locations/information.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-dotnet AOT Items that are part of the dotnet CLI AOT-ification effort DO NOT MERGE

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants