Skip to content

Add explicit migration support to dotnetup install#53909

Open
dsplaisted wants to merge 12 commits intodotnet:release/dnupfrom
dsplaisted:fix/dotnetup-migrate
Open

Add explicit migration support to dotnetup install#53909
dsplaisted wants to merge 12 commits intodotnet:release/dnupfrom
dsplaisted:fix/dotnetup-migrate

Conversation

@dsplaisted
Copy link
Copy Markdown
Member

@dsplaisted dsplaisted commented Apr 16, 2026

Summary

  • add explicit support for migrating existing system-managed .NET installs during dotnetup sdk install and dotnetup runtime install via --migrate-from-system
  • keep migration prompting in dotnetup init, but stop re-prompting during later install commands after the user has already gone through init
  • simplify the shared init/install flow around that behavior

Fixes #53839
Fixes #53841

Copilot AI review requested due to automatic review settings April 16, 2026 00:51
@dsplaisted dsplaisted requested a review from a team as a code owner April 16, 2026 00:51
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 an explicit dotnetup migrate command to migrate (reinstall) existing system .NET installs into the dotnetup-managed install root, and adjusts the install/init workflows to reduce migration prompting during normal install flows. This fits into the dotnetup CLI command set and its interactive init/migration workflows.

Changes:

  • Introduce dotnetup migrate command + parser wiring and help text.
  • Deduplicate system install candidates (component/version/arch) and improve migration list display (optionally include architecture).
  • Suppress migration prompting during interactive install flows by passing a new promptForMigration flag through BaseConfigurationWalkthrough.

Reviewed changes

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

Show a summary per file
File Description
test/dotnetup.Tests/ParserTests.cs Adds parser coverage for the new migrate command and ensures it appears in root help.
test/dotnetup.Tests/InitWorkflowTests.cs Adds tests for deduplication, architecture display formatting, and disabling migration prompting in a specific workflow invocation.
src/Installer/dotnetup/Strings.resx Adds localized strings for migrate command and --noninteractive option descriptions.
src/Installer/dotnetup/Parser.cs Registers the migrate subcommand and includes it in help grouping.
src/Installer/dotnetup/DotnetEnvironmentManager.cs Deduplicates enumerated system installs and sorts by ReleaseVersion comparison.
src/Installer/dotnetup/Commands/Shared/InstallWorkflow.cs Routes interactive installs through init flow without migration prompting (new flag).
src/Installer/dotnetup/Commands/Migrate/MigrateCommandParser.cs Defines the migrate command and options.
src/Installer/dotnetup/Commands/Migrate/MigrateCommand.cs Implements migrate execution flow (candidate discovery, prompting/noninteractive, batch execution).
src/Installer/dotnetup/Commands/Init/InitWorkflows.cs Adds promptForMigration plumbing, factors out candidate discovery, adds arch-aware display formatting, and returns failures from migration batch execution.

Comment thread src/Installer/dotnetup/Commands/Shared/InstallWorkflow.cs Outdated
Comment thread src/Installer/dotnetup/Commands/Init/InitWorkflows.cs Outdated
Comment thread src/Installer/dotnetup/Strings.resx Outdated
Comment thread src/Installer/dotnetup/Commands/Migrate/MigrateCommand.cs Outdated
@dsplaisted dsplaisted changed the title Add explicit dotnetup migrate command Move dotnetup migration into install commands Apr 20, 2026
@dsplaisted dsplaisted changed the title Move dotnetup migration into install commands Add explicit migration support to dotnetup install Apr 20, 2026
Adds --migrate-from-system flag to sdk install and runtime install commands.
Migration discovers existing system .NET installs, maps them to update channels,
deduplicates, and merges into the install batch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsplaisted dsplaisted force-pushed the fix/dotnetup-migrate branch from 58e7c38 to 830304e Compare April 23, 2026 17:03
@dsplaisted dsplaisted requested a review from nagilson April 23, 2026 17:08
Copy link
Copy Markdown
Member

@nagilson nagilson left a comment

Choose a reason for hiding this comment

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

First round of feedback for today I could get to - let me know what you think!


