Skip to content

Add dotnet sdk add and dotnet sdk remove commands#54577

Open
devlead wants to merge 7 commits into
dotnet:mainfrom
devlead:feature/addRemoveSDK
Open

Add dotnet sdk add and dotnet sdk remove commands#54577
devlead wants to merge 7 commits into
dotnet:mainfrom
devlead:feature/addRemoveSDK

Conversation

@devlead
Copy link
Copy Markdown
Contributor

@devlead devlead commented Jun 3, 2026

Summary

  • Adds dotnet sdk add and dotnet sdk remove for managing MSBuild SDK references in SDK-style project files and file-based apps (#:sdk directives).
  • Supports version resolution from explicit input, global.json msbuild-sdks, bundled SDKs, or NuGet; protects the primary SDK from removal; rolls back project changes on failed restore.
  • Includes hidden aliases (dotnet add sdk, dotnet remove sdk), localized strings, and integration tests for project and file-based scenarios.

Test plan

  • dotnet sdk add Cake.Sdk --version 6.2.0 --no-restore adds <Sdk> element to a project
  • dotnet sdk add Cake.Sdk@6.2.0 works with @ version syntax
  • Semicolon-delimited Project Sdk="..." attributes are updated without dropping other SDKs
  • Additive SDKs are inserted after the primary SDK / existing <Sdk> elements
  • Version omitted when SDK is in global.json or bundled with the .NET SDK
  • Malformed global.json fails gracefully with a clear error
  • Restore failure rolls back project file changes (including encoding)
  • dotnet sdk remove removes additive SDK references from projects and files
  • Primary SDK removal is rejected before any file changes are persisted
  • Batch remove is transactional when primary SDK is in the list
  • File-based #:sdk directives preserve primary SDK order on add
  • GivenDotnetAddSdk and GivenDotnetRemoveSdk integration tests pass

Example usage & use cases

# Add an additive SDK to a project (creates <Sdk Name="Cake.Sdk" Version="6.2.0" />)
dotnet sdk add Cake.Sdk --version 6.2.0

# Same using name@version syntax
dotnet sdk add Cake.Sdk@6.2.0

# Add without pinning a version when global.json or the bundled SDK supplies it
dotnet sdk add Microsoft.NET.Sdk.Web

# Add an Aspire AppHost SDK alongside the primary SDK
dotnet sdk add Aspire.AppHost.Sdk@9.1.0

# Add to a specific project
dotnet sdk add Cake.Sdk@6.2.0 --project ./src/MyApp/MyApp.csproj

# Add to a file-based app
dotnet add sdk Cake.Sdk@6.2.0 --file Program.cs

# Skip restore (useful in scripts or when restore will run separately)
dotnet sdk add Cake.Sdk@6.2.0 --no-restore

# Remove one additive SDK
dotnet sdk remove Cake.Sdk

# Remove Aspire.AppHost.Sdk from a multi-SDK project
dotnet sdk remove Aspire.AppHost.Sdk

# Remove multiple SDKs in one command
dotnet sdk remove Cake.Sdk Aspire.AppHost.Sdk

# Remove from a file-based app
dotnet remove sdk Cake.Sdk --file Program.cs

Use cases

  • Build tooling SDKs — Add SDKs like Cake.Sdk or custom MSBuild SDK packages without hand-editing .csproj files.
  • Aspire / multi-SDK projects — Manage semicolon-delimited Project Sdk="Microsoft.NET.Sdk;Aspire.AppHost.Sdk/9.1.0" references safely.
  • Version pinning policy — Omit versions when global.json centralizes msbuild-sdks, or pin explicitly with --version / @version.
  • File-based apps — Manage #:sdk directives the same way as package references, with primary SDK order preserved.
  • Cleanup / migration — Remove additive SDK references when migrating off a tool or consolidating SDK usage, without risking accidental removal of the primary project SDK.

Copilot AI review requested due to automatic review settings June 3, 2026 14:37
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

Note

Copilot was unable to run its full agentic suite in this review.

Adds new dotnet sdk add / dotnet sdk remove functionality (including hidden dotnet add sdk / dotnet remove sdk) for both SDK-style MSBuild projects and file-based apps, plus supporting helpers, command definitions, localizations, and tests.

Changes:

  • Introduces CLI commands for adding/removing SDK references (project + file-based app directives).
  • Adds ProjectSdkReferenceHelper for parsing/updating SDK references in MSBuild projects.
  • Expands test coverage and localization resources for the new command surface.

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
test/dotnet.Tests/CommandTests/Sdk/Remove/GivenDotnetRemoveSdk.cs Adds end-to-end tests for removing SDK references (project + file-based).
test/dotnet.Tests/CommandTests/Sdk/Add/GivenDotnetAddSdk.cs Adds end-to-end tests for adding/updating SDK references (project + file-based).
test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs Adds regression test ensuring new SDK directives preserve primary SDK order.
src/Cli/dotnet/ProjectSdkReferenceHelper.cs Implements MSBuild project SDK reference add/update/remove logic.
src/Cli/dotnet/Commands/Sdk/SdkCommandParser.cs Wires sdk add / sdk remove actions into the dotnet sdk command.
src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs Implements dotnet sdk add for projects and file-based apps, including restore behavior.
src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs Adds version resolution logic (global.json, bundled SDK, NuGet latest).
src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs Implements dotnet sdk remove for projects and file-based apps.
src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs Updates directive insertion logic to keep primary SDK directive first.
src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs Wires hidden dotnet add sdk to the new implementation.
src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs Wires hidden dotnet remove sdk to the new implementation.
src/Cli/dotnet/Commands/CliCommandStrings.resx Adds new user-facing CLI messages for SDK add/remove/version resolution errors.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf Adds localization entries for new SDK-related CLI strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs Adds parsing/typing for SDK identity argument (name[@Version]).
src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkCommandDefinition.cs Adds sdk add / sdk remove subcommands to the sdk command definition.
src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkAddCommandDefinition.cs Defines the dotnet sdk add command shape/options.
src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkRemoveCommandDefinition.cs Defines the dotnet sdk remove command shape/options.
src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/AddCommandDefinition.cs Adds hidden add sdk command definition.
src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/Sdk/AddSdkCommandDefinition.cs Defines hidden dotnet add sdk subcommand mapping.
src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/RemoveCommandDefinition.cs Adds hidden remove sdk command definition.
src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/Sdk/RemoveSdkCommandDefinition.cs Defines hidden dotnet remove sdk subcommand mapping.
src/Cli/Microsoft.DotNet.Cli.Definitions/CommandDefinitionStrings.resx Adds new command/argument strings for SDK add/remove.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hant.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hans.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.tr.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ru.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pt-BR.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pl.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ko.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ja.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.it.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.fr.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.es.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.de.xlf Adds localization entries for new SDK command definition strings.
src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.cs.xlf Adds localization entries for new SDK command definition strings.

Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs Outdated
Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs Outdated
Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs
Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs Outdated
Comment thread src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs
Comment thread src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkAddCommandDefinition.cs Outdated
Comment thread src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkAddCommandDefinition.cs Outdated
Comment thread src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs
Comment thread src/Cli/dotnet/Commands/CliCommandStrings.resx
Comment thread src/Cli/dotnet/Commands/CliCommandStrings.resx
@devlead devlead force-pushed the feature/addRemoveSDK branch from 8ff960d to 8a995dd Compare June 3, 2026 14:57
@devlead devlead requested a review from Copilot June 3, 2026 14:58
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

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

Comment thread src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs Outdated
Comment thread src/Cli/dotnet/Commands/CliCommandStrings.resx
Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs Outdated
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

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

Comments suppressed due to low confidence (2)

src/Cli/dotnet/ProjectSdkReferenceHelper.cs:1

  • TryRemoveSdk returns after removing the first matching occurrence in both the semicolon-delimited Sdk attribute and in <Sdk ... /> elements. If a project file contains duplicate SDK references (manually edited or merged), dotnet sdk remove <name> will leave additional occurrences behind. Consider removing all matches in a single call (and returning true if at least one was removed) to make removal idempotent and consistent with the file-based removal logic which removes multiple directives.
    src/Cli/dotnet/ProjectSdkReferenceHelper.cs:1
  • The new removal behavior in TryRemoveSdk doesn’t appear to be covered for duplicate SDK references (e.g., the same SDK listed twice in Project Sdk="A;B;B" or repeated <Sdk Name="B" /> elements). Adding a test case in the new SDK remove test suite to assert all duplicates are removed would help lock down the expected semantics and prevent regressions.

Comment thread src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs
Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs
Comment thread src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkAddCommandDefinition.cs Outdated
@devlead devlead requested a review from Copilot June 3, 2026 22:25
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

Copilot reviewed 47 out of 47 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

src/Cli/dotnet/ProjectSdkReferenceHelper.cs:1

  • TryRemoveSdk recomputes the primary SDK check (which re-parses SDK references) inside both removal loops. Since “cannot remove primary” depends only on (project, sdkName), this can be checked once up-front before scanning/removing, reducing repeated parsing and allocations.
    src/Cli/dotnet/ProjectSdkReferenceHelper.cs:1
  • TryRemoveSdk recomputes the primary SDK check (which re-parses SDK references) inside both removal loops. Since “cannot remove primary” depends only on (project, sdkName), this can be checked once up-front before scanning/removing, reducing repeated parsing and allocations.

Comment thread src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs Outdated
@devlead devlead requested a review from Copilot June 3, 2026 22:34
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

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

Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs Outdated
Comment thread src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs
Comment thread src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs
@devlead devlead requested a review from Copilot June 3, 2026 22:50
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

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

Comment thread src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs Outdated
Comment thread src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs
@devlead devlead requested a review from Copilot June 3, 2026 23:00
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

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

Comments suppressed due to low confidence (1)

src/Cli/dotnet/ProjectSdkReferenceHelper.cs:1

  • When a project already uses the Project Sdk=\"...;...\" attribute form (semicolon-delimited SDKs), adding a new SDK currently falls through to creating a <Sdk Name=\"...\" /> element instead of appending to the existing Project.Sdk list. This can change import ordering semantics and mixes two representations in the same file. Prefer appending a new SdkReference into project.Sdk when it is already non-empty (and the new SDK is not found), preserving the existing style and the relative ordering of SDK imports.

@devlead devlead force-pushed the feature/addRemoveSDK branch 4 times, most recently from 71d3483 to eca6ab2 Compare June 5, 2026 05:43
@devlead devlead force-pushed the feature/addRemoveSDK branch 2 times, most recently from 7f7525a to 132ac6c Compare June 5, 2026 18:52
devlead added 4 commits June 6, 2026 10:09
Introduce CLI commands for managing MSBuild SDK references in project
files and file-based apps, mirroring the existing package add/remove
experience.

New commands:
- `dotnet sdk add <SDK_NAME>[@Version]` — add or update an SDK reference
- `dotnet sdk remove <SDK_NAME>...` — remove one or more SDK references
- Hidden aliases: `dotnet add sdk` and `dotnet remove sdk`

Command definitions and parsing:
- Add SdkAddCommandDefinition/SdkRemoveCommandDefinition with shared base
  classes for public and hidden command variants
- Add SdkReferenceIdentity argument parsing (name or name@version)
- Wire commands into SdkCommandDefinition, AddCommandDefinition, and
  RemoveCommandDefinition

Implementation:
- ProjectSdkReferenceHelper — read/update/remove SDK references in
  SDK-style projects (Project Sdk attribute and <Sdk> elements), with
  primary SDK protection and additive SDK insertion order preserved
- SdkAddVersionHelper — resolve SDK version when not specified: honor
  global.json msbuild-sdks, omit version for bundled SDKs, otherwise
  query NuGet for latest stable
- SdkAddCommand — supports --project, --file, --version, --no-restore,
  and --interactive; rolls back project file on restore failure
- SdkRemoveCommand — batch removal with upfront primary-SDK validation
  so partial removals are not persisted on failure
- FileBasedAppSourceEditor — append new #:sdk directives after existing
  SDKs to preserve primary SDK ordering

Localization:
- Add command definition and CLI output strings (resx + xlf)

Tests:
- Integration tests for add/remove across project and file-based scenarios
- Unit test for SDK directive insertion order in FileBasedAppSourceEditor
Fix nullable reference usage, restore argument handling, CLI messaging,
and command definition visibility based on code review.

SdkAddCommand:
- Read project file snapshot as non-nullable `byte[]` only when a save is
  needed, before rollback on restore failure
- Always pass `--nologo` to restore and append `--interactive` when
  requested, instead of making the flags mutually exclusive

SdkRemoveCommand:
- Use file-based-specific error message when blocking removal of the
  primary SDK from a `#:sdk` directive

Command definitions:
- Make SdkAddCommandDefinition and SdkAddCommandDefinitionBase internal
  to match other SDK command definitions

Localization:
- Clarify SdkIdentityArgumentIdIsNull to cover null or empty SDK names
- Change *InProject CLI strings to say "project" instead of "file"
- Add CannotRemovePrimarySdkReferenceInFile for file-based apps
- Update resx and xlf files accordingly

Tests:
- Update project-based add SDK assertions to expect "project" wording
Plumb `--interactive` through sdk remove, clarify primary SDK error
messaging, and isolate NuGet version-resolution temp files.

SdkRemoveCommand:
- Pass the parsed `--interactive` value to `MsbuildProject.FromFile` so the
  option is functional for project-based removals

SdkAddVersionHelper:
- Resolve NuGet SDK versions in a per-run directory under
  `%TEMP%\dotnet-sdk-add\{guid}` instead of the temp root
- Delete the directory after version resolution completes

Localization:
- Reword `CannotRemovePrimarySdkReference` to be representation-agnostic
  (covers both `Project Sdk="..."` and top-level `<Sdk>` primary references)
- Update resx and xlf files accordingly
- Handle empty SDK identity tokens explicitly in CreateRequiredSdkReferenceIdentityArgument
  by throwing GracefulException with SdkIdentityArgumentIdIsNull instead of relying on
  null-forgiving operator that could surface NullReferenceException
- Type --version as Option<string?> to reflect that the option is optional
- Catch UnauthorizedAccessException and SecurityException when reading global.json so
  unreadable files produce the same graceful GlobalJsonFileParsingFailed message as
  JSON and IO failures
devlead added 3 commits June 6, 2026 10:09
Use Tokens.FirstOrDefault() instead of Tokens[0] when parsing the required
sdkId argument so an empty token list fails with SdkIdentityArgumentIdIsNull
via GracefulException rather than ArgumentOutOfRangeException.
- Use stable sdkId argument name for sdk remove with CmdSdk as HelpName for
  culture-invariant binding and consistent help display
- Do not wrap OperationCanceledException or GracefulException when resolving
  latest NuGet SDK versions so cancellation and existing CLI errors keep
  their intended semantics
- Deduplicate SDK names case-insensitively before remove validation and
  removal to avoid misleading "not found" messages for repeated arguments
- Add GetSdkIdFromIdentityArgument to parse Name@Version arguments and return
  the SDK id so sdk remove matches sdk add identity syntax
- Ignore whitespace-only msbuild-sdks versions in global.json by trimming and
  using IsNullOrWhiteSpace so sdk add does not skip version resolution incorrectly
- Add integration test for removing an SDK reference using the @Version suffix
@devlead devlead force-pushed the feature/addRemoveSDK branch from 132ac6c to 98c4605 Compare June 6, 2026 08:09
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.

2 participants