diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/CommandDefinitionStrings.resx b/src/Cli/Microsoft.DotNet.Cli.Definitions/CommandDefinitionStrings.resx index 6b008ece4218..929394ae5fa5 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/CommandDefinitionStrings.resx +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/CommandDefinitionStrings.resx @@ -264,6 +264,15 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + SDK reference name must not be null or empty. + + + SDK version must not be empty when specified using the '@' separator. + Package reference id and version must not be null. @@ -539,6 +548,9 @@ This is equivalent to deleting project.assets.json. PACKAGE_NAME + + SDK_NAME + PACKAGE_DIR @@ -904,6 +916,18 @@ If not specified the file will be generated inside the default 'TestResults' dir The package reference to remove. + + Add an SDK reference to the project. + + + Add the SDK reference without running restore. + + + Remove an SDK reference from the project. + + + The SDK reference to remove. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/AddCommandDefinition.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/AddCommandDefinition.cs index 632a4ee0ac38..bc9cfaea864f 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/AddCommandDefinition.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/AddCommandDefinition.cs @@ -5,6 +5,7 @@ using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Hidden.Add.Package; using Microsoft.DotNet.Cli.Commands.Hidden.Add.Reference; +using Microsoft.DotNet.Cli.Commands.Hidden.Add.Sdk; using Microsoft.DotNet.Cli.Commands.Package; namespace Microsoft.DotNet.Cli.Commands.Hidden.Add; @@ -16,6 +17,7 @@ internal sealed class AddCommandDefinition : Command public readonly AddPackageCommandDefinition PackageCommand = new(); public readonly AddReferenceCommandDefinition ReferenceCommand = new(); + public readonly AddSdkCommandDefinition SdkCommand = new(); public readonly Argument ProjectOrFileArgument = PackageCommandDefinition.CreateProjectOrFileArgument(); @@ -28,5 +30,6 @@ public AddCommandDefinition() Arguments.Add(ProjectOrFileArgument); Subcommands.Add(PackageCommand); Subcommands.Add(ReferenceCommand); + Subcommands.Add(SdkCommand); } } diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/Sdk/AddSdkCommandDefinition.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/Sdk/AddSdkCommandDefinition.cs new file mode 100644 index 000000000000..25f3d6d3e2a2 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Add/Sdk/AddSdkCommandDefinition.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Cli.Commands.Sdk.Add; + +namespace Microsoft.DotNet.Cli.Commands.Hidden.Add.Sdk; + +internal sealed class AddSdkCommandDefinition() : SdkAddCommandDefinitionBase(Name) +{ + public new const string Name = "sdk"; + + public AddCommandDefinition Parent => (AddCommandDefinition)Parents.Single(); + + public override Argument? GetProjectOrFileArgument() + => Parent.ProjectOrFileArgument; +} diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/RemoveCommandDefinition.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/RemoveCommandDefinition.cs index ca286e026895..81a20e27c5bb 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/RemoveCommandDefinition.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/RemoveCommandDefinition.cs @@ -5,6 +5,7 @@ using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Hidden.Remove.Package; using Microsoft.DotNet.Cli.Commands.Hidden.Remove.Reference; +using Microsoft.DotNet.Cli.Commands.Hidden.Remove.Sdk; using Microsoft.DotNet.Cli.Commands.Package; namespace Microsoft.DotNet.Cli.Commands.Hidden.Remove; @@ -19,6 +20,7 @@ internal sealed class RemoveCommandDefinition : Command public readonly RemovePackageCommandDefinition PackageCommand = new(); public readonly RemoveReferenceCommandDefinition ReferenceCommand = new(); + public readonly RemoveSdkCommandDefinition SdkCommand = new(); public RemoveCommandDefinition() : base(Name, CommandDefinitionStrings.NetRemoveCommand) @@ -29,5 +31,6 @@ public RemoveCommandDefinition() Arguments.Add(ProjectOrFileArgument); Subcommands.Add(PackageCommand); Subcommands.Add(ReferenceCommand); + Subcommands.Add(SdkCommand); } } diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/Sdk/RemoveSdkCommandDefinition.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/Sdk/RemoveSdkCommandDefinition.cs new file mode 100644 index 000000000000..75bb9fc14203 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Hidden/Remove/Sdk/RemoveSdkCommandDefinition.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Cli.Commands.Sdk.Remove; + +namespace Microsoft.DotNet.Cli.Commands.Hidden.Remove.Sdk; + +internal sealed class RemoveSdkCommandDefinition() : SdkRemoveCommandDefinitionBase(Name) +{ + public new const string Name = "sdk"; + + public RemoveCommandDefinition Parent => (RemoveCommandDefinition)Parents.Single(); + + public override Argument? GetProjectOrFileArgument() + => Parent.ProjectOrFileArgument; +} diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkAddCommandDefinition.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkAddCommandDefinition.cs new file mode 100644 index 000000000000..701f9be14fec --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkAddCommandDefinition.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Commands.Package; + +namespace Microsoft.DotNet.Cli.Commands.Sdk.Add; + +internal abstract class SdkAddCommandDefinitionBase : Command +{ + public static Option CreateVersionOption() => new Option("--version", "-v") + { + Description = CommandDefinitionStrings.CmdVersionDescription, + HelpName = CommandDefinitionStrings.CmdVersion, + }; + + public static Option CreateNoRestoreOption() => new("--no-restore", "-n") + { + Description = CommandDefinitionStrings.SdkAddCmdNoRestoreDescription, + Arity = ArgumentArity.Zero + }; + + public static Option CreateInteractiveOption() => CommonOptions.CreateInteractiveOption().ForwardIfEnabled("--interactive"); + + public readonly Argument SdkIdArgument = CommonArguments.CreateRequiredSdkReferenceIdentityArgument(); + public readonly Option VersionOption = CreateVersionOption(); + public readonly Option NoRestoreOption = CreateNoRestoreOption(); + public readonly Option InteractiveOption = CreateInteractiveOption(); + public readonly Option ProjectOption = PackageCommandDefinition.CreateProjectOption(); + public readonly Option FileOption = PackageCommandDefinition.CreateFileOption(); + + public SdkAddCommandDefinitionBase(string name) + : base(name, CommandDefinitionStrings.SdkAddAppFullName) + { + VersionOption.Validators.Add(result => + { + if (result.Parent?.GetValue(SdkIdArgument).HasVersion == true) + { + result.AddError(CommandDefinitionStrings.ValidationFailedDuplicateVersion); + } + }); + + Arguments.Add(SdkIdArgument); + + Options.Add(VersionOption); + Options.Add(NoRestoreOption); + Options.Add(InteractiveOption); + Options.Add(ProjectOption); + Options.Add(FileOption); + } + + public abstract Argument? GetProjectOrFileArgument(); +} diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkRemoveCommandDefinition.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkRemoveCommandDefinition.cs new file mode 100644 index 000000000000..da8b4d7932a6 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Commands/Sdk/SdkRemoveCommandDefinition.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Commands.Package; + +namespace Microsoft.DotNet.Cli.Commands.Sdk.Remove; + +internal abstract class SdkRemoveCommandDefinitionBase : Command +{ + public readonly Argument SdkIdArgument = new(CommonArguments.SdkIdArgumentName) + { + Description = CommandDefinitionStrings.SdkRemoveAppHelpText, + HelpName = CommandDefinitionStrings.CmdSdk, + Arity = ArgumentArity.OneOrMore, + }; + + public readonly Option InteractiveOption = CommonOptions.CreateInteractiveOption().ForwardIfEnabled("--interactive"); + public readonly Option ProjectOption = PackageCommandDefinition.CreateProjectOption(); + public readonly Option FileOption = PackageCommandDefinition.CreateFileOption(); + + public SdkRemoveCommandDefinitionBase(string name) + : base(name, CommandDefinitionStrings.SdkRemoveAppFullName) + { + Arguments.Add(SdkIdArgument); + Options.Add(InteractiveOption); + Options.Add(ProjectOption); + Options.Add(FileOption); + } + + public abstract Argument? GetProjectOrFileArgument(); +} diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs index 07b7edc76507..3893b916d8dc 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonArguments.cs @@ -58,6 +58,68 @@ public static Argument CreateRequiredPackageIdentityAr return new PackageIdentityWithRange(packageId, versionRange); } + + public const string SdkIdArgumentName = "sdkId"; + + public static Argument CreateRequiredSdkReferenceIdentityArgument( + string exampleSdk = "Cake.Sdk", + string exampleVersion = "6.2.0") => + new(SdkIdArgumentName) + { + Description = string.Format(CommandDefinitionStrings.SdkIdentityArgumentDescription, exampleSdk, exampleVersion), + CustomParser = argumentResult => + { + SdkReferenceIdentity? identity = ParseSdkReferenceIdentity(argumentResult.Tokens.FirstOrDefault()?.Value); + if (identity is null) + { + throw new GracefulException(CommandDefinitionStrings.SdkIdentityArgumentIdIsNull); + } + + return identity.Value; + }, + Arity = ArgumentArity.ExactlyOne, + IsDynamic = true + }; + + public static string GetSdkIdFromIdentityArgument(string sdkIdentity) + { + SdkReferenceIdentity? identity = ParseSdkReferenceIdentity(sdkIdentity); + if (identity is null) + { + throw new GracefulException(CommandDefinitionStrings.SdkIdentityArgumentIdIsNull); + } + + return identity.Value.Id; + } + + private static SdkReferenceIdentity? ParseSdkReferenceIdentity(string? sdkIdentity, char versionSeparator = '@') + { + if (string.IsNullOrEmpty(sdkIdentity)) + { + return null; + } + + string[] splitSdkIdentity = sdkIdentity.Split(versionSeparator); + var sdkId = splitSdkIdentity.ElementAtOrDefault(0); + var version = splitSdkIdentity.ElementAtOrDefault(1); + + if (string.IsNullOrEmpty(sdkId)) + { + throw new GracefulException(CommandDefinitionStrings.SdkIdentityArgumentIdIsNull); + } + + if (splitSdkIdentity.Length > 2) + { + version = string.Join(versionSeparator, splitSdkIdentity.Skip(1)); + } + + if (sdkIdentity.Contains(versionSeparator) && string.IsNullOrWhiteSpace(version)) + { + throw new GracefulException(CommandDefinitionStrings.SdkIdentityArgumentVersionIsEmpty); + } + + return new SdkReferenceIdentity(sdkId, string.IsNullOrEmpty(version) ? null : version); + } } public readonly record struct PackageIdentityWithRange(string Id, VersionRange? VersionRange) @@ -65,4 +127,9 @@ public readonly record struct PackageIdentityWithRange(string Id, VersionRange? [MemberNotNullWhen(returnValue: true, nameof(VersionRange))] public bool HasVersion => VersionRange != null; } + + public readonly record struct SdkReferenceIdentity(string Id, string? Version) + { + public bool HasVersion => Version != null; + } } diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.cs.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.cs.xlf index aa41c97bb6a4..a29fdd7a64cd 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.cs.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.cs.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ Pokud zadaný adresář neexistuje, bude vytvořen. Cílový modul runtime pro obnovení balíčků + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Nastaví v balíčku příznak obsluhovatelnosti. Další informace najdete na adrese https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ Ve výchozím nastavení je publikována aplikace závislá na architektuře.Zobrazí schéma příkazu jako JSON. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command Příkaz sady .NET SDK @@ -1835,6 +1850,31 @@ Ve výchozím nastavení je publikována aplikace závislá na architektuře.Příkaz kontroly sady .NET SDK + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. Možnosti --self-contained a --no-self-contained nelze použít společně. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.de.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.de.xlf index e5790eaf2998..c5c87992362e 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.de.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.de.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ Das angegebene Verzeichnis wird erstellt, wenn es nicht vorhanden ist. Die Zielruntime für die Wiederherstellung von Paketen. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Hiermit wird das Verarbeitungsflag im Paket festgelegt. Weitere Informationen finden Sie unter https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ Standardmäßig wird eine Framework-abhängige Anwendung veröffentlicht.Das Befehlsschema als JSON anzeigen. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command .NET SDK-Befehl @@ -1835,6 +1850,31 @@ Standardmäßig wird eine Framework-abhängige Anwendung veröffentlicht.Befehl zur .NET SDK-Überprüfung + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. Die Optionen „--self-contained“ und „--no-self-contained“ können nicht gemeinsam verwendet werden. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.es.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.es.xlf index d8b976e13340..17f2b8b863b6 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.es.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.es.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ Si no existe, se creará el directorio especificado. El tiempo de ejecución de destino para el que se restauran los paquetes. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Establece la marca serviceable en el paquete. Para obtener más información, consulte https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ El valor predeterminado es publicar una aplicación dependiente del marco.Muestre el esquema de comandos como JSON. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command Comando del SDK de .NET @@ -1835,6 +1850,31 @@ El valor predeterminado es publicar una aplicación dependiente del marco.Comando de comprobación del SDK de .NET + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. Las opciones '--self-contained' y '--no-self-contained' no se pueden usar juntas. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.fr.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.fr.xlf index 1ff5df1c828c..a9cdc316c3f4 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.fr.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.fr.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ Le répertoire spécifié est créé, s'il n'existe pas déjà. Runtime cible pour lequel la restauration des packages est effectuée. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Définissez l'indicateur de maintenance dans le package. Pour plus d'informations, consultez https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ La valeur par défaut est de publier une application dépendante du framework.Affichez le schéma de commande au format JSON. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command Commande du kit SDK .NET @@ -1835,6 +1850,31 @@ La valeur par défaut est de publier une application dépendante du framework.Commande de vérification du kit SDK .NET + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. Les options '--self-contained' et '--no-self-contained' ne peuvent pas être utilisées ensemble. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.it.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.it.xlf index c00f2f695585..c9af02bbdf65 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.it.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.it.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ Se non esiste, la directory specificata verrà creata. Runtime di destinazione per cui ripristinare i pacchetti. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Imposta il flag serviceable nel pacchetto. Per altre informazioni, vedere https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ Per impostazione predefinita, viene generato un pacchetto dipendente dal framewo Visualizza lo schema del comando in formato JSON. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command Comando SDK .NET @@ -1835,6 +1850,31 @@ Per impostazione predefinita, viene generato un pacchetto dipendente dal framewo Comando per controllo disponibilità .NET SDK + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. Non è possibile usare contemporaneamente le opzioni '--self-contained' e ‘--no-self-contained'. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ja.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ja.xlf index f0344a032732..8c8b99381c18 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ja.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ja.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ The specified directory will be created if it does not exist. パッケージを復元するターゲット ランタイム。 + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. パッケージに処理可能なフラグを設定します。詳細については、https://aka.ms/nupkgservicing をご覧ください。 @@ -1825,6 +1830,16 @@ The default is to publish a framework-dependent application. コマンド スキーマを JSON として表示します。 + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command .NET SDK コマンド @@ -1835,6 +1850,31 @@ The default is to publish a framework-dependent application. .NET SDK のチェック コマンド + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. '--self-contained' と '--no-self-contained' オプションは同時に使用できません。 diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ko.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ko.xlf index 622ee72ef683..28a83fe27fa0 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ko.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ko.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ The specified directory will be created if it does not exist. 패키지를 복원할 대상 런타임입니다. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. 패키지에서 서비스 가능 플래그를 설정합니다. 자세한 내용은 https://aka.ms/nupkgservicing을 참조하세요. @@ -1825,6 +1830,16 @@ The default is to publish a framework-dependent application. 명령 스키마를 JSON으로 표시합니다. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command .NET SDK 명령 @@ -1835,6 +1850,31 @@ The default is to publish a framework-dependent application. .NET SDK 확인 명령 + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. '--self-contained' 및 '--no-self-contained' 옵션은 함께 사용할 수 없습니다. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pl.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pl.xlf index 593409bb79f1..3fc931e35abe 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pl.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pl.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ Jeśli określony katalog nie istnieje, zostanie utworzony. Docelowe środowisko uruchomieniowe, dla którego mają zostać przywrócone pakiety. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Ustaw w pakiecie flagę oznaczającą możliwość serwisowania. Aby uzyskać więcej informacji, zobacz https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ Domyślnie publikowana jest aplikacja zależna od struktury. Wyświetl schemat polecenia jako kod JSON. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command Polecenie zestawu .NET SDK @@ -1835,6 +1850,31 @@ Domyślnie publikowana jest aplikacja zależna od struktury. Polecenie sprawdzania zestawu .NET SDK + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. Opcji „--self-contained” i „--no-self-contained” nie można używać razem. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pt-BR.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pt-BR.xlf index 9bd05337ee40..e766180f6f3f 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pt-BR.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ O diretório especificado será criado se ele ainda não existir. O runtime de destino no qual os pacotes serão restaurados. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Definir o sinalizador operacional no pacote. Para obter mais informações, confira https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ O padrão é publicar uma aplicação dependente de framework. Exibir o esquema do comando em JSON. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command Comando do SDK do .NET @@ -1835,6 +1850,31 @@ O padrão é publicar uma aplicação dependente de framework. Comando de Verificação do SDK do .NET + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. As opções '--self-contained' e '--no-self-contained' não podem ser usadas juntas. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ru.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ru.xlf index 7c47905c546b..890295c32af7 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ru.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.ru.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ The specified directory will be created if it does not exist. Целевая среда выполнения для восстановления пакетов. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Задание для пакета флага "подлежит обслуживанию". Дополнительные сведения: https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ The default is to publish a framework-dependent application. Отобразить схему команды в формате JSON. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command Команда .NET SDK @@ -1835,6 +1850,31 @@ The default is to publish a framework-dependent application. Команда проверки пакета SDK для .NET + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. Параметры "--self-contained" и "--no-self-contained" нельзя использовать вместе. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.tr.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.tr.xlf index cecfa3078c00..d5ebc315bd9d 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.tr.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.tr.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ Belirtilen dizin yoksa oluşturulur. Paketlerin geri yükleneceği hedef çalışma zamanı. + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. Pakette serviceable bayrağını ayarlar. Daha fazla bilgi için bkz. https://aka.ms/nupkgservicing. @@ -1825,6 +1830,16 @@ Varsayılan durum, çerçeveye bağımlı bir uygulama yayımlamaktır. Komut şemasını JSON formatında görüntüle. + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command .NET SDK Komutu @@ -1835,6 +1850,31 @@ Varsayılan durum, çerçeveye bağımlı bir uygulama yayımlamaktır. .NET SDK Denetim Komutu + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. '--self-contained' ve '--no-self-contained' seçenekleri birlikte kullanılamaz. diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hans.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hans.xlf index e94a6403043c..08738497e6ea 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hans.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ The specified directory will be created if it does not exist. 要还原包的目标运行时。 + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. 在包中设置可用标志。有关详细信息,请参阅 https://aka.ms/nupkgservicing。 @@ -1825,6 +1830,16 @@ The default is to publish a framework-dependent application. 将命令架构显示为 JSON。 + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command .NET SDK 命令 @@ -1835,6 +1850,31 @@ The default is to publish a framework-dependent application. .NET SDK 检查命令 + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. "--self-contained"和 "--no-self-contained" 选项不能一起使用。 diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hant.xlf b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hant.xlf index fe5b45c6b7dc..d9baaf9dfc21 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hant.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/xlf/CommandDefinitionStrings.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -640,6 +640,11 @@ The specified directory will be created if it does not exist. 要對其還原套件的目標執行階段。 + + SDK_NAME + SDK_NAME + + Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information. 設定套件中的服務旗標。如需詳細資訊,請參閱 https://aka.ms/nupkgservicing。 @@ -1825,6 +1830,16 @@ The default is to publish a framework-dependent application. 以 JSON 格式顯示命令結構描述。 + + Add an SDK reference to the project. + Add an SDK reference to the project. + + + + Add the SDK reference without running restore. + Add the SDK reference without running restore. + + .NET SDK Command .NET SDK 命令 @@ -1835,6 +1850,31 @@ The default is to publish a framework-dependent application. .NET SDK 檢查命令 + + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + SDK reference in the form of an SDK name like '{0}' or SDK name and version separated by '@' like '{0}@{1}'. + + + + SDK reference name must not be null or empty. + SDK reference name must not be null or empty. + + + + SDK version must not be empty when specified using the '@' separator. + SDK version must not be empty when specified using the '@' separator. + + + + Remove an SDK reference from the project. + Remove an SDK reference from the project. + + + + The SDK reference to remove. + The SDK reference to remove. + + The '--self-contained' and '--no-self-contained' options cannot be used together. 不能同時使用 '--self-contained' 和 '--no-self-contained' 選項。 diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx index cc38ba223cad..f93720adc030 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -1259,6 +1259,49 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man {0} is a directive kind (like '#:package'). {1} is number of removed directives. {2} is directive key (e.g., package name). {3} is file path from which directives were removed. + + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}' is already present in project '{1}'. + + + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}' is already present in file '{1}'. + + + Removing SDK reference '{0}' from project '{1}'. + + + SDK reference '{0}' not found in project '{1}'. + + + SDK reference '{0}' not found in file '{1}'. + + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + + Could not parse global.json file '{0}': {1} + + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Command names conflict. Command names are case insensitive. {0} diff --git a/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs b/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs index 23f41a0e945e..798a62365d2d 100644 --- a/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs +++ b/src/Cli/dotnet/Commands/Hidden/Add/AddCommandParser.cs @@ -4,6 +4,7 @@ using System.CommandLine; using Microsoft.DotNet.Cli.Commands.Package; using Microsoft.DotNet.Cli.Commands.Reference.Add; +using Microsoft.DotNet.Cli.Commands.Sdk.Add; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Hidden.Add; @@ -16,5 +17,6 @@ public static void ConfigureCommand(AddCommandDefinition command) PackageCommandParser.ConfigureAddCommand(command.PackageCommand); command.ReferenceCommand.SetAction(parseResult => new ReferenceAddCommand(parseResult).Execute()); + command.SdkCommand.SetAction(parseResult => new SdkAddCommand(parseResult).Execute()); } } diff --git a/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs b/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs index c8f558c1b7ff..0abcfd8f5d8e 100644 --- a/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs +++ b/src/Cli/dotnet/Commands/Hidden/Remove/RemoveCommandParser.cs @@ -4,6 +4,7 @@ using System.CommandLine; using Microsoft.DotNet.Cli.Commands.Package.Remove; using Microsoft.DotNet.Cli.Commands.Reference.Remove; +using Microsoft.DotNet.Cli.Commands.Sdk.Remove; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Hidden.Remove; @@ -16,5 +17,6 @@ public static void ConfigureCommand(RemoveCommandDefinition command) command.PackageCommand.SetAction(parseResult => new PackageRemoveCommand(parseResult).Execute()); command.ReferenceCommand.SetAction(parseResult => new ReferenceRemoveCommand(parseResult).Execute()); + command.SdkCommand.SetAction(parseResult => new SdkRemoveCommand(parseResult).Execute()); } } diff --git a/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs b/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs index cb7bf28db9ef..9469a20852e2 100644 --- a/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs +++ b/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs @@ -94,6 +94,15 @@ private TextChange DetermineAddChange(CSharpDirective directive) return new TextChange(toReplace.Info.Span, newText: directive.ToString() + NewLine); } + // SDK directive order is semantic: the first directive is the primary SDK. + // Append new SDKs after the last existing SDK rather than sorting by name. + if (directive is CSharpDirective.Sdk && + Directives.OfType().LastOrDefault() is { } lastSdk) + { + var span = new TextSpan(start: lastSdk.Info.Span.End, length: 0); + return new TextChange(span, newText: directive.ToString() + NewLine); + } + // Find the last directive of the first group of directives of the same kind. // If found, we will insert the new directive after it. CSharpDirective? addAfter = null; diff --git a/src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs b/src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs new file mode 100644 index 000000000000..4da6ddd0d071 --- /dev/null +++ b/src/Cli/dotnet/Commands/Sdk/Add/SdkAddCommand.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using System.Diagnostics; +using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Commands.Package; +using Microsoft.DotNet.Cli.Commands.Restore; +using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; + +namespace Microsoft.DotNet.Cli.Commands.Sdk.Add; + +internal sealed class SdkAddCommand : CommandBase +{ + private readonly SdkReferenceIdentity _sdkId; + + public SdkAddCommand(ParseResult parseResult) + : base(parseResult) + { + _sdkId = parseResult.GetValue(Definition.SdkIdArgument); + } + + public override int Execute() + { + var (fileOrDirectory, allowedAppKinds) = PackageCommandParser.ProcessPathOptions( + Definition.FileOption, + Definition.ProjectOption, + Definition.GetProjectOrFileArgument(), + _parseResult); + + if (allowedAppKinds.HasFlag(AppKinds.FileBased) && VirtualProjectBuilder.IsValidEntryPointPath(fileOrDirectory)) + { + return ExecuteForFileBasedApp(fileOrDirectory); + } + + Debug.Assert(allowedAppKinds.HasFlag(AppKinds.ProjectBased)); + + string projectFilePath = File.Exists(fileOrDirectory) + ? fileOrDirectory + : MsbuildProject.GetProjectFileFromDirectory(fileOrDirectory); + + return ExecuteForProject(projectFilePath); + } + + private int ExecuteForProject(string projectFilePath) + { + bool userVersionSpecified = IsVersionSpecified(); + string? userVersion = GetSpecifiedVersion(); + var startDirectory = Path.GetDirectoryName(projectFilePath) ?? Environment.CurrentDirectory; + + bool interactive = _parseResult.GetValue(Definition.InteractiveOption); + + using var projects = new ProjectCollection(); + var msbuildProject = MsbuildProject.FromFile(projects, projectFilePath, interactive); + + if (!userVersionSpecified && ProjectSdkReferenceHelper.ContainsSdk(msbuildProject.ProjectRootElement, _sdkId.Id)) + { + WriteAddResultMessage(SdkAddResult.Unchanged, version: null, projectFilePath, forFile: false); + return 0; + } + + var (version, leaveExistingUnchanged) = SdkAddVersionHelper.ResolveVersion( + _sdkId.Id, + userVersion, + userVersionSpecified, + startDirectory, + interactive); + + SdkAddResult result = ProjectSdkReferenceHelper.AddOrUpdateSdk( + msbuildProject.ProjectRootElement, + _sdkId.Id, + version, + leaveExistingUnchanged); + + if (result == SdkAddResult.Unchanged) + { + WriteAddResultMessage(result, version, projectFilePath, forFile: false); + return 0; + } + + byte[] snapshot = File.ReadAllBytes(projectFilePath); + msbuildProject.ProjectRootElement.Save(); + + if (_parseResult.GetValue(Definition.NoRestoreOption)) + { + WriteAddResultMessage(result, version, projectFilePath, forFile: false); + return 0; + } + + string[] restoreArgs = interactive + ? [projectFilePath, "--nologo", "-v:q", "--interactive"] + : [projectFilePath, "--nologo", "-v:q"]; + int exitCode = RestoreCommand.FromArgs(restoreArgs).Execute(); + if (exitCode != 0) + { + File.WriteAllBytes(projectFilePath, snapshot); + return exitCode; + } + + WriteAddResultMessage(result, version, projectFilePath, forFile: false); + return 0; + } + + private int ExecuteForFileBasedApp(string path) + { + bool userVersionSpecified = IsVersionSpecified(); + string? userVersion = GetSpecifiedVersion(); + var fullPath = Path.GetFullPath(path); + var startDirectory = Path.GetDirectoryName(fullPath) ?? Environment.CurrentDirectory; + bool interactive = _parseResult.GetValue(Definition.InteractiveOption); + + var file = SourceFile.Load(fullPath); + var editor = FileBasedAppSourceEditor.Load(file); + + var existing = editor.Directives + .OfType() + .FirstOrDefault(s => string.Equals(s.Name, _sdkId.Id, StringComparison.OrdinalIgnoreCase)); + + if (!userVersionSpecified && existing != null) + { + WriteAddResultMessage(SdkAddResult.Unchanged, version: null, fullPath, forFile: true); + return 0; + } + + var (version, _) = SdkAddVersionHelper.ResolveVersion( + _sdkId.Id, + userVersion, + userVersionSpecified, + startDirectory, + interactive); + + SdkAddResult result; + string? effectiveVersion = userVersionSpecified ? userVersion : version ?? existing?.Version; + + var command = new VirtualProjectBuildingCommand( + entryPointFileFullPath: fullPath, + msbuildArgs: MSBuildArgs.FromProperties(new Dictionary(1) + { + ["NuGetInteractive"] = interactive.ToString(), + }.AsReadOnly())) + { + NoCache = true, + NoBuild = true, + }; + + editor.Add(new CSharpDirective.Sdk(default) { Name = _sdkId.Id, Version = effectiveVersion }); + command.Directives = editor.Directives; + result = existing != null ? SdkAddResult.Updated : SdkAddResult.Added; + + if (_parseResult.GetValue(Definition.NoRestoreOption)) + { + editor.SourceFile.Save(); + WriteAddResultMessage(result, effectiveVersion, fullPath, forFile: true); + return 0; + } + + int exitCode = command.Execute(); + if (exitCode != 0) + { + return exitCode; + } + + editor.SourceFile.Save(); + WriteAddResultMessage(result, effectiveVersion, fullPath, forFile: true); + return 0; + } + + private void WriteAddResultMessage(SdkAddResult result, string? version, string path, bool forFile) + { + switch (result) + { + case SdkAddResult.Added: + Reporter.Output.WriteLine( + forFile ? CliCommandStrings.SdkReferenceAddedToFile : CliCommandStrings.SdkReferenceAddedToProject, + _sdkId.Id, + FormatVersionSuffix(version), + path); + break; + case SdkAddResult.Updated: + Reporter.Output.WriteLine( + forFile ? CliCommandStrings.SdkReferenceUpdatedInFile : CliCommandStrings.SdkReferenceUpdatedInProject, + _sdkId.Id, + FormatVersionSuffix(version), + path); + break; + case SdkAddResult.Unchanged: + Reporter.Output.WriteLine( + forFile ? CliCommandStrings.SdkReferenceUnchangedInFile : CliCommandStrings.SdkReferenceUnchangedInProject, + _sdkId.Id, + path); + break; + } + } + + private string? GetSpecifiedVersion() + => _sdkId.HasVersion ? _sdkId.Version : _parseResult.GetValue(Definition.VersionOption); + + private bool IsVersionSpecified() + => _sdkId.HasVersion || _parseResult.HasOption(Definition.VersionOption); + + private static string FormatVersionSuffix(string? version) + => string.IsNullOrEmpty(version) ? string.Empty : $" version '{version}'"; +} diff --git a/src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs b/src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs new file mode 100644 index 000000000000..86583f79e682 --- /dev/null +++ b/src/Cli/dotnet/Commands/Sdk/Add/SdkAddVersionHelper.cs @@ -0,0 +1,239 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +extern alias DotNetNativeWrapper; + +using System.Security; +using System.Text.Json; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.Cli.ToolPackage; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.EnvironmentAbstractions; +using Microsoft.NET.Sdk.WorkloadManifestReader; +using NuGet.Versioning; +using EnvironmentProvider = DotNetNativeWrapper::Microsoft.DotNet.NativeWrapper.EnvironmentProvider; +using HostFxrNotFoundException = DotNetNativeWrapper::Microsoft.DotNet.NativeWrapper.HostFxrNotFoundException; +using HostFxrRuntimePropertyNotSetException = DotNetNativeWrapper::Microsoft.DotNet.NativeWrapper.HostFxrRuntimePropertyNotSetException; +using NETCoreSdkResolverNativeWrapper = DotNetNativeWrapper::Microsoft.DotNet.NativeWrapper.NETCoreSdkResolverNativeWrapper; + +namespace Microsoft.DotNet.Cli.Commands.Sdk.Add; + +/// +/// Resolves the SDK version to write when the user does not specify one explicitly. +/// +internal static class SdkAddVersionHelper +{ + private const string PackageCacheFolderName = "dotnet-sdk-add"; + /// + /// Resolves the version to store in the project or file-based app, and whether an existing reference may be left unchanged. + /// + public static (string? Version, bool LeaveExistingUnchangedWhenVersionIsNull) ResolveVersion( + string sdkName, + string? userVersion, + bool userVersionSpecified, + string startDirectory, + bool interactive) + { + if (userVersionSpecified) + { + return (userVersion, LeaveExistingUnchangedWhenVersionIsNull: false); + } + + if (TryGetMsbuildSdkVersionFromGlobalJson(startDirectory, sdkName, out _)) + { + return (null, LeaveExistingUnchangedWhenVersionIsNull: true); + } + + if (IsBundledInCurrentDotNetSdk(sdkName, startDirectory)) + { + return (null, LeaveExistingUnchangedWhenVersionIsNull: true); + } + + var nugetVersion = GetLatestNuGetSdkVersion(sdkName, startDirectory, interactive); + return (nugetVersion, LeaveExistingUnchangedWhenVersionIsNull: false); + } + + internal static bool TryGetMsbuildSdkVersionFromGlobalJson(string? startDirectory, string sdkName, out string? version) + { + version = null; + string? globalJsonPath = SdkDirectoryWorkloadManifestProvider.GetGlobalJsonPath(startDirectory); + if (globalJsonPath is null) + { + return false; + } + + JsonDocument document; + try + { + var jsonDocumentOptions = new JsonDocumentOptions + { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip + }; + + using var stream = File.OpenRead(globalJsonPath); + document = JsonDocument.Parse(stream, jsonDocumentOptions); + } + catch (Exception ex) when (ex is JsonException or IOException or UnauthorizedAccessException or SecurityException) + { + throw new GracefulException( + string.Format(CliCommandStrings.GlobalJsonFileParsingFailed, globalJsonPath, ex.Message)); + } + + using (document) + { + if (!document.RootElement.TryGetProperty("msbuild-sdks", out JsonElement msbuildSdks) || + msbuildSdks.ValueKind != JsonValueKind.Object) + { + return false; + } + + foreach (JsonProperty property in msbuildSdks.EnumerateObject()) + { + if (string.Equals(property.Name, sdkName, StringComparison.OrdinalIgnoreCase) && + property.Value.ValueKind == JsonValueKind.String) + { + version = property.Value.GetString()?.Trim(); + return !string.IsNullOrWhiteSpace(version); + } + } + } + + return false; + } + + private static bool IsBundledInCurrentDotNetSdk(string sdkName, string startDirectory) + { + string? dotnetDir = EnvironmentProvider.GetDotnetExeDirectory( + key => Environment.GetEnvironmentVariable(key), + getCurrentProcessPath: () => Environment.ProcessPath ?? string.Empty); + + if (string.IsNullOrEmpty(dotnetDir)) + { + return false; + } + + foreach (string sdkDirectory in GetSdkDirectoriesToSearch(dotnetDir, startDirectory)) + { + string sdkImportPath = Path.Combine(sdkDirectory, "Sdks", sdkName, "Sdk"); + if (Directory.Exists(sdkImportPath)) + { + return true; + } + } + + return false; + } + + private static IEnumerable GetSdkDirectoriesToSearch(string dotnetDir, string startDirectory) + { + string? resolvedSdkDirectory = TryResolveSdkDirectory(dotnetDir, startDirectory); + if (resolvedSdkDirectory is not null) + { + yield return resolvedSdkDirectory; + yield break; + } + + foreach (string sdkDirectory in GetInstalledSdkDirectories(dotnetDir)) + { + yield return sdkDirectory; + } + } + + private static string? TryResolveSdkDirectory(string dotnetDir, string startDirectory) + { + try + { + return NETCoreSdkResolverNativeWrapper.ResolveSdk(dotnetDir, startDirectory).ResolvedSdkDirectory; + } + catch (Exception ex) when (ex is HostFxrRuntimePropertyNotSetException or HostFxrNotFoundException) + { + return null; + } + } + + private static IEnumerable GetInstalledSdkDirectories(string dotnetDir) + { + string sdkRoot = Path.Combine(dotnetDir, "sdk"); + if (!Directory.Exists(sdkRoot)) + { + yield break; + } + + IEnumerable sdkDirectories; + try + { + sdkDirectories = NETCoreSdkResolverNativeWrapper.GetAvailableSdks(dotnetDir); + } + catch (Exception ex) when (ex is HostFxrRuntimePropertyNotSetException or HostFxrNotFoundException) + { + sdkDirectories = Directory.GetDirectories(sdkRoot); + } + + var yielded = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (!string.IsNullOrEmpty(Product.Version)) + { + string productVersionPath = Path.Combine(sdkRoot, Product.Version); + if (Directory.Exists(productVersionPath) && yielded.Add(productVersionPath)) + { + yield return productVersionPath; + } + } + + foreach (string sdkDirectory in sdkDirectories) + { + if (yielded.Add(sdkDirectory)) + { + yield return sdkDirectory; + } + } + } + + private static string GetLatestNuGetSdkVersion(string sdkName, string startDirectory, bool interactive) + { + string packageInstallDir = Path.Combine(Path.GetTempPath(), PackageCacheFolderName, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(packageInstallDir); + + try + { + var downloader = new NuGetPackageDownloader.NuGetPackageDownloader( + packageInstallDir: new DirectoryPath(packageInstallDir), + currentWorkingDirectory: startDirectory, + restoreActionConfig: new RestoreActionConfig(Interactive: interactive)); + + NuGetVersion version = downloader + .GetLatestPackageVersion(new PackageId(sdkName), includePreview: false) + .GetAwaiter() + .GetResult(); + + return version.ToNormalizedString(); + } + catch (Exception ex) when (ex is not OperationCanceledException and not GracefulException) + { + throw new GracefulException( + string.Format(CliCommandStrings.SdkVersionResolutionFailed, sdkName, ex.Message)); + } + finally + { + TryDeleteDirectory(packageInstallDir); + } + } + + private static void TryDeleteDirectory(string path) + { + try + { + if (Directory.Exists(path)) + { + Directory.Delete(path, recursive: true); + } + } + catch (IOException) + { + } + catch (UnauthorizedAccessException) + { + } + } +} diff --git a/src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs b/src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs new file mode 100644 index 000000000000..2dcd8084cc6d --- /dev/null +++ b/src/Cli/dotnet/Commands/Sdk/Remove/SdkRemoveCommand.cs @@ -0,0 +1,172 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using System.Diagnostics; +using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Commands.Package; +using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; + +namespace Microsoft.DotNet.Cli.Commands.Sdk.Remove; + +internal sealed class SdkRemoveCommand(ParseResult parseResult) : CommandBase(parseResult) +{ + private readonly SdkRemoveCommandDefinitionBase _definition = (SdkRemoveCommandDefinitionBase)parseResult.CommandResult.Command; + + public override int Execute() + { + var sdkNames = DeduplicateSdkNames( + (_parseResult.GetValue(_definition.SdkIdArgument) ?? []) + .Select(CommonArguments.GetSdkIdFromIdentityArgument)); + + var (fileOrDirectory, allowedAppKinds) = PackageCommandParser.ProcessPathOptions( + _definition.FileOption, + _definition.ProjectOption, + _definition.GetProjectOrFileArgument(), + _parseResult); + + if (allowedAppKinds.HasFlag(AppKinds.FileBased) && VirtualProjectBuilder.IsValidEntryPointPath(fileOrDirectory)) + { + return ExecuteForFileBasedApp(fileOrDirectory, sdkNames); + } + + Debug.Assert(allowedAppKinds.HasFlag(AppKinds.ProjectBased)); + + string projectFilePath = File.Exists(fileOrDirectory) + ? fileOrDirectory + : MsbuildProject.GetProjectFileFromDirectory(fileOrDirectory); + + bool interactive = _parseResult.GetValue(_definition.InteractiveOption); + return ExecuteForProject(projectFilePath, sdkNames, interactive); + } + + private static int ExecuteForProject(string projectFilePath, IEnumerable sdkNames, bool interactive) + { + using var projects = new ProjectCollection(); + var msbuildProject = MsbuildProject.FromFile(projects, projectFilePath, interactive); + var project = msbuildProject.ProjectRootElement; + var sdkNameList = sdkNames as IList ?? [.. sdkNames]; + + foreach (var sdkName in sdkNameList) + { + if (ProjectSdkReferenceHelper.IsPrimarySdkReference(project, sdkName)) + { + throw new GracefulException(CliCommandStrings.CannotRemovePrimarySdkReference, sdkName); + } + } + + var removed = new List(); + var notFound = new List(); + + foreach (var sdkName in sdkNameList) + { + if (ProjectSdkReferenceHelper.TryRemoveSdk(project, sdkName)) + { + removed.Add(sdkName); + } + else + { + notFound.Add(sdkName); + } + } + + if (removed.Count > 0) + { + msbuildProject.ProjectRootElement.Save(); + } + + foreach (var sdkName in removed) + { + Reporter.Output.WriteLine(CliCommandStrings.SdkReferenceRemovedFromProject, sdkName, projectFilePath); + } + + foreach (var sdkName in notFound) + { + Reporter.Output.WriteLine(CliCommandStrings.SdkReferenceNotFoundInProject, sdkName, projectFilePath); + } + + return removed.Count > 0 ? 0 : 1; + } + + private static int ExecuteForFileBasedApp(string path, IEnumerable sdkNames) + { + var fullPath = Path.GetFullPath(path); + var editor = FileBasedAppSourceEditor.Load(SourceFile.Load(fullPath)); + + var primarySdk = editor.Directives.OfType().FirstOrDefault(); + var sdkNameList = sdkNames as IList ?? [.. sdkNames]; + + foreach (var sdkName in sdkNameList) + { + if (primarySdk != null && + string.Equals(primarySdk.Name, sdkName, StringComparison.OrdinalIgnoreCase)) + { + throw new GracefulException(CliCommandStrings.CannotRemovePrimarySdkReferenceInFile, sdkName); + } + } + + var removedCounts = new List<(string SdkName, int Count)>(); + var notFound = new List(); + + foreach (var sdkName in sdkNameList) + { + int count = 0; + var directives = editor.Directives; + for (int i = directives.Length - 1; i >= 0; i--) + { + var directive = directives[i]; + if (directive is CSharpDirective.Sdk sdk && + string.Equals(sdk.Name, sdkName, StringComparison.OrdinalIgnoreCase)) + { + editor.Remove(directive); + count++; + } + } + + if (count > 0) + { + removedCounts.Add((sdkName, count)); + } + else + { + notFound.Add(sdkName); + } + } + + if (removedCounts.Count > 0) + { + editor.SourceFile.Save(); + } + + foreach (var (sdkName, count) in removedCounts) + { + Reporter.Output.WriteLine(CliCommandStrings.DirectivesRemoved, "#:sdk", count, sdkName, fullPath); + } + + foreach (var sdkName in notFound) + { + Reporter.Output.WriteLine(CliCommandStrings.SdkReferenceNotFoundInFile, sdkName, fullPath); + } + + return removedCounts.Count > 0 ? 0 : 1; + } + + private static List DeduplicateSdkNames(IEnumerable sdkNames) + { + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + var result = new List(); + foreach (string sdkName in sdkNames) + { + if (seen.Add(sdkName)) + { + result.Add(sdkName); + } + } + + return result; + } +} diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf index 92b92ad5de1e..f0bedc5d2eab 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -107,6 +107,16 @@ .NET Builder + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Cílem projektu je více architektur. Pomocí parametru {0} určete, která arch Máte nainstalované další verze sady SDK, které se dají použít ke spuštění této šablony. Pokud chcete spustit tuto šablonu, přepněte na některou z následujících sad SDK ve vašem systému: {0}. Podrobnosti o výběru verze sady SDK, která se má spustit: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: Sady .NET SDK: @@ -3092,6 +3152,11 @@ Cílem projektu je více architektur. Pomocí parametru {0} určete, která arch Možnost --sdk-version se už pro tento příkaz nepodporuje. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. Nejsou nainstalované žádné sady úloh. Spuštěním příkazu dotnet workload restore nainstalujte sadu úloh. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf index 8137246818da..356b204397af 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -107,6 +107,16 @@ .NET-Generator + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Ihr Projekt verwendet mehrere Zielframeworks. Geben Sie über "{0}" an, welches Sie haben andere SDK-Versionen installiert, die zum Ausführen dieser Vorlage verwendet werden können. Wechseln Sie zu einem der folgenden SDK(s) auf Ihrem System, um diese Vorlage auszuführen: „{0}“. Details zum Auswählen der auszuführenden SDK-Version: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: .NET SDKs: @@ -3092,6 +3152,11 @@ Ihr Projekt verwendet mehrere Zielframeworks. Geben Sie über "{0}" an, welches Die Option "--sdk-version" wird für diesen Befehl nicht mehr unterstützt. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. Es sind keine Workloadsätze installiert. Führen Sie „dotnet workload restore“ aus, um einen Workloadsatz zu installieren. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf index 4b636d98df00..9dcd10d60414 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -107,6 +107,16 @@ Generador para .NET + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Su proyecto tiene como destino varias plataformas. Especifique la que quiere usa Tiene instaladas otras versiones del SDK que se pueden usar para ejecutar esta plantilla. Cambie a cualquiera de los siguientes SDK en el sistema para ejecutar esta plantilla: "{0}". Detalles sobre la selección de la versión del SDK que se va a ejecutar: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: SDK de .NET: @@ -3092,6 +3152,11 @@ Su proyecto tiene como destino varias plataformas. Especifique la que quiere usa La opción --sdk-version ya no es compatible con este comando. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. No hay conjuntos de cargas de trabajo instalados. Ejecute "dotnet workload restore" para instalar un conjunto de carga de trabajo. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf index f5bf83ca4bc0..4a99e2b99751 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -107,6 +107,16 @@ Générateur .NET + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Votre projet cible plusieurs frameworks. Spécifiez le framework à exécuter à Vous avez d'autres versions du SDK installées qui peuvent être utilisées pour exécuter ce modèle. Basculez vers l'un des SDK suivants sur votre système pour exécuter ce modèle : '{0}'. Détails sur la sélection de la version du SDK à exécuter : https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: Kits SDK .NET : @@ -3092,6 +3152,11 @@ Votre projet cible plusieurs frameworks. Spécifiez le framework à exécuter à L’option --sdk-version n’est plus prise en charge pour cette commande. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. Aucun ensemble de charges de travail n’est installé. Exécutez « dotnet workload restore » pour installer un ensemble de charge de travail. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf index 6078e67fbb90..deaa6c21048c 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -107,6 +107,16 @@ Generatore .NET + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Il progetto è destinato a più framework. Specificare il framework da eseguire Sono installate altre versioni dell'SDK che possono essere usate per eseguire questo modello. Per eseguire questo modello, passare a uno degli SDK seguenti nel sistema: '{0}'. Dettagli sulla selezione della versione SDK da eseguire: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: .NET SDK: @@ -3092,6 +3152,11 @@ Il progetto è destinato a più framework. Specificare il framework da eseguire L'opzione --sdk-version non è più supportata per questo comando. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. Non sono installati set di carico di lavoro. Esegui il "dotnet workload restore" per installare un set del carico di lavoro. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf index 64bd198a6a58..5ab1e1204cb0 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -107,6 +107,16 @@ .NET ビルダー + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Your project targets multiple frameworks. Specify which framework to run using ' このテンプレートの実行に使用できる他の SDK バージョンがインストールされています。システム上の SDK '{0}' のいずれかに切り替えてこのテンプレートを実行してください。実行する SDK バージョンの選択の詳細については、https://docs.microsoft.com/dotnet/core/tools/global-json をご覧ください。 {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: .NET SDK: @@ -3092,6 +3152,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' このコマンドでは、--sdk-version オプションはサポートされなくなりました。 {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. ワークロード セットがインストールされていません。"dotnet workload restore" を実行してワークロード セットをインストールします。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf index ed96bade643b..9f365e91a3dd 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -107,6 +107,16 @@ .NET 작성기 + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Your project targets multiple frameworks. Specify which framework to run using ' 이 템플릿을 실행하는 데 사용할 수 있는 다른 SDK 버전이 설치되어 있습니다. 이 템플릿을 실행하려면 시스템에서 다음 SDK 중 하나로 전환하세요. '{0}'. 실행할 SDK 버전 선택에 대한 세부 정보: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: .NET SDK: @@ -3092,6 +3152,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' --sdk-version 옵션은 더 이상 이 명령의 지원을 받지 않습니다. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. 워크로드 집합이 설치되어 있지 않습니다. "dotnet workload restore"를 실행하여 워크로드 집합을 설치하세요. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf index 29aa9cd4db26..219491ca8657 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -107,6 +107,16 @@ Konstruktor platformy .NET + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Projekt ma wiele platform docelowych. Określ platformę do uruchomienia przy u Masz zainstalowane inne wersje zestawów SDK, których można użyć do uruchomienia tego szablonu. Przełącz się do dowolnego z następujących zestawów SDK w systemie, aby uruchomić ten szablon: „{0}”. Szczegóły dotyczące wybierania wersji zestawu SDK do uruchomienia: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: Zestawy .NET SDK: @@ -3092,6 +3152,11 @@ Projekt ma wiele platform docelowych. Określ platformę do uruchomienia przy u Opcja --sdk-version nie jest już obsługiwana dla tego polecenia. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. Nie zainstalowano żadnych zestawów obciążeń. Uruchom „dotnet workload restore”, aby zainstalować zestaw obciążeń. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf index d4c5288c98f6..7c63dc48620a 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -107,6 +107,16 @@ Construtor do .NET + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Ele tem diversas estruturas como destino. Especifique que estrutura executar usa Você tem outras versões do SDK instaladas que podem ser usadas para executar este modelo. Alterne para qualquer um dos seguintes SDKs em seu sistema para executar este modelo: '{0}'. Detalhes sobre como selecionar a versão do SDK a ser executada: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: SDKs do .NET: @@ -3092,6 +3152,11 @@ Ele tem diversas estruturas como destino. Especifique que estrutura executar usa A opção --sdk-version não tem mais suporte para este comando. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. Nenhum conjunto de cargas de trabalho está instalado. Execute "dotnet workload restore" para instalar um conjunto de cargas de trabalho. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf index e4478f2c95de..6836a70e3f88 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -107,6 +107,16 @@ Построитель .NET + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Your project targets multiple frameworks. Specify which framework to run using ' У вас установлены другие версии пакета SDK, которые можно использовать для запуска этого шаблона. Переключитесь на любой из следующих пакетов SDK в системе, чтобы запустить этот шаблон: "{0}". Сведения о выборе версии пакета SDK для запуска: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: Пакеты SDK для .NET: @@ -3092,6 +3152,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' Параметр --sdk-version больше не поддерживается для этой команды. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. Наборы рабочих нагрузок не установлены. Запустите команду "dotnet workload restore", чтобы установить набор рабочей нагрузки. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf index d00d0206fba1..8c24ba24de21 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -107,6 +107,16 @@ .NET Oluşturucusu + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Projeniz birden fazla Framework'ü hedefliyor. '{0}' kullanarak hangi Framework' Bu şablonu çalıştırmak için kullanılabilen başka SDK sürümü/sürümleri yüklü. Bu şablonu çalıştırmak için sisteminizde aşağıdaki SDK’(lar)dan herhangi birine geçiş yapın: '{0}'. Çalıştırılacak SDK sürümünü seçmeyle ilgili ayrıntılar için: https://docs.microsoft.com/dotnet/core/tools/global-json. {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: .NET SDK'ları: @@ -3092,6 +3152,11 @@ Projeniz birden fazla Framework'ü hedefliyor. '{0}' kullanarak hangi Framework' --sdk-version seçeneği artık bu komut için desteklenmiyor. {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. İş yükü kümesi yüklenmemiştir. Bir iş yükü seti yüklemek için “dotnet workload restore” komutunu çalıştırın. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf index 6252e32a26b0..2eef90fbe061 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -107,6 +107,16 @@ .NET 生成器 + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Your project targets multiple frameworks. Specify which framework to run using ' 已安装可用于运行此模板的其他 SDK 版本。切换到系统上以下任一 SDK 以运行此模板:“{0}”。有关选择要运行的 SDK 版本的详细信息,请参阅: https://docs.microsoft.com/dotnet/core/tools/global-json。 {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: .NET SDK: @@ -3092,6 +3152,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' 此命令不再支持 --sdk-version 选项。 {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. 未安装任何 workload sets。运行 “dotnet workload restore” 以安装工作负载集。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf index 5f73e1463c29..e175075dfb7c 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -107,6 +107,16 @@ .NET 產生器 + + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + Cannot remove the primary SDK '{0}'. This SDK must remain as the project's main SDK reference. + + + + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + Cannot remove the primary SDK '{0}' specified by the first '#:sdk' directive. + + Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. Affected-test selection is experimental. Set the '{0}' environment variable to '1' to enable it. @@ -282,6 +292,11 @@ failed -> passed Describes the outcome change of a flaky test, shown next to its name in the 'Flaky tests:' section. The arrow reads left (first outcome) to right (final outcome). + + Could not parse global.json file '{0}': {1} + Could not parse global.json file '{0}': {1} + + Handshake failures: Handshake failures: @@ -3082,6 +3097,51 @@ Your project targets multiple frameworks. Specify which framework to run using ' 您已安裝其他可用來執行此範本的 SDK 版本。請在系統上切換至下列任一 SDK 以執行此範本: '{0}'。有關選取要執行之 SDK 版本的詳細資料: https://docs.microsoft.com/dotnet/core/tools/global-json。 {0} is the list of installed and supported versions. + + SDK reference '{0}'{1} added to file '{2}'. + SDK reference '{0}'{1} added to file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + + + SDK reference '{0}'{1} added to project '{2}'. + SDK reference '{0}'{1} added to project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}' not found in project '{1}'. + SDK reference '{0}' not found in project '{1}'. + + + + SDK reference '{0}' not found in file '{1}'. + SDK reference '{0}' not found in file '{1}'. + + + + Removing SDK reference '{0}' from project '{1}'. + Removing SDK reference '{0}' from project '{1}'. + + + + SDK reference '{0}' is already present in file '{1}'. + SDK reference '{0}' is already present in file '{1}'. + + + + SDK reference '{0}' is already present in project '{1}'. + SDK reference '{0}' is already present in project '{1}'. + + + + SDK reference '{0}'{1} updated in project '{2}'. + SDK reference '{0}'{1} updated in project '{2}'. + {0} is SDK name. {1} is optional version suffix like " version '6.2.0'". {2} is project path. + + + SDK reference '{0}'{1} updated in file '{2}'. + SDK reference '{0}'{1} updated in file '{2}'. + {0} is SDK name. {1} is optional version suffix. {2} is C# file path. + .NET SDKs: .NET SDK: @@ -3092,6 +3152,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' 此命令已不再支援 --sdk-version 選項。 {Locked="--sdk-version"} + + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + Could not resolve a NuGet version for SDK '{0}'. Specify a version explicitly. {1} + + No workload sets are installed. Run "dotnet workload restore" to install a workload set. 未安裝任何工作負載集合。執行 "dotnet workload restore" 以安裝工作負載集合。 diff --git a/src/Cli/dotnet/ProjectSdkReferenceHelper.cs b/src/Cli/dotnet/ProjectSdkReferenceHelper.cs new file mode 100644 index 000000000000..7402092a5915 --- /dev/null +++ b/src/Cli/dotnet/ProjectSdkReferenceHelper.cs @@ -0,0 +1,237 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.Construction; +using Microsoft.DotNet.Cli.Commands; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Cli; + +internal enum SdkAddResult +{ + Added, + Updated, + Unchanged, +} + +/// +/// Reads and updates SDK references in SDK-style MSBuild project files. +/// +internal static class ProjectSdkReferenceHelper +{ + /// + /// Returns whether the project file already references the given SDK. + /// + public static bool ContainsSdk(ProjectRootElement project, string sdkName) + { + ArgumentException.ThrowIfNullOrEmpty(sdkName); + + var attributeSdks = ParseSdkAttribute(project.Sdk); + if (attributeSdks.Exists(s => string.Equals(s.Name, sdkName, StringComparison.OrdinalIgnoreCase))) + { + return true; + } + + return GetSdkElements(project).Any(s => string.Equals(s.Name, sdkName, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Adds or updates an SDK reference in the project file. + /// When is and + /// is , an existing reference is left unchanged. + /// + public static SdkAddResult AddOrUpdateSdk( + ProjectRootElement project, + string sdkName, + string? version, + bool leaveExistingUnchangedWhenVersionIsNull) + { + ArgumentException.ThrowIfNullOrEmpty(sdkName); + + var attributeSdks = ParseSdkAttribute(project.Sdk); + for (int i = 0; i < attributeSdks.Count; i++) + { + if (string.Equals(attributeSdks[i].Name, sdkName, StringComparison.OrdinalIgnoreCase)) + { + if (leaveExistingUnchangedWhenVersionIsNull && version is null) + { + return SdkAddResult.Unchanged; + } + + attributeSdks[i] = new SdkReference(sdkName, version); + project.Sdk = FormatSdkAttribute(attributeSdks); + return SdkAddResult.Updated; + } + } + + foreach (var sdkElement in GetSdkElements(project)) + { + if (string.Equals(sdkElement.Name, sdkName, StringComparison.OrdinalIgnoreCase)) + { + if (leaveExistingUnchangedWhenVersionIsNull && version is null) + { + return SdkAddResult.Unchanged; + } + + SetSdkElementVersion(sdkElement, version); + return SdkAddResult.Updated; + } + } + + var newSdkElement = project.CreateProjectSdkElement(sdkName, version ?? string.Empty); + SetSdkElementVersion(newSdkElement, version); + InsertAdditiveSdkElement(project, newSdkElement); + + return SdkAddResult.Added; + } + + /// + /// Removes an SDK reference from the project file. + /// + /// if the SDK reference was removed. + public static bool TryRemoveSdk(ProjectRootElement project, string sdkName) + { + ArgumentException.ThrowIfNullOrEmpty(sdkName); + + var attributeSdks = ParseSdkAttribute(project.Sdk); + for (int i = 0; i < attributeSdks.Count; i++) + { + if (!string.Equals(attributeSdks[i].Name, sdkName, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (IsPrimarySdkReference(project, sdkName)) + { + throw new GracefulException(CliCommandStrings.CannotRemovePrimarySdkReference, sdkName); + } + + attributeSdks.RemoveAt(i); + project.Sdk = FormatSdkAttribute(attributeSdks); + return true; + } + + foreach (var sdkElement in GetSdkElements(project).ToList()) + { + if (!string.Equals(sdkElement.Name, sdkName, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (IsPrimarySdkReference(project, sdkName)) + { + throw new GracefulException(CliCommandStrings.CannotRemovePrimarySdkReference, sdkName); + } + + project.RemoveChild(sdkElement); + return true; + } + + return false; + } + + /// + /// Returns whether the given SDK name is the primary SDK for the project. + /// + public static bool IsPrimarySdkReference(ProjectRootElement project, string sdkName) + { + ArgumentException.ThrowIfNullOrEmpty(sdkName); + + return TryGetPrimarySdk(project, out string primaryName, out _) && + string.Equals(primaryName, sdkName, StringComparison.OrdinalIgnoreCase); + } + + private static bool TryGetPrimarySdk(ProjectRootElement project, out string name, out string? version) + { + name = string.Empty; + version = null; + + var attributeSdks = ParseSdkAttribute(project.Sdk); + if (attributeSdks.Count > 0) + { + name = attributeSdks[0].Name; + version = attributeSdks[0].Version; + return true; + } + + var firstSdkElement = GetSdkElements(project).FirstOrDefault(); + if (firstSdkElement is null) + { + return false; + } + + name = firstSdkElement.Name; + version = string.IsNullOrEmpty(firstSdkElement.Version) ? null : firstSdkElement.Version; + return true; + } + + /// + /// Inserts an additive <Sdk> after existing SDK elements to preserve import order. + /// + private static void InsertAdditiveSdkElement(ProjectRootElement project, ProjectSdkElement newSdkElement) + { + var sdkElements = GetSdkElements(project).ToList(); + if (sdkElements.Count > 0) + { + project.InsertAfterChild(newSdkElement, sdkElements[^1]); + return; + } + + var insertBefore = project.Children.FirstOrDefault(); + if (insertBefore is null) + { + project.AppendChild(newSdkElement); + } + else + { + project.InsertBeforeChild(newSdkElement, insertBefore); + } + } + + private static IEnumerable GetSdkElements(ProjectRootElement project) + => project.Children.OfType(); + + private static List ParseSdkAttribute(string? sdkAttribute) + { + if (string.IsNullOrWhiteSpace(sdkAttribute)) + { + return []; + } + + return sdkAttribute + .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(ParseSingleSdkReference) + .ToList(); + } + + private static SdkReference ParseSingleSdkReference(string reference) + { + var slashIndex = reference.IndexOf('/'); + if (slashIndex >= 0) + { + return new SdkReference(reference[..slashIndex], reference[(slashIndex + 1)..]); + } + + return new SdkReference(reference, null); + } + + private static string FormatSdkAttribute(IReadOnlyList sdks) + => string.Join(";", sdks.Select(FormatSingleSdkReference)); + + private static string FormatSingleSdkReference(SdkReference sdk) + => sdk.Version is null ? sdk.Name : $"{sdk.Name}/{sdk.Version}"; + + private static void SetSdkElementVersion(ProjectSdkElement sdkElement, string? version) + { + if (version is null) + { + sdkElement.Version = string.Empty; + } + else + { + sdkElement.Version = version; + } + } + + private readonly record struct SdkReference(string Name, string? Version); +} diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index 494db55f17e8..cd6835937f1a 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -495,6 +495,27 @@ public void GroupWithoutSpace() """)); } + /// + /// New SDK directives should be appended after the last existing SDK to preserve primary SDK order. + /// + [Fact] + public void SdkAppendPreservesPrimaryOrder() + { + Verify( + """ + #:sdk Microsoft.NET.Sdk.Web + #:property X=Y + Console.WriteLine(); + """, + (static editor => editor.Add(new CSharpDirective.Sdk(default) { Name = "Cake.Sdk", Version = "6.2.0" }), + """ + #:sdk Microsoft.NET.Sdk.Web + #:sdk Cake.Sdk@6.2.0 + #:property X=Y + Console.WriteLine(); + """)); + } + /// /// New package directive should be sorted into the correct location in the package group. /// diff --git a/test/dotnet.Tests/CommandTests/Sdk/Add/GivenDotnetAddSdk.cs b/test/dotnet.Tests/CommandTests/Sdk/Add/GivenDotnetAddSdk.cs new file mode 100644 index 000000000000..497686b6ae0b --- /dev/null +++ b/test/dotnet.Tests/CommandTests/Sdk/Add/GivenDotnetAddSdk.cs @@ -0,0 +1,419 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.Cli.Commands; + +namespace Microsoft.DotNet.Cli.Sdk.Add.Tests; + +public sealed class GivenDotnetAddSdk(ITestOutputHelper log) : SdkTest(log) +{ + [Fact] + public void WhenValidSdkIsPassedItGetsAddedToProject() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string sdkName = "Cake.Sdk"; + const string sdkVersion = "6.2.0"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", sdkName, "--version", sdkVersion, "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining($"SDK reference '{sdkName}' version '{sdkVersion}' added to project '{projectFilePath}'"); + + File.ReadAllText(projectFilePath).Should().Contain($""""""); + } + + [Fact] + public void WhenValidSdkIsPassedWithVersionSuffixItGetsAddedToProject() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string sdkName = "Cake.Sdk"; + const string sdkVersion = "6.2.0"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", $"{sdkName}@{sdkVersion}", "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining($"SDK reference '{sdkName}' version '{sdkVersion}' added to project '{projectFilePath}'"); + + File.ReadAllText(projectFilePath).Should().Contain($""""""); + } + + [Fact] + public void WhenSdkInSemicolonDelimitedAttributeIsUpdatedOtherSdksArePreserved() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", "Microsoft.NET.Sdk@9.0.200", "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining("SDK reference 'Microsoft.NET.Sdk' version '9.0.200' updated"); + + File.ReadAllText(projectFilePath).Should().Contain("Microsoft.NET.Sdk/9.0.200;Aspire.AppHost.Sdk/9.1.0"); + } + + [Fact] + public void WhenProjectUsesTopLevelPrimarySdkNewSdkIsInsertedAfterIt() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", "Cake.Sdk@6.2.0", "--no-restore") + .Should().Pass(); + + var contents = File.ReadAllText(projectFilePath); + contents.Should().Contain($""""""); + contents.IndexOf("Microsoft.NET.Sdk", StringComparison.Ordinal) + .Should().BeLessThan(contents.IndexOf("Cake.Sdk", StringComparison.Ordinal)); + } + + [Fact] + public void WhenExistingSdkIsPassedWithoutVersionPinnedVersionIsPreserved() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string sdkName = "Cake.Sdk"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", sdkName, "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining($"SDK reference '{sdkName}' is already present"); + + File.ReadAllText(projectFilePath).Should().Contain($""""""); + } + + [Fact] + public void WhenGlobalJsonSpecifiesSdkVersionProjectOmitsVersion() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + File.WriteAllText(Path.Combine(projectDirectory, "global.json"), """ + { + "msbuild-sdks": { + "Cake.Sdk": "6.2.0" + } + } + """); + + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", "Cake.Sdk", "--no-restore") + .Should().Pass(); + + File.ReadAllText(projectFilePath).Should().Contain(""""""); + } + + [Fact] + public void WhenGlobalJsonWithCommentsAndTrailingCommasOmitsVersion() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + File.WriteAllText(Path.Combine(projectDirectory, "global.json"), """ + { + // pinned by repo policy + "msbuild-sdks": { + "Cake.Sdk": "6.2.0", + }, + } + """); + + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", "Cake.Sdk", "--no-restore") + .Should().Pass(); + + File.ReadAllText(projectFilePath).Should().Contain(""""""); + } + + [Fact] + public void FileBasedApp_WithoutVersionPreservesPinnedVersion() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Cake.Sdk@6.2.0 + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("add", "sdk", "Cake.Sdk", "--file", "Program.cs", "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining("SDK reference 'Cake.Sdk' is already present"); + + File.ReadAllText(file).Should().Be(""" + #:sdk Cake.Sdk@6.2.0 + Console.WriteLine(); + """); + } + + [Fact] + public void WhenExistingAdditiveSdkIsPassedItGetsUpdated() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string sdkName = "Cake.Sdk"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", $"{sdkName}@6.2.0", "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining($"SDK reference '{sdkName}' version '6.2.0' updated in project '{projectFilePath}'"); + + File.ReadAllText(projectFilePath).Should().Contain($""""""); + } + + [Fact] + public void FileBasedApp() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("add", "sdk", "Cake.Sdk@6.2.0", "--file", "Program.cs", "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining("SDK reference 'Cake.Sdk' version '6.2.0' added to file"); + + File.ReadAllText(file).Should().Be(""" + #:sdk Cake.Sdk@6.2.0 + + Console.WriteLine(); + """); + } + + [Fact] + public void FileBasedApp_AdditiveSdkPreservesPrimary() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Microsoft.NET.Sdk.Web + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("add", "sdk", "Cake.Sdk@6.2.0", "--file", "Program.cs", "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining("SDK reference 'Cake.Sdk' version '6.2.0' added to file"); + + File.ReadAllText(file).Should().Be(""" + #:sdk Microsoft.NET.Sdk.Web + #:sdk Cake.Sdk@6.2.0 + Console.WriteLine(); + """); + } + + [Fact] + public void WhenBundledSdkIsAddedWithoutVersionVersionIsOmitted() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string sdkName = "Microsoft.NET.Sdk.Web"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", sdkName, "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining($"SDK reference '{sdkName}' added to project"); + + File.ReadAllText(projectFilePath).Should().Contain($""""""); + } + + [Fact] + public void WhenGlobalJsonIsMalformedAddingNewSdkFailsGracefully() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var appDirectory = Path.Join(testInstance.Path, "app"); + Directory.CreateDirectory(appDirectory); + File.WriteAllText(Path.Combine(appDirectory, "global.json"), "{ invalid json"); + + var file = Path.Join(appDirectory, "Program.cs"); + File.WriteAllText(file, """ + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("add", "sdk", "Cake.Sdk", "--file", "app/Program.cs", "--no-restore") + .Should().Fail() + .And.HaveStdErrContaining("Could not parse global.json file"); + } + + [Fact] + public void WhenExistingSdkIsReAddedWithoutVersionMalformedGlobalJsonIsIgnored() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var appDirectory = Path.Join(testInstance.Path, "app"); + Directory.CreateDirectory(appDirectory); + File.WriteAllText(Path.Combine(appDirectory, "global.json"), "{ invalid json"); + + var file = Path.Join(appDirectory, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Cake.Sdk@6.2.0 + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("add", "sdk", "Cake.Sdk", "--file", "app/Program.cs", "--no-restore") + .Should().Pass() + .And.HaveStdOutContaining("SDK reference 'Cake.Sdk' is already present"); + + File.ReadAllText(file).Should().Be(""" + #:sdk Cake.Sdk@6.2.0 + Console.WriteLine(); + """); + } + + [Fact] + public void FileBasedApp_ReplaceExisting() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Cake.Sdk@1.0.0 + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("add", "sdk", "Cake.Sdk@6.2.0", "--file", "Program.cs", "--no-restore") + .Should().Pass(); + + File.ReadAllText(file).Should().Be(""" + #:sdk Cake.Sdk@6.2.0 + Console.WriteLine(); + """); + } + + [Fact] + public void WhenSdkIdentityHasEmptyVersionSuffixItFails() + { + new DotnetCommand(Log) + .Execute("add", "sdk", "Cake.Sdk@") + .Should().Fail() + .And.HaveStdErrContaining("SDK version must not be empty"); + } + + [Fact] + public void WhenRestoreFailsProjectFileEncodingIsPreserved() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + var projectContents = File.ReadAllText(projectFilePath); + File.WriteAllText(projectFilePath, projectContents, Encoding.Unicode); + var originalBytes = File.ReadAllBytes(projectFilePath); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("add", "sdk", "Cake.Sdk@99999.0.0") + .Should().Fail(); + + File.ReadAllBytes(projectFilePath).Should().Equal(originalBytes); + File.ReadAllText(projectFilePath, Encoding.Unicode).Should().NotContain("Cake.Sdk"); + } +} diff --git a/test/dotnet.Tests/CommandTests/Sdk/Remove/GivenDotnetRemoveSdk.cs b/test/dotnet.Tests/CommandTests/Sdk/Remove/GivenDotnetRemoveSdk.cs new file mode 100644 index 000000000000..bf096e97759c --- /dev/null +++ b/test/dotnet.Tests/CommandTests/Sdk/Remove/GivenDotnetRemoveSdk.cs @@ -0,0 +1,313 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.Cli.Commands; + +namespace Microsoft.DotNet.Cli.Sdk.Remove.Tests; + +public sealed class GivenDotnetRemoveSdk(ITestOutputHelper log) : SdkTest(log) +{ + [Fact] + public void WhenReferencedSdkIsPassedItGetsRemoved() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string sdkName = "Cake.Sdk"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("remove", "sdk", sdkName) + .Should().Pass() + .And.HaveStdOutContaining($"Removing SDK reference '{sdkName}' from project '{projectFilePath}'"); + + File.ReadAllText(projectFilePath).Should().NotContain(sdkName); + } + + [Fact] + public void WhenReferencedSdkIsPassedWithVersionSuffixItGetsRemoved() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string sdkName = "Cake.Sdk"; + const string sdkVersion = "6.2.0"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("remove", "sdk", $"{sdkName}@{sdkVersion}") + .Should().Pass() + .And.HaveStdOutContaining($"Removing SDK reference '{sdkName}' from project '{projectFilePath}'"); + + File.ReadAllText(projectFilePath).Should().NotContain(sdkName); + } + + [Fact] + public void WhenSdkInSemicolonDelimitedAttributeIsPassedItGetsRemoved() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("remove", "sdk", "Aspire.AppHost.Sdk") + .Should().Pass() + .And.HaveStdOutContaining($"Removing SDK reference 'Aspire.AppHost.Sdk' from project '{projectFilePath}'"); + + var contents = File.ReadAllText(projectFilePath); + contents.Should().Contain(""""""); + contents.Should().NotContain("Aspire.AppHost.Sdk"); + } + + [Fact] + public void WhenPrimarySdkIsPassedItFails() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("remove", "sdk", "Microsoft.NET.Sdk") + .Should().Fail(); + } + + [Fact] + public void WhenPrimaryRemovalFailsEarlierAdditiveRemovalIsNotPersisted() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + const string additiveSdk = "Cake.Sdk"; + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("remove", "sdk", additiveSdk, "Microsoft.NET.Sdk") + .Should().Fail(); + + File.ReadAllText(projectFilePath).Should().Contain(additiveSdk); + } + + [Fact] + public void WhenPrimaryTopLevelSdkElementIsPassedItFails() + { + const string testAsset = "TestAppSimple"; + var projectDirectory = TestAssetsManager + .CopyTestAsset(testAsset) + .WithSource() + .Path; + + var projectFilePath = Path.Combine(projectDirectory, $"{testAsset}.csproj"); + File.WriteAllText(projectFilePath, $""" + + + + + + {ToolsetInfo.CurrentTargetFramework} + + + + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("remove", "sdk", "Microsoft.NET.Sdk") + .Should().Fail(); + + File.ReadAllText(projectFilePath).Should().Contain(""""""); + } + + [Fact] + public void FileBasedApp() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Microsoft.NET.Sdk.Web + #:sdk Cake.Sdk@6.2.0 + + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("remove", "sdk", "Cake.Sdk", "--file", "Program.cs") + .Should().Pass() + .And.HaveStdOut(string.Format(CliCommandStrings.DirectivesRemoved, "#:sdk", 1, "Cake.Sdk", file)); + + File.ReadAllText(file).Should().Be(""" + #:sdk Microsoft.NET.Sdk.Web + + Console.WriteLine(); + """); + } + + [Fact] + public void FileBasedApp_PrimarySdkFails() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Microsoft.NET.Sdk.Web + #:sdk Cake.Sdk@6.2.0 + + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("remove", "sdk", "Microsoft.NET.Sdk.Web", "--file", "Program.cs") + .Should().Fail(); + + File.ReadAllText(file).Should().Contain("#:sdk Microsoft.NET.Sdk.Web"); + File.ReadAllText(file).Should().Contain("#:sdk Cake.Sdk@6.2.0"); + } + + [Fact] + public void FileBasedApp_PrimaryRemovalDoesNotRemoveAdditive() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Microsoft.NET.Sdk.Web + #:sdk Cake.Sdk@6.2.0 + + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("remove", "sdk", "Cake.Sdk", "Microsoft.NET.Sdk.Web", "--file", "Program.cs") + .Should().Fail(); + + File.ReadAllText(file).Should().Contain("#:sdk Cake.Sdk@6.2.0"); + } + + [Fact] + public void FileBasedApp_NotFound() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Microsoft.NET.Sdk.Web + + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("remove", "sdk", "Cake.Sdk", "--file", "Program.cs") + .Should().Fail() + .And.HaveStdOut(string.Format(CliCommandStrings.SdkReferenceNotFoundInFile, "Cake.Sdk", file)); + } + + [Fact] + public void FileBasedApp_PrimaryValidatedBeforeAnyRemoval() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Microsoft.NET.Sdk.Web + #:sdk Cake.Sdk@6.2.0 + + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("remove", "sdk", "Microsoft.NET.Sdk.Web", "Cake.Sdk", "--file", "Program.cs") + .Should().Fail(); + + File.ReadAllText(file).Should().Contain("#:sdk Cake.Sdk@6.2.0"); + } + + [Fact] + public void FileBasedApp_Multiple() + { + var testInstance = TestAssetsManager.CreateTestDirectory(); + var file = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(file, """ + #:sdk Microsoft.NET.Sdk.Web + #:sdk Cake.Sdk@6.2.0 + #:sdk Another.Sdk@1.0.0 + #:sdk Cake.Sdk@2.0.0 + + Console.WriteLine(); + """); + + new DotnetCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .Execute("remove", "sdk", "Cake.Sdk", "--file", "Program.cs") + .Should().Pass() + .And.HaveStdOut(string.Format(CliCommandStrings.DirectivesRemoved, "#:sdk", 2, "Cake.Sdk", file)); + + File.ReadAllText(file).Should().Be(""" + #:sdk Microsoft.NET.Sdk.Web + #:sdk Another.Sdk@1.0.0 + + Console.WriteLine(); + """); + } +}