rootCommand.SetAction(parseResult =>
{
return new InitCommand(parseResult).Execute();
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.

This is ok but noteworthy to me: Now, if you run just 'dotnetup' and the config if missing or deleted - we will never configure again after first run for install workflow, where we used to before.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If the config is deleted, then InstallWorkflow.ShouldRunFirstUseOnboarding should return true so you should get the init walkthrough again.

This should be true regardless of whether you're running dotnetup or dotnetup sdk install.

Comment thread src/Installer/dotnetup/DotnetEnvironmentManager.cs
SpectreAnsiConsole.MarkupLine(DotnetupTheme.Dim(
$"Migrating {migrationSelections.Count} matching system .NET channel(s) because --migrate-from-system was specified."));

return InitWorkflows.MergeInstallRequests(
Copy link
Copy Markdown
Member

@nagilson nagilson May 5, 2026

Choose a reason for hiding this comment

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

I believe there is an architectural code drift introduced here between different agent sessions which introduces coupling.

PromptUserForMigration is inside InitWorkflows
MergeRequestedMigration is inside InstallWorkflows

Before:
InstallWorkflows depends on InitWorkflows

Now:
InstallWorkflows depends on InitWorkflows
InitWorkflows depends on InstallWorkflows, calling ShouldRunFirstUseOnboarding and similarly new methods

What if we move all of the migration logic into a separate class? The arg for migration can still be forwarded and respected.

This also makes me think the decision making such as ShouldRunFirstUseOnboarding should not be known to the InstallWorkflow - which was the intent of the initial design with how it wrapped things.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Personally I do not mind the circular dependency. For me, this PR untangles the code flow and makes it a lot easier to follow. We could improve it more, but:

  • I think it's better to favor smaller, incremental improvements rather than trying to do everything in one PR
  • I'm not sure how this is going to change when we do the UI refactoring for the init workflow.

Also, note that InitWorkflows already depends on InstallWorkflows: https://github.com/dotnet/sdk/blob/release/dnup/src/Installer/dotnetup/Commands/Init/InitWorkflows.cs#L95-L98

I haven't had a chance to dive into your comment about ShouldRunFirstUseOnboarding.

Comment thread src/Installer/dotnetup/Commands/Init/InitWorkflows.cs Outdated
Comment thread src/Installer/dotnetup/Commands/Init/InitWorkflows.cs Outdated
Comment thread src/Installer/dotnetup/Commands/Shared/InstallWorkflow.cs
dsplaisted and others added 9 commits May 6, 2026 16:48
… starter channel

- MergeInstallRequests now forwards Untracked, Verbosity, and RequireMuxerUpdate from the install command to migration install requests (was previously dropping them).

- Init walkthrough and --migrate-from-system path now use a two-phase install: SDK migrations install with the primary requests, then runtime / aspnetcore / windowsdesktop migrations whose shared-framework folder for the same major.minor is already on disk are skipped (avoids duplicate downloads).

- ShouldPromptForStarterChannel only fires for SDK installs, so 'dotnetup runtime install' on first run no longer turns into an SDK install via the starter-channel prompt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the major.minor band match with an exact-version check that uses ChannelVersionResolver to resolve each runtime migration's channel to the same version the install would actually produce. The resolver is already shared per command and reuses cached release manifest data, so this is an in-process lookup in the common case.

This avoids the band-match's downgrade pitfall (e.g. system has 8.0.20 from a security update but SDK ships 8.0.10): the resolver returns the latest published 8.0 patch, the on-disk check looks for that exact folder, and the migration runs when the SDK didn't already produce it. When resolution returns null we keep the migration so the install can surface a clear error rather than silently skipping.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both onboarding paths construct InitWorkflows and call InitWalkthrough; the only difference is whether requests are pre-resolved or deferred to the starter-channel prompt. Fold them into a single branch with a conditional initial-requests value.

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

Both InitWorkflows.RunInstallsWithMigration and InstallWorkflow.ExecuteTwoPhaseMigrationInstall had near-identical bodies that partitioned migrations, merged Phase 1 requests, ran them, filtered runtime migrations against disk, merged Phase 2 requests, and combined the result. The only real difference was the runner each used (RunInstallRequests with a predownload task vs ExecuteInstallRequests with manifest-aware exception handling).

Extract the request-building into BuildMigrationPhase1Requests and BuildMigrationPhase2Requests on InitWorkflows, leaving each caller to drive its own runner. FilterRuntimeMigrationsAgainstDisk becomes a private implementation detail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
RunInstallRequests in the init walkthrough was calling InstallExecutor.ExecuteInstalls but discarding the InstallBatchResult, so install failures were displayed but never thrown. The init flow then proceeded to save config and apply env modifications as if the installs had succeeded.

Move telemetry recording and ExceptionDispatchInfo rethrow into a shared InstallExecutor.ExecuteInstallsAndThrowOnFailure helper used by both the init and install code paths.

Also fold the {Untracked, Verbosity, RequireMuxerUpdate} arg trio in MergeInstallRequests into a single InstallCommand parameter via a private BuildMigrationInstallOptions helper, removing per-caller field plumbing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both InitWorkflows.RunInstallsWithMigration and InstallWorkflow.ExecuteWithMigration drove the same Phase1/Phase2 sequence with copy-pasted boilerplate. Extract InitWorkflows.ExecuteMigrationInPhases that takes an Action runner so each call site supplies only its phase-specific behavior (banner + predownload await for the init walkthrough; plain ExecuteInstallRequests for the install command).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
InitWorkflows had grown into a dual-purpose class: the interactive init walkthrough plus the non-interactive migration helpers shared with InstallWorkflow. Pull the non-interactive pieces into a new internal static MigrationWorkflow in Commands.Shared:

  - MigrationSelection record

  - GetMigrationCandidates / BuildMigrationSelections

  - GetTrackedMigrationChannels / GetTrackedMigrationChannelName

  - ExecuteMigrationInPhases / BuildMigrationPhase{1,2}Requests

  - MergeInstallRequests / BuildMigrationInstallOptions

  - FilterRuntimeMigrationsAgainstDisk / RuntimeFolderExistsOnDisk

InitWorkflows keeps the interactive surface (PromptInstallsToMigrateIfDesired, PromptUserForMigration, FormatMigrationDisplayItems, the Phase 1/2 runner with banner+predownload, etc.). InstallWorkflow no longer has to reach into Init to find migration types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
InstallExecutor.ExecuteInstalls already short-circuits to a no-op InstallBatchResult when the request list is empty, so calling ExecuteInstallRequests(requests) here did nothing visible — no print, no telemetry, no throw. The early return is the part that mattered (it prevents requests[0] from throwing on the next line).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the 9 tests that target MigrationWorkflow (GetMigrationCandidates, BuildMigrationSelections, MergeInstallRequests) out of InitWorkflowTests.cs and into a new MigrationWorkflowTests.cs file, mirroring the recent split of MigrationWorkflow from InitWorkflows.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants