diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Add/DotNetPackageAdderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Add/DotNetPackageAdderFixture.cs new file mode 100644 index 0000000000..2c2b1fa842 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Add/DotNetPackageAdderFixture.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Package.Add; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Add +{ + internal sealed class DotNetPackageAdderFixture : DotNetFixture + { + public string PackageName { get; set; } + + public string Project { get; set; } + + protected override void RunTool() + { + var tool = new DotNetPackageAdder(FileSystem, Environment, ProcessRunner, Tools); + tool.Add(PackageName, Project, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Add/DotNetPackageAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Add/DotNetPackageAdderTests.cs new file mode 100644 index 0000000000..9eef45bc11 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Add/DotNetPackageAdderTests.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Add; +using Cake.Common.Tests.Fixtures.Tools.DotNet.SDKCheck; +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Restore; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Package.Add; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Package.Add +{ + public sealed class DotNetPackageAdderTests + { + public sealed class TheAddMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_PackageName_Is_Null() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageName"); + } + + [Fact] + public void Should_Add_Project_Argument() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.Project = "ToDo.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add \"ToDo.csproj\" package Microsoft.AspNetCore.StaticFiles", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.Settings.Framework = "net7.0"; + fixture.Settings.Interactive = true; + fixture.Settings.NoRestore = true; + fixture.Settings.PackageDirectory = "./src/project"; + fixture.Settings.Prerelease = true; + fixture.Settings.Source = "http://www.nuget.org/api/v2/package"; + fixture.Settings.Version = "1.0.0"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "add package Microsoft.AspNetCore.StaticFiles --framework net7.0 --interactive --no-restore --package-directory \"/Working/src/project\" --prerelease --source \"http://www.nuget.org/api/v2/package\" --version \"1.0.0\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs index 4e6dedf7ca..749508588b 100644 --- a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs @@ -15,6 +15,7 @@ using Cake.Common.Tools.DotNet.NuGet.Push; using Cake.Common.Tools.DotNet.NuGet.Source; using Cake.Common.Tools.DotNet.Pack; +using Cake.Common.Tools.DotNet.Package.Add; using Cake.Common.Tools.DotNet.Publish; using Cake.Common.Tools.DotNet.Restore; using Cake.Common.Tools.DotNet.Run; @@ -2285,5 +2286,104 @@ public static void DotNetWorkloadRestore(this ICakeContext context, string proje var restorer = new DotNetWorkloadRestorer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); restorer.Restore(project, settings); } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// + /// + /// DotNetAddPackage("Cake.FileHelper"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("AddPackage")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName) + { + context.DotNetAddPackage(packageName, null, null); + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// The project or solution file to install workloads for. + /// + /// + /// DotNetAddPackage("Cake.FileHelper", "ToDo.csproj"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("AddPackage")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName, string project) + { + context.DotNetAddPackage(packageName, project, null); + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// The settings. + /// + /// + /// var settings = new DotNetPackageAddSettings + /// { + /// NoRestore = true, + /// Version = "6.1.3" + /// }; + /// + /// DotNetAddPackage("Cake.FileHelper", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("AddPackage")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName, DotNetPackageAddSettings settings) + { + context.DotNetAddPackage(packageName, null, settings); + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// The project or solution file to install workloads for. + /// The settings. + /// + /// + /// var settings = new DotNetPackageAddSettings + /// { + /// NoRestore = true, + /// Version = "6.1.3" + /// }; + /// + /// DotNetAddPackage("Cake.FileHelper", "ToDo.csproj", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("AddPackage")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName, string project, DotNetPackageAddSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings is null) + { + settings = new DotNetPackageAddSettings(); + } + + var adder = new DotNetPackageAdder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + adder.Add(packageName, project, settings); + } } } diff --git a/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAddSettings.cs b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAddSettings.cs new file mode 100644 index 0000000000..cd52cc571e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAddSettings.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. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Package.Add +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetPackageAddSettings : DotNetSettings + { + /// + /// Gets or sets a specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes run faster, but requires restore to be done before run is executed. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets the directory path where to restore the packages. + /// The default package restore location is %userprofile%\.nuget\packages on Windows and ~/.nuget/packages on macOS and Linux. + /// + public DirectoryPath PackageDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to allow installation of prerelease packages. + /// Available since .NET Core 5 SDK. + /// + public bool Prerelease { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use during the restore operation. + /// + public string Source { get; set; } + + /// + /// Gets or sets the version of the package to install. + /// If none specified, the latest will be used. + /// + public string Version { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAdder.cs b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAdder.cs new file mode 100644 index 0000000000..299a21838a --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAdder.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Package.Add +{ + /// + /// .NET package adder. + /// + public sealed class DotNetPackageAdder : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetPackageAdder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The package reference to add. + /// The target project file path. If not specified, the command searches the current directory for one. + /// The settings. + public void Add(string packageName, string project, DotNetPackageAddSettings settings) + { + if (packageName == null) + { + throw new ArgumentNullException(nameof(packageName)); + } + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + RunCommand(settings, GetArguments(packageName, project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageName, string project, DotNetPackageAddSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("add"); + + // Project path + if (project != null) + { + builder.AppendQuoted(project); + } + + // Package Name + builder.AppendSwitch("package", packageName); + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.AppendSwitch("--framework", settings.Framework); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // Package Directory + if (settings.PackageDirectory != null) + { + builder.Append("--package-directory"); + builder.AppendQuoted(settings.PackageDirectory.MakeAbsolute(_environment).FullPath); + } + + // Prerelease + if (settings.Prerelease) + { + builder.Append("--prerelease"); + } + + // Source + if (!string.IsNullOrEmpty(settings.Source)) + { + builder.AppendSwitchQuoted("--source", settings.Source); + } + + // Version + if (!string.IsNullOrEmpty(settings.Version)) + { + builder.AppendSwitchQuoted("--version", settings.Version); + } + + return builder; + } + } +} diff --git a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake index 68b058a6d6..acfa1a1ad2 100644 --- a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake +++ b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake @@ -251,7 +251,7 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetFormat") var project = path.CombineWithFilePath("hwapp/hwapp.csproj"); // When - DotNetFormat(project.FullPath); + DotNetFormat(project.FullPath); }); Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSDKCheck") @@ -308,6 +308,27 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadRestore") DotNetWorkloadRestore(project.FullPath); }); +Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetAddPackage") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.Setup") + .Does(() => +{ + // Given + var path = Paths.Temp.Combine("./Cake.Common/Tools/DotNet"); + var project = path.CombineWithFilePath("hwapp/hwapp.csproj"); + var package = "grok.net"; + + // When + DotNetAddPackage(package, project.FullPath); + + var value = XmlPeek( + project.FullPath, + $"/Project/ItemGroup/PackageReference[@Include='{package}']/@Include" + ); + + // Then + Assert.Equal(package, value); +}); + Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetRestore") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuild") @@ -329,6 +350,7 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadRepair") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadUpdate") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadRestore") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetAddPackage") .Does(() => { // When