Add dotnet sdk add and dotnet sdk remove commands#54577
Conversation
There was a problem hiding this comment.
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
ProjectSdkReferenceHelperfor 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. |
8ff960d to
8a995dd
Compare
97b0cb9 to
0ad2f49
Compare
There was a problem hiding this comment.
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
TryRemoveSdkreturns after removing the first matching occurrence in both the semicolon-delimitedSdkattribute 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 returningtrueif 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
TryRemoveSdkdoesn’t appear to be covered for duplicate SDK references (e.g., the same SDK listed twice inProject 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.
There was a problem hiding this comment.
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
TryRemoveSdkrecomputes 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:1TryRemoveSdkrecomputes 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.
There was a problem hiding this comment.
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 existingProject.Sdklist. This can change import ordering semantics and mixes two representations in the same file. Prefer appending a newSdkReferenceintoproject.Sdkwhen it is already non-empty (and the new SDK is not found), preserving the existing style and the relative ordering of SDK imports.
71d3483 to
eca6ab2
Compare
7f7525a to
132ac6c
Compare
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
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
132ac6c to
98c4605
Compare
Summary
dotnet sdk addanddotnet sdk removefor managing MSBuild SDK references in SDK-style project files and file-based apps (#:sdkdirectives).global.jsonmsbuild-sdks, bundled SDKs, or NuGet; protects the primary SDK from removal; rolls back project changes on failed restore.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-restoreadds<Sdk>element to a projectdotnet sdk add Cake.Sdk@6.2.0works with@version syntaxProject Sdk="..."attributes are updated without dropping other SDKs<Sdk>elementsglobal.jsonor bundled with the .NET SDKglobal.jsonfails gracefully with a clear errordotnet sdk removeremoves additive SDK references from projects and files#:sdkdirectives preserve primary SDK order on addGivenDotnetAddSdkandGivenDotnetRemoveSdkintegration tests passExample usage & use cases
Use cases
Cake.Sdkor custom MSBuild SDK packages without hand-editing.csprojfiles.Project Sdk="Microsoft.NET.Sdk;Aspire.AppHost.Sdk/9.1.0"references safely.global.jsoncentralizesmsbuild-sdks, or pin explicitly with--version/@version.#:sdkdirectives the same way as package references, with primary SDK order preserved.