From 1d40c7c3c84db87d08f76307ee0ad878e73051d8 Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Thu, 7 Nov 2024 18:21:50 +0200 Subject: [PATCH 1/2] Add DotNetSlnAdd alias for dotnet sln add command --- .../DotNet/Sln/Add/DotNetSlnAdderFixture.cs | 23 ++ .../DotNet/Sln/Add/DotNetSlnAdderTests.cs | 220 ++++++++++++++++++ src/Cake.Common/Tools/DotNet/DotNetAliases.cs | 98 ++++++++ .../DotNet/Sln/Add/DotNetSlnAddSettings.cs | 25 ++ .../Tools/DotNet/Sln/Add/DotNetSlnAdder.cs | 96 ++++++++ .../Tools/DotNet/DotNetAliases.cake | 13 ++ 6 files changed, 475 insertions(+) create mode 100644 src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Add/DotNetSlnAdderFixture.cs create mode 100644 src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs create mode 100644 src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAddSettings.cs create mode 100644 src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAdder.cs diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Add/DotNetSlnAdderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Add/DotNetSlnAdderFixture.cs new file mode 100644 index 0000000000..ede36264de --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Add/DotNetSlnAdderFixture.cs @@ -0,0 +1,23 @@ +// 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.Collections.Generic; +using Cake.Common.Tools.DotNet.Sln.Add; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.Add +{ + internal sealed class DotNetSlnAdderFixture : DotNetFixture + { + public FilePath Solution { get; set; } + + public IEnumerable ProjectPath { get; set; } + + protected override void RunTool() + { + var tool = new DotNetSlnAdder(FileSystem, Environment, ProcessRunner, Tools); + tool.Add(Solution, ProjectPath, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs new file mode 100644 index 0000000000..aec3102c1a --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs @@ -0,0 +1,220 @@ +// 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.Common.Tests.Fixtures.Tools.DotNet.Sln.Add; +using Cake.Common.Tools.DotNet; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Sln.Add +{ + public sealed class DotNetSlnAdderTests + { + public sealed class TheAddMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + 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 DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + 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_ProjectPath_Is_Null() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectPath"); + } + + [Fact] + public void Should_Throw_If_ProjectPath_Is_Empty() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = []; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectPath"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_InRoot_And_SolutionFolder_Are_Used_Together() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.InRoot = true; + fixture.Settings.SolutionFolder = "mylibs"; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("InRoot and SolutionFolder cannot be used together.", result.Message); + } + + [Fact] + public void Should_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.Solution = (FilePath)"test.sln"; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln \"/Working/test.sln\" add \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Not_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.Solution = null; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_ProjectPath_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_All_ProjectPath() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj", "./lib2.csproj", "./lib3.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add \"/Working/lib1.csproj\" \"/Working/lib2.csproj\" \"/Working/lib3.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_InRoot_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.InRoot = true; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add --in-root \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_SolutionFolder_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.SolutionFolder = "mylibs"; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add --solution-folder \"/Working/mylibs\" \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.Solution = (FilePath)"test.sln"; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.Verbosity = DotNetVerbosity.Detailed; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln \"/Working/test.sln\" add \"/Working/lib1.csproj\" --verbosity detailed", result.Args); + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs index 8a46139242..5febd18664 100644 --- a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs @@ -26,6 +26,7 @@ using Cake.Common.Tools.DotNet.Restore; using Cake.Common.Tools.DotNet.Run; using Cake.Common.Tools.DotNet.SDKCheck; +using Cake.Common.Tools.DotNet.Sln.Add; using Cake.Common.Tools.DotNet.Sln.List; using Cake.Common.Tools.DotNet.Test; using Cake.Common.Tools.DotNet.Tool; @@ -2972,5 +2973,102 @@ public static IEnumerable DotNetSlnList(this ICakeContext context, FileP var lister = new DotNetSlnLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); return lister.List(solution, settings); } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// + /// + /// DotNetSlnAdd(GetFiles("./*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, IEnumerable projectPath) + { + context.DotNetSlnAdd(null, projectPath); + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// + /// + /// DotNetSlnAdd("app.sln", GetFiles("./*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, FilePath solution, IEnumerable projectPath) + { + context.DotNetSlnAdd(solution, projectPath, null); + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + /// + /// + /// var settings = new DotNetSlnAddSettings + /// { + /// SolutionFolder = "libs/math" + /// }; + /// + /// DotNetSlnAdd(GetFiles("./*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + context.DotNetSlnAdd(null, projectPath, settings); + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + /// + /// + /// var settings = new DotNetSlnAddSettings + /// { + /// SolutionFolder = "libs/math" + /// }; + /// + /// DotNetSlnAdd("app.sln", GetFiles("./*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, FilePath solution, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings is null) + { + settings = new DotNetSlnAddSettings(); + } + + var adder = new DotNetSlnAdder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + adder.Add(solution, projectPath, settings); + } } } diff --git a/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAddSettings.cs b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAddSettings.cs new file mode 100644 index 0000000000..0896472f92 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAddSettings.cs @@ -0,0 +1,25 @@ +// 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.Sln.Add +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetSlnAddSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating whether to place the projects in the root of the solution, rather than creating a solution folder. + /// Can't be used with . + /// + public bool InRoot { get; set; } + + /// + /// Gets or sets the destination solution folder path to add the projects to. Can't be used with . + /// + public DirectoryPath SolutionFolder { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAdder.cs b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAdder.cs new file mode 100644 index 0000000000..81bf58c04b --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAdder.cs @@ -0,0 +1,96 @@ +// 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 System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Sln.Add +{ + /// + /// .NET project adder. + /// + public sealed class DotNetSlnAdder : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetSlnAdder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + public void Add(FilePath solution, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + if (projectPath == null || !projectPath.Any()) + { + throw new ArgumentNullException(nameof(projectPath)); + } + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (settings.InRoot && settings.SolutionFolder != null) + { + throw new ArgumentException("InRoot and SolutionFolder cannot be used together."); + } + + RunCommand(settings, GetArguments(solution, projectPath, settings)); + } + + private ProcessArgumentBuilder GetArguments(FilePath solution, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("sln"); + + // Solution path + if (solution != null) + { + builder.AppendQuoted(solution.MakeAbsolute(_environment).FullPath); + } + + builder.Append("add"); + + // Solution folder + if (settings.SolutionFolder != null) + { + builder.AppendSwitchQuoted("--solution-folder", settings.SolutionFolder.MakeAbsolute(_environment).FullPath); + } + + // In root + if (settings.InRoot) + { + builder.Append("--in-root"); + } + + // Project path + foreach (var project in projectPath) + { + builder.AppendQuoted(project.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake index 711ef50aae..a0b750c8d2 100644 --- a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake +++ b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake @@ -451,6 +451,18 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSlnList") Assert.Contains(result, item => item == project); }); +Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSlnAdd") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.Setup") + .Does(() => +{ + // Given + var path = Paths.Temp.Combine("./Cake.Common/Tools/DotNet"); + var solution = path.CombineWithFilePath("hwapp.sln"); + var project = path.CombineWithFilePath("hwapp/hwapp.csproj"); + // When + DotNetSlnAdd(solution.FullPath, new[] { (FilePath)project.FullPath}); +}); + Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetRestore") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuild") @@ -480,6 +492,7 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetRemoveReference") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListReference") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSlnList") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSlnAdd") .Does(() => { // When From 7ec572d6f5e0066415017ee36354eaaa2419ecc7 Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Thu, 7 Nov 2024 18:34:04 +0200 Subject: [PATCH 2/2] Fix SA1010 --- .../Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs index aec3102c1a..22094b0180 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs @@ -64,7 +64,7 @@ public void Should_Throw_If_ProjectPath_Is_Empty() { // Given var fixture = new DotNetSlnAdderFixture(); - fixture.ProjectPath = []; + fixture.ProjectPath = new FilePath[] { }; // When var result = Record.Exception(() => fixture.Run());