From 1295f7f41d0f026432daf33dacc2dbccda2b54c3 Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Sun, 3 Nov 2024 02:20:10 +0200 Subject: [PATCH 1/9] Add Initial version of 'dotnet sln list' alias --- .../DotNet/Sln/List/DotNetSlnListerFixture.cs | 44 ++++++ .../DotNet/Sln/List/DotNetSlnListerTests.cs | 147 ++++++++++++++++++ src/Cake.Common/Tools/DotNet/DotNetAliases.cs | 89 +++++++++++ .../DotNet/Sln/List/DotNetSlnListSettings.cs | 13 ++ .../Tools/DotNet/Sln/List/DotNetSlnLister.cs | 109 +++++++++++++ .../Tools/DotNet/DotNetAliases.cake | 15 ++ 6 files changed, 417 insertions(+) create mode 100644 src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/List/DotNetSlnListerFixture.cs create mode 100644 src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs create mode 100644 src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnListSettings.cs create mode 100644 src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/List/DotNetSlnListerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/List/DotNetSlnListerFixture.cs new file mode 100644 index 0000000000..0fbdbe7d85 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/List/DotNetSlnListerFixture.cs @@ -0,0 +1,44 @@ +// 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.List; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.List +{ + internal sealed class DotNetSlnListerFixture : DotNetFixture + { + public string Solution { get; set; } + + public string StandardError { get; set; } + + public IEnumerable Projects { get; private set; } + + public void GivenProjectsResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Project(s)", + "--------------------", + "Common\\Common.AspNetCore\\Common.AspNetCore.csproj", + "Common\\Common.Messaging\\Common.Messaging.csproj", + "Common\\Common.Utilities\\Common.Utilities.csproj" + }); + } + + public void GivenErrorResult() + { + ProcessRunner.Process.SetStandardError(new string[] + { + StandardError + }); + } + + protected override void RunTool() + { + var tool = new DotNetSlnLister(FileSystem, Environment, ProcessRunner, Tools); + Projects = tool.List(Solution, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs new file mode 100644 index 0000000000..b0fd5e59ba --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs @@ -0,0 +1,147 @@ +// 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.Sln.List; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Sln.List +{ + public sealed class DotNetSlnListerTests + { + public sealed class TheListMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetSlnListerFixture(); + 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 DotNetSlnListerFixture(); + 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 DotNetSlnListerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Not_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.Solution = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sln list", result.Args); + } + + [Fact] + public void Should_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.Solution = "ToDo.sln"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sln \"ToDo.sln\" list", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.Solution = "ToDo.sln"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "sln \"ToDo.sln\" list --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_Projects() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.GivenProjectsResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.Projects, + item => + { + Assert.Equal(item, "Common\\Common.AspNetCore\\Common.AspNetCore.csproj"); + }, + item => + { + Assert.Equal(item, ".Common\\Common.Messaging\\Common.Messaging.csproj"); + }, + item => + { + Assert.Equal(item, "Common\\Common.Utilities\\Common.Utilities.csproj"); + }); + } + + [Fact] + public void Should_Return_StandardError_ExitCode() + { + const string expectedStandardError = "Specified solution file C:\\Cake\\Cake.Core\\ does not exist, or there is no solution file in the directory."; + + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.StandardError = expectedStandardError; + fixture.GivenErrorResult(); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardError, fixture.StandardError); + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs index 595e626d37..d54eb84246 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.List; using Cake.Common.Tools.DotNet.Test; using Cake.Common.Tools.DotNet.Tool; using Cake.Common.Tools.DotNet.VSTest; @@ -2883,5 +2884,93 @@ public static DotNetPackageList DotNetListPackage(this ICakeContext context, str var lister = new DotNetPackageLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); return lister.List(project, settings); } + + /// + /// Lists all projects in a solution file. + /// + /// The context. + /// The list of projects. + /// + /// + /// var projects = DotNetSlnList(); + /// + /// foreach (var project in projects) + /// { + /// Information(project); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] + public static IEnumerable DotNetSlnList(this ICakeContext context) + { + return context.DotNetListSln(null); + } + + /// + /// Lists all projects in a solution file. + /// + /// The context. + /// The solution file to use. If this argument is omitted, the command searches the current directory for one. If it finds no solution file or multiple solution files, the command fails. + /// The list of projects. + /// + /// + /// var projects = DotNetSlnList("./app/app.sln"); + /// + /// foreach (var project in projects) + /// { + /// Information(project); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] + public static IEnumerable DotNetListSln(this ICakeContext context, string solution) + { + return context.DotNetSlnList(solution, null); + } + + /// + /// Lists all projects in a solution file. + /// + /// The context. + /// The solution file to use. If this argument is omitted, the command searches the current directory for one. If it finds no solution file or multiple solution files, the command fails. + /// The settings. + /// The list of projects. + /// + /// + /// var settings = new DotNetSlnListSettings + /// { + /// Verbosity = DotNetVerbosity.Diagnostic + /// }; + /// + /// var projects = DotNetSlnList("./app/app.sln"); + /// + /// foreach (var project in projects) + /// { + /// Information(project); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] + public static IEnumerable DotNetSlnList(this ICakeContext context, string solution, DotNetSlnListSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings is null) + { + settings = new DotNetSlnListSettings(); + } + + var lister = new DotNetSlnLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return lister.List(solution, settings); + } } } diff --git a/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnListSettings.cs b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnListSettings.cs new file mode 100644 index 0000000000..62a11de27f --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnListSettings.cs @@ -0,0 +1,13 @@ +// 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. + +namespace Cake.Common.Tools.DotNet.Sln.List +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetSlnListSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs new file mode 100644 index 0000000000..063db4c82c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs @@ -0,0 +1,109 @@ +// 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.List +{ + /// + /// .NET projects lister. + /// + public sealed class DotNetSlnLister : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetSlnLister( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Lists all projects in a solution file. + /// + /// The solution file to use. If not specified, the command searches the current directory for one. If it finds no solution file or multiple solution files, the command fails. + /// The settings. + /// The list of project-to-project references. + public IEnumerable List(string solution, DotNetSlnListSettings settings) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true + }; + + IEnumerable result = null; + RunCommand(settings, GetArguments(solution, settings), processSettings, + process => result = process.GetStandardOutput()); + + return ParseResult(result).ToList(); + } + + private ProcessArgumentBuilder GetArguments(string solution, DotNetSlnListSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("sln"); + + // Solution path + if (!string.IsNullOrWhiteSpace(solution)) + { + builder.AppendQuoted(solution); + } + + builder.Append("list"); + + return builder; + } + + private static IEnumerable ParseResult(IEnumerable result) + { + bool first = true; + foreach (var line in result) + { + if (first) + { + if (line?.StartsWith("Project(s)") == true) + { + first = false; + } + continue; + } + + if (string.IsNullOrWhiteSpace(line)) + { + break; + } + + var trimmedLine = line.Trim(); + + if (trimmedLine.Trim().All(c => c == '-')) + { + continue; + } + + yield return trimmedLine; + } + } + } +} diff --git a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake index 0fd9f3a0ce..8b8386e8bd 100644 --- a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake +++ b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake @@ -437,6 +437,20 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListPackage") Assert.Contains(result.Projects, item => item.Path == project); }); +Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListSln") + .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 = "hwapp.common\hwapp.common.csproj"; + // When + var result = DotNetSlnList(solution.FullPath); + // Then + Assert.Contains(result, item => item == project); +}); + Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetRestore") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuild") @@ -465,6 +479,7 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetAddReference") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetRemoveReference") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListReference") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListSln") .Does(() => { // When From 6b27117ae21e2083a27087cfa1d6975dafdcb6f4 Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Sun, 3 Nov 2024 02:28:24 +0200 Subject: [PATCH 2/9] Fix unit test --- .../Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs index b0fd5e59ba..080634f5ae 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs @@ -118,7 +118,7 @@ public void Should_Return_Correct_List_Of_Projects() }, item => { - Assert.Equal(item, ".Common\\Common.Messaging\\Common.Messaging.csproj"); + Assert.Equal(item, "Common\\Common.Messaging\\Common.Messaging.csproj"); }, item => { From 28930d68411e64664f9584033cc5a0b66801cc0c Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Sun, 3 Nov 2024 13:34:17 +0200 Subject: [PATCH 3/9] Fix test --- src/Cake.Common/Tools/DotNet/DotNetAliases.cs | 4 ++-- tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs index d54eb84246..13fd812c56 100644 --- a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs @@ -2905,7 +2905,7 @@ public static DotNetPackageList DotNetListPackage(this ICakeContext context, str [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] public static IEnumerable DotNetSlnList(this ICakeContext context) { - return context.DotNetListSln(null); + return context.DotNetSlnList(null); } /// @@ -2927,7 +2927,7 @@ public static IEnumerable DotNetSlnList(this ICakeContext context) [CakeMethodAlias] [CakeAliasCategory("Sln")] [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] - public static IEnumerable DotNetListSln(this ICakeContext context, string solution) + public static IEnumerable DotNetSlnList(this ICakeContext context, string solution) { return context.DotNetSlnList(solution, null); } diff --git a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake index 8b8386e8bd..e04263b1d3 100644 --- a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake +++ b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake @@ -444,7 +444,7 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListSln") // Given var path = Paths.Temp.Combine("./Cake.Common/Tools/DotNet"); var solution = path.CombineWithFilePath("hwapp.sln"); - var project = "hwapp.common\hwapp.common.csproj"; + var project = new DirectoryPath("./hwapp.common").CombineWithFilePath("hwapp.common.csproj"); // When var result = DotNetSlnList(solution.FullPath); // Then From 49e8e19235b166130bead026dcd296799223e198 Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Thu, 7 Nov 2024 18:02:20 +0200 Subject: [PATCH 4/9] Use FilePath type for Solution argument --- .../Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs | 4 ++-- src/Cake.Common/Tools/DotNet/DotNetAliases.cs | 4 ++-- src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs index 080634f5ae..c39a97a093 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs @@ -81,7 +81,7 @@ public void Should_Add_Solution_Argument() var result = fixture.Run(); // Then - Assert.Equal("sln \"ToDo.sln\" list", result.Args); + Assert.Equal("sln \"/Working/ToDo.sln\" list", result.Args); } [Fact] @@ -96,7 +96,7 @@ public void Should_Add_Additional_Arguments() var result = fixture.Run(); // Then - var expected = "sln \"ToDo.sln\" list --verbosity diagnostic"; + var expected = "sln \"/Working/ToDo.sln\" list --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 13fd812c56..8a46139242 100644 --- a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs @@ -2927,7 +2927,7 @@ public static IEnumerable DotNetSlnList(this ICakeContext context) [CakeMethodAlias] [CakeAliasCategory("Sln")] [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] - public static IEnumerable DotNetSlnList(this ICakeContext context, string solution) + public static IEnumerable DotNetSlnList(this ICakeContext context, FilePath solution) { return context.DotNetSlnList(solution, null); } @@ -2957,7 +2957,7 @@ public static IEnumerable DotNetSlnList(this ICakeContext context, strin [CakeMethodAlias] [CakeAliasCategory("Sln")] [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] - public static IEnumerable DotNetSlnList(this ICakeContext context, string solution, DotNetSlnListSettings settings) + public static IEnumerable DotNetSlnList(this ICakeContext context, FilePath solution, DotNetSlnListSettings settings) { if (context is null) { diff --git a/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs index 063db4c82c..cb60cab9cf 100644 --- a/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs +++ b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs @@ -40,7 +40,7 @@ public DotNetSlnLister( /// The solution file to use. If not specified, the command searches the current directory for one. If it finds no solution file or multiple solution files, the command fails. /// The settings. /// The list of project-to-project references. - public IEnumerable List(string solution, DotNetSlnListSettings settings) + public IEnumerable List(FilePath solution, DotNetSlnListSettings settings) { if (settings == null) { @@ -59,16 +59,16 @@ public IEnumerable List(string solution, DotNetSlnListSettings settings) return ParseResult(result).ToList(); } - private ProcessArgumentBuilder GetArguments(string solution, DotNetSlnListSettings settings) + private ProcessArgumentBuilder GetArguments(FilePath solution, DotNetSlnListSettings settings) { var builder = CreateArgumentBuilder(settings); builder.Append("sln"); // Solution path - if (!string.IsNullOrWhiteSpace(solution)) + if (solution != null) { - builder.AppendQuoted(solution); + builder.AppendQuoted(solution.MakeAbsolute(_environment).FullPath); } builder.Append("list"); From 2cde9c5c226b88de249a33dfb6e13beef2cb942e Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Thu, 7 Nov 2024 18:26:15 +0200 Subject: [PATCH 5/9] Rename integration test --- tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake index e04263b1d3..711ef50aae 100644 --- a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake +++ b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake @@ -437,7 +437,7 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListPackage") Assert.Contains(result.Projects, item => item.Path == project); }); -Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListSln") +Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSlnList") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.Setup") .Does(() => { @@ -479,7 +479,7 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetAddReference") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetRemoveReference") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListReference") - .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetListSln") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSlnList") .Does(() => { // When From f7305973906792e61b1ad51c39917a29f5b58789 Mon Sep 17 00:00:00 2001 From: Mattias Karlsson Date: Sat, 9 Nov 2024 23:05:30 +0100 Subject: [PATCH 6/9] (GH-4391) GHA cross-job download artifact support * fixes #4391 --- .github/workflows/build.yml | 13 +++- .../Build/GitHubActionsCommandsFixture.cs | 63 ++++++++++++++----- .../Artifact/GitHubActionsArtifactService.cs | 31 ++++++++- .../Commands/Artifact/ListArtifactsRequest.cs | 20 ++++++ .../Artifact/ListArtifactsResponse.cs | 27 ++++++++ .../GitHubActions/GitHubActionsProvider.cake | 18 +++++- 6 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsRequest.cs create mode 100644 src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsResponse.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8089bfce3e..a6b811e2cf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,19 @@ on: - develop - hotfix/* jobs: + prepare: + name: Prepare integration tests + runs-on: ubuntu-latest + steps: + - run: echo "Cake Integration Tests" > cake-integration-tests.txt + - uses: actions/upload-artifact@v4 + with: + name: cake-integration-tests + path: cake-integration-tests.txt + build: name: Build + needs: prepare runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -34,7 +45,7 @@ jobs: - name: Run Cake script id: build-cake - uses: cake-build/cake-action@v1 + uses: cake-build/cake-action@master with: target: Run-Integration-Tests cake-version: tool-manifest diff --git a/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs index 58d1d92da4..23e083bcc2 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Cake.Common.Build.GitHubActions.Commands; using Cake.Common.Build.GitHubActions.Commands.Artifact; -using Cake.Common.Build.GitHubActions.Data; using Cake.Common.Tests.Fakes; using Cake.Core; using Cake.Core.IO; @@ -22,21 +21,45 @@ internal sealed class GitHubActionsCommandsFixture : HttpMessageHandler private const string ArtifactUrl = GitHubActionsInfoFixture.ActionResultsUrl + "twirp/github.actions.results.api.v1.ArtifactService/"; private const string CreateArtifactUrl = ArtifactUrl + "CreateArtifact"; private const string FinalizeArtifactUrl = ArtifactUrl + "FinalizeArtifact"; - private const string GetSignedArtifactURLurl = ArtifactUrl + "GetSignedArtifactURL"; + private const string GetSignedArtifactURLUrl = ArtifactUrl + "GetSignedArtifactURL"; + private const string ListArtifactsUrl = ArtifactUrl + "ListArtifacts"; private const string UploadFileUrl = "https://cake.build.net/actions-results/a9d82106-d5d5-4310-8f60-0bfac035cf02/workflow-job-run-1d849a45-2f30-5fbb-3226-b730a17a93af/artifacts/91e64594182918fa8012cdbf7d1a4f801fa0c35f485c3277268aad8e3f45377c.zip?sig=upload"; private const string DownloadFileUrl = "https://cake.build.net/actions-results/a9d82106-d5d5-4310-8f60-0bfac035cf02/workflow-job-run-1d849a45-2f30-5fbb-3226-b730a17a93af/artifacts/91e64594182918fa8012cdbf7d1a4f801fa0c35f485c3277268aad8e3f45377c.zip?sig=download"; - private const string CreateArtifactResponse = @"{ - ""ok"": true, - ""signed_upload_url"": """ + UploadFileUrl + @""" -}"; - private const string FinalizeArtifactResponse = @"{ - ""ok"": true, - ""artifact_id"": ""1991105334"" -}"; - private const string GetSignedArtifactURLResponse = @"{ - ""name"": ""artifact"", - ""signed_url"": """ + DownloadFileUrl + @""" -}"; + private const string CreateArtifactResponse = + $$""" + { + "ok": true, + "signed_upload_url": "{{UploadFileUrl}}" + } + """; + private const string FinalizeArtifactResponse = + """ + { + "ok": true, + "artifact_id": "1991105334" + } + """; + private const string GetSignedArtifactURLResponse = + $$""" + { + "name": "artifact", + "signed_url": "{{DownloadFileUrl}}" + } + """; + private const string ListArtifactsResponse = + $$""" + { + "artifacts": [ + { + "workflow_run_backend_id": "b9e28153-ca20-4b86-91dd-09e8f644efdf", + "workflow_job_run_backend_id": "1d849a45-2f30-5fbb-3226-b730a17a93af", + "database_id": "1", + "name": "artifact", + "created_at": "2024-11-09T21:53:00.7110204+00:00" + } + ] + } + """; private GitHubActionsInfoFixture GitHubActionsInfoFixture { get; } private ICakeEnvironment Environment { get; } @@ -122,7 +145,7 @@ protected override async Task SendAsync(HttpRequestMessage // Get Signed Artifact Url case { - RequestUri: { AbsoluteUri: GetSignedArtifactURLurl }, + RequestUri: { AbsoluteUri: GetSignedArtifactURLUrl }, Method: { Method: "POST" }, }: { @@ -189,6 +212,16 @@ protected override async Task SendAsync(HttpRequestMessage return Ok(); } + // List Artifacts + case + { + RequestUri: { AbsoluteUri: ListArtifactsUrl }, + Method: { Method: "POST" } + }: + { + return Ok(new StringContent(ListArtifactsResponse)); + } + // Download File case { diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GitHubActionsArtifactService.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GitHubActionsArtifactService.cs index dc686e6ace..ee0f5f6796 100644 --- a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GitHubActionsArtifactService.cs +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GitHubActionsArtifactService.cs @@ -30,16 +30,43 @@ internal record GitHubActionsArtifactService( private static readonly Uri CreateArtifactUrl = new Uri("CreateArtifact", UriKind.Relative); private static readonly Uri FinalizeArtifactUrl = new Uri("FinalizeArtifact", UriKind.Relative); private static readonly Uri GetSignedArtifactURLUrl = new Uri("GetSignedArtifactURL", UriKind.Relative); + private static readonly Uri ListArtifactsUrl = new Uri("ListArtifacts", UriKind.Relative); internal async Task DownloadArtifactFiles( string artifactName, DirectoryPath directoryPath) + { + var listArtifactsResponse = await ListArtifacts( + artifactName); + + if (listArtifactsResponse.Artifacts.FirstOrDefault(artifact => artifact.Name == artifactName) + is { WorkflowRunBackendId.Length: > 0 } and { WorkflowJobRunBackendId.Length: > 0 } artifact) + { + var signedArtifactURLResponse = await GetSignedArtifactURL(artifact.WorkflowRunBackendId, artifact.WorkflowJobRunBackendId, artifactName); + + await DownloadArtifact(signedArtifactURLResponse.SignedUrl, directoryPath); + } + else + { + throw new CakeException($"Artifact {artifactName} not found."); + } + } + + private async Task ListArtifacts( + string nameFilter = null, + long? idFilter = null) { GetWorkflowBackendIds(out var workflowRunBackendId, out var workflowJobRunBackendId); - var (_, signedUrl) = await GetSignedArtifactURL(workflowRunBackendId, workflowJobRunBackendId, artifactName); + var listArtifactsRequest = new ListArtifactsRequest( + workflowRunBackendId, + workflowJobRunBackendId, + nameFilter, + idFilter); - await DownloadArtifact(signedUrl, directoryPath); + return await PostArtifactService( + ListArtifactsUrl, + listArtifactsRequest); } private async Task DownloadArtifact(string signedUrl, DirectoryPath directoryPath) diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsRequest.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsRequest.cs new file mode 100644 index 0000000000..fe06c1fa52 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsRequest.cs @@ -0,0 +1,20 @@ +// 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.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record ListArtifactsRequest( + [property: JsonPropertyName("workflow_run_backend_id")] + string WorkflowRunBackendId, + [property: JsonPropertyName("workflow_job_run_backend_id")] + string WorkflowJobRunBackendId, + [property: JsonPropertyName("name_filter")] + string NameFilter = null, + [property: JsonPropertyName("id_filter")] + long? IdFilter = null); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsResponse.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsResponse.cs new file mode 100644 index 0000000000..28559b6f51 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsResponse.cs @@ -0,0 +1,27 @@ +// 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.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record ListArtifactsResponse( + [property: JsonPropertyName("artifacts")] + ListArtifactsResponse.MonolithArtifact[] Artifacts) + { + internal record MonolithArtifact( + [property: JsonPropertyName("workflow_run_backend_id")] + string WorkflowRunBackendId, + [property: JsonPropertyName("workflow_job_run_backend_id")] + string WorkflowJobRunBackendId, + [property: JsonPropertyName("database_id")] + string DatabaseId, + [property: JsonPropertyName("name")] + string Name, + [property: JsonPropertyName("created_at")] + DateTimeOffset CreatedAt); + } +} \ No newline at end of file diff --git a/tests/integration/Cake.Common/Build/GitHubActions/GitHubActionsProvider.cake b/tests/integration/Cake.Common/Build/GitHubActions/GitHubActionsProvider.cake index 421ddc7f08..f6a5b9ab90 100644 --- a/tests/integration/Cake.Common/Build/GitHubActions/GitHubActionsProvider.cake +++ b/tests/integration/Cake.Common/Build/GitHubActions/GitHubActionsProvider.cake @@ -137,6 +137,21 @@ Task("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact") Assert.True(FileHashEquals(data.AssemblyPath, targetArtifactPath), $"{data.AssemblyPath.FullPath}=={targetArtifactPath.FullPath}"); }); +Task("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact.PreviousJob") + .Does(async () => { + // Given + var targetPath = Paths.Temp.Combine("./Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact.PreviousJob"); + EnsureDirectoryExists(targetPath); + var targetArtifactPath = targetPath.CombineWithFilePath("cake-integration-tests.txt"); + + // When + await GitHubActions.Commands.DownloadArtifact("cake-integration-tests", targetPath); + + // Then + Assert.True(System.IO.File.Exists(targetArtifactPath.FullPath), $"{targetArtifactPath.FullPath} Missing"); + Assert.Equal("Cake Integration Tests\n", System.IO.File.ReadAllText(targetArtifactPath.FullPath)); +}); + Task("Cake.Common.Build.GitHubActionsProvider.Environment.Runner.Architecture") .Does(() => { // Given / When @@ -193,7 +208,8 @@ if (GitHubActions.Environment.Runtime.IsRuntimeAvailable) gitHubActionsProviderTask .IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.UploadArtifact.File") .IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.UploadArtifact.Directory") - .IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact"); + .IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact") + .IsDependentOn("Cake.Common.Build.GitHubActionsProvider.Commands.DownloadArtifact.PreviousJob"); } public class GitHubActionsData From ccab6e2a112c89d0511f6ea1d5c8a5e7fdfb3279 Mon Sep 17 00:00:00 2001 From: Mattias Karlsson Date: Tue, 12 Nov 2024 22:51:50 +0100 Subject: [PATCH 7/9] (GH-4382) Update to Stable versions of dependencies * fixes #4382 --- global.json | 2 +- src/Directory.Packages.props | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/global.json b/global.json index df75a38afb..37a123145a 100644 --- a/global.json +++ b/global.json @@ -3,7 +3,7 @@ "src" ], "sdk": { - "version": "9.0.100-rc.2.24474.11", + "version": "9.0.100", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 95d25c1505..c42b6bda6e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -13,8 +13,8 @@ - - + + @@ -30,14 +30,14 @@ - - - + + + - + From 1d40c7c3c84db87d08f76307ee0ad878e73051d8 Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Thu, 7 Nov 2024 18:21:50 +0200 Subject: [PATCH 8/9] 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 9/9] 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());