Skip to content

Add AOT support for dotnet sln list, migrate, and remove#54384

Open
NikolaMilosavljevic wants to merge 9 commits into
dotnet:mainfrom
NikolaMilosavljevic:aot2
Open

Add AOT support for dotnet sln list, migrate, and remove#54384
NikolaMilosavljevic wants to merge 9 commits into
dotnet:mainfrom
NikolaMilosavljevic:aot2

Conversation

@NikolaMilosavljevic
Copy link
Copy Markdown
Member

Summary

Add Native AOT support for three dotnet sln subcommands: list, migrate, and remove. These commands operate on solution files using pure file I/O and the Microsoft.VisualStudio.SolutionPersistence package — no MSBuild or NuGet dependencies required.

Changes

New file: src/Cli/dotnet-aot/CliStrings.cs

String shim providing the subset of CliStrings properties needed by linked source files (SlnFileFactory.cs, Parser.cs) in the AOT project context, where the full .resx auto-generation doesn't work for linked resources.

Modified: src/Cli/dotnet-aot/dotnet-aot.csproj

  • Link SlnFileFactory.cs and SlnfFileHelper.cs from the main dotnet project
  • Add Microsoft.VisualStudio.SolutionPersistence package reference
  • Include CliStrings.cs shim

Modified: src/Cli/dotnet/Parser.cs (#if CLI_AOT section)

  • Add sln command with list, migrate, and remove subcommands
  • SLN_FILE argument on parent sln command (matching managed CLI structure)
  • Inline action handlers with error handling matching managed implementations
  • sln remove: includes project removal from .sln and .slnf files, empty folder cleanup, directory-to-project resolution, and misplaced-argument validation
  • Bare dotnet sln (no subcommand) falls back to managed CLI for complete help

Design decisions

  • Argument structure: SLN_FILE is on the parent sln command, not subcommands, matching managed CLI. This prevents parsing ambiguity with sln remove where an optional first arg + required variadic arg could misparse.
  • Fallback safety: Unknown subcommands (e.g., sln add) produce parse errors, causing NativeEntryPoint.cs to fall back to managed dotnet.dll. Bare dotnet sln also falls back for complete help.
  • Hardcoded English strings: Matches the existing AOT pattern used by --version and --info.
  • Inlined GetProjectFileFromDirectory: The managed version delegates through MsbuildProject to ProjectLocator, but the actual logic is pure file I/O (dir.GetFiles("*proj")). Inlined to avoid pulling in MsbuildProject dependencies.

Testing

  • All 157 sln tests pass (list: 54, migrate: 2, remove: 101)
  • Both dotnet-aot and main dotnet projects build with 0 warnings, 0 errors
  • Full build.cmd completes (only pre-existing ApiCompat file-locking errors unrelated to this change)

NikolaMilosavljevic and others added 4 commits May 19, 2026 10:09
Enable 'sln list' and 'sln migrate' commands in the AOT-compiled CLI
(dotnet-aot). These commands are self-contained — they parse solution
files using Microsoft.VisualStudio.SolutionPersistence without any
MSBuild or NuGet dependencies.

Changes:
- Parser.cs: Add sln command with list/migrate subcommands under
  #if CLI_AOT, with inline action handlers that avoid CommandBase
  and its ParseResultExtensions dependency chain
- dotnet-aot.csproj: Link SlnFileFactory.cs and SlnfFileHelper.cs,
  add CliStrings.resx as embedded resource, add
  Microsoft.VisualStudio.SolutionPersistence package reference

Design decisions:
- SLN_FILE argument is on each subcommand (not parent) so that
  unsupported commands like 'sln add/remove' produce parse errors
  and correctly fall back to the managed CLI
- Hardcoded English strings for AOT-specific messages (matching the
  existing --info AOT pattern) to avoid CliCommandStrings.resx dep
- GracefulException handling wraps all action handlers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement sln remove subcommand in the CLI_AOT parser section,
including project removal from both .sln and .slnf files, empty
solution folder cleanup, and directory-to-project resolution.

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

- sln migrate: Use same error/success messages as managed implementation
  ('Only .sln files can be migrated' and '.slnx file {0} generated.')
- sln remove: Add validation to detect misplaced .sln/.slnx files in
  project arguments with 'Did you mean' suggestion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move SLN_FILE argument from individual subcommands to the parent sln
command, matching the managed CLI structure. This fixes a parsing
ambiguity where 'dotnet sln remove proj1.csproj proj2.csproj' could
misinterpret the first project as the solution file.

Also:
- Remove parent sln action handler so bare 'dotnet sln' falls back
  to managed CLI (which shows complete help including 'add')
- Use .GetAwaiter().GetResult() instead of .Wait() in migrate

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 19, 2026 19:13
@baronfel
Copy link
Copy Markdown
Member

@NikolaMilosavljevic as we start lighting up these commands, a before/after comparison of time spent would be very useful data to have!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Native AOT support for dotnet sln list, sln migrate, and sln remove by wiring inline handlers into the CLI_AOT Parser, linking the SlnFileFactory/SlnfFileHelper source files into the AOT project, and providing a hand-written CliStrings shim with the subset of strings these flows need. Unknown sln subcommands and bare dotnet sln deliberately fail to parse so NativeEntryPoint falls back to the managed CLI.

Changes:

  • Add sln list/migrate/remove handlers in Parser.cs under #if CLI_AOT, including misplaced-sln-file detection, directory-to-project resolution, and empty-solution-folder cleanup.
  • Link SlnFileFactory.cs/SlnfFileHelper.cs and add the Microsoft.VisualStudio.SolutionPersistence package reference to the AOT csproj.
  • Introduce src/Cli/dotnet-aot/CliStrings.cs as a string shim with hard-coded English values for the resources used by the linked sources.

Reviewed changes

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

File Description
src/Cli/dotnet/Parser.cs New ConfigureSolutionCommand, inline list/migrate/remove handlers, and helpers (GetProjectFileFromDirectory, RemoveProjectsFromSolution, RemoveProjectsFromSolutionFilter) duplicated from the managed CLI with hard-coded English strings.
src/Cli/dotnet-aot/dotnet-aot.csproj Links SlnFileFactory.cs/SlnfFileHelper.cs, adds Microsoft.VisualStudio.SolutionPersistence package, includes the new CliStrings.cs shim.
src/Cli/dotnet-aot/CliStrings.cs New shim providing the subset of CliStrings properties (matching the values in CliStrings.resx) consumed by the linked AOT sources.

Comment thread src/Cli/dotnet/Parser.cs Outdated
Comment thread src/Cli/dotnet/Parser.cs Outdated
Comment thread src/Cli/dotnet-aot/CliStrings.cs Outdated
Comment thread src/Cli/dotnet/Parser.cs
Comment thread src/Cli/dotnet/Parser.cs Outdated
Replace duplicated inline command definitions and action handlers with
shared code from Microsoft.DotNet.Cli.Definitions project:

- Use SolutionCommandDefinition from Definitions project instead of
  creating ad-hoc Command instances in Parser.cs AOT section
- Use SolutionCommandParser to wire actions for list/migrate/remove
- Link command implementation files (SolutionListCommand,
  SolutionMigrateCommand, SolutionRemoveCommand) from managed project
- Link CommandBase, CommandParsingException, SolutionArgumentValidator,
  and MsbuildProject with #if CLI_AOT guards
- Replace hand-written CliStrings.cs shim with proper .resx inclusion
  (CliStrings.resx + CliCommandStrings.resx with GenerateSource)
- Add Definitions project reference to dotnet-aot.csproj
- Add #if CLI_AOT guards in CommandBase.cs (skip ShowHelpOrErrorIfAppropriate)
- Add #if CLI_AOT guards in MsbuildProject.cs (inline file-I/O methods,
  exclude MSBuild-dependent code)
- Add #if CLI_AOT guards in SolutionCommandParser.cs (skip add command
  and parent action wiring)
- Remove AddCommand from AOT parser to trigger managed fallback
- Add localized strings for project directory lookup errors

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NikolaMilosavljevic
Copy link
Copy Markdown
Member Author

Refactoring summary (commit 908be28)

This commit addresses the review feedback by refactoring the AOT sln commands to share definitions and implementations with the managed CLI instead of duplicating them:

Key changes:

  • Shared command definitions: Uses SolutionCommandDefinition from the Microsoft.DotNet.Cli.Definitions project instead of creating ad-hoc Command instances inline. The AddCommand is removed from the AOT tree so dotnet sln add falls back to managed CLI.
  • Shared command implementations: Links SolutionListCommand.cs, SolutionMigrateCommand.cs, SolutionRemoveCommand.cs, SolutionArgumentValidator.cs, CommandBase.cs, and SolutionCommandParser.cs from the managed project.
  • Proper localization: Replaced the hand-written CliStrings.cs shim with linked .resx files (CliStrings.resx + CliCommandStrings.resx) using GenerateSource=true.
  • #if CLI_AOT guards: Added conditional compilation in shared files:
    • MsbuildProject.cs: Exposes only file-I/O methods (GetProjectFileFromDirectory/TryGetProjectFileFromDirectory) for AOT; wraps MSBuild-dependent code with #if !CLI_AOT.
    • CommandBase.cs: Skips ShowHelpOrErrorIfAppropriate in AOT (handled by native entry point fallback).
    • SolutionCommandParser.cs: Skips add command wiring and parent action in AOT.
  • AOT fallback safety: Added a validator on the parent sln command requiring a subcommand (so bare dotnet sln triggers managed fallback). Added GracefulException handling in NativeEntryPoint.cs.

Net effect on Parser.cs AOT section:

  • Before: ~300 lines of inline command definitions, action handlers, and helper methods
  • After: ~65 lines using shared definitions and SolutionCommandParser

All 157 sln tests pass (list: 54, migrate: 2, remove: 101).

Copy link
Copy Markdown
Member

@baronfel baronfel left a comment

Choose a reason for hiding this comment

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

Love the new approach - it makes a lot of sense to me!

Comment thread src/Cli/dotnet-aot/NativeEntryPoint.cs Outdated
Comment thread src/Cli/dotnet/Parser.cs
@NikolaMilosavljevic
Copy link
Copy Markdown
Member Author

One additional note on the refactoring commit: the --version and --info options in the AOT Parser.CreateCommand() had their hardcoded English Description strings removed and Arity = ArgumentArity.Zero added instead, to match the managed DotNetCommandDefinition.cs definitions. The AOT parser doesn't render help (bare dotnet falls back to managed CLI), so the descriptions aren't needed, and this avoids the unlocalized string concern.

NikolaMilosavljevic and others added 2 commits May 21, 2026 10:08
Address review feedback: replace the sketchy Data dictionary check with
a direct catch of GracefulException, which is the specific exception
type used to signal user-displayable errors.

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

Instead of removing AddCommand from the definition tree in Parser.cs,
wire it (and the parent sln command) to throw a dedicated exception
in AOT mode. NativeEntryPoint catches CommandNotAvailableInAotException
and falls back to managed CLI. This keeps AOT customization local to
SolutionCommandParser.cs and the definition tree intact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants