diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b6cadaf67b..7bd5f9f561 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "2.1.0", + "version": "2.2.0", "commands": [ "dotnet-cake" ] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84aa424a94..e5af5a4d4b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-latest, ubuntu-18.04, macos-latest] + os: [windows-latest, ubuntu-18.04, macos-latest, ubuntu-22.04] steps: - name: Get the sources uses: actions/checkout@v2 diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 3695d197c3..0c186671e8 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,3 +1,33 @@ +### New in 2.3.0 (Released 2022/10/14) + +* 3947 Easier Way to Read Process Output?. +* 3916 GitVersion: Add ShortSha property. +* 3487 Add alias for dotnet workload update command. +* 3486 Add alias for dotnet workload uninstall command. +* 3484 Add alias for dotnet workload restore command. +* 3483 Add alias for dotnet workload repair command. +* 3482 Add alias for dotnet workload list command. +* 3978 Microsoft.Extensions.DependencyInjection to 6.0.1. +* 3976 Update NuGet.* to 6.3.1. +* 3970 Update Basic.Reference.Assemblies.* to 1.3.0. +* 3965 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.3.1. +* 3956 Extensibility issue - CakeEngineActions is internal. +* 3933 Update NuGet.* to 6.3.0. +* 3920 Update Microsoft.NETCore.Platforms to 6.0.5. +* 3909 Update Autofac to 6.4.0. +* 3901 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.2.0. +* 3899 Microsoft.NETCore.Platforms to 6.0.4. +* 3897 Update NuGet.* to 6.2.1. +* 3890 Update NuGet.* to 6.2.0. +* 3880 Better support global script cache. +* 2953 Allow setting MSBuild target via MSBuildSettings using a string. +* 2591 Extensibility issue - CakeTaskBuilder is sealed and CakeTaskBuilder(CakeTask task) is internal. . +* 3931 Cake fails to load native libraries on Ubuntu 22.04. +* 3894 Guard against null Console instance on InfoFeature. +* 3879 Build script caching throws after running dry-run on non-changed Cake script. +* 3878 OpenCover filters should be case sensitive. +* 1852 Incorrect escaping of semi-colon in property values for MS Build. + ### New in 2.2.0 (Released 2022/04/15) * 3821 PostAction is not setable on DotNetSettings. diff --git a/build.cake b/build.cake index 7a4870bcc8..90caf16368 100644 --- a/build.cake +++ b/build.cake @@ -3,7 +3,7 @@ #addin "nuget:https://api.nuget.org/v3/index.json?package=Cake.Gitter&version=2.0.0" // Install .NET Core Global tools. -#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitVersion.Tool&version=5.8.1" +#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitVersion.Tool&version=5.10.3" #tool "dotnet:https://api.nuget.org/v3/index.json?package=SignClient&version=1.3.155" #tool "dotnet:https://api.nuget.org/v3/index.json?package=GitReleaseManager.Tool&version=0.12.1" diff --git a/global.json b/global.json index 65c27ed7be..8b559ba7ba 100644 --- a/global.json +++ b/global.json @@ -3,7 +3,7 @@ "src" ], "sdk": { - "version": "6.0.202", + "version": "6.0.402", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/src/Cake.Cli/Cake.Cli.csproj b/src/Cake.Cli/Cake.Cli.csproj index f517071b83..b9322fea01 100644 --- a/src/Cake.Cli/Cake.Cli.csproj +++ b/src/Cake.Cli/Cake.Cli.csproj @@ -18,7 +18,7 @@ - + \ No newline at end of file diff --git a/src/Cake.Cli/Features/InfoFeature.cs b/src/Cake.Cli/Features/InfoFeature.cs index 70b0abacb4..5e4989f56d 100644 --- a/src/Cake.Cli/Features/InfoFeature.cs +++ b/src/Cake.Cli/Features/InfoFeature.cs @@ -2,6 +2,7 @@ // 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; namespace Cake.Cli @@ -37,6 +38,11 @@ public InfoFeature(IVersionResolver resolver) /// public void Run(IConsole console) { + if (console is null) + { + throw new ArgumentNullException(nameof(console)); + } + var version = _resolver.GetVersion(); var product = _resolver.GetProductVersion(); diff --git a/src/Cake.Common.Tests/Cake.Common.Tests.csproj b/src/Cake.Common.Tests/Cake.Common.Tests.csproj index 60e0e0a249..cccb157400 100644 --- a/src/Cake.Common.Tests/Cake.Common.Tests.csproj +++ b/src/Cake.Common.Tests/Cake.Common.Tests.csproj @@ -16,14 +16,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerFixture.cs new file mode 100644 index 0000000000..39dbe064cb --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerFixture.cs @@ -0,0 +1,49 @@ +// 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.Command; +using Cake.Core.IO; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools.Command +{ + internal class CommandRunnerFixture : ToolFixture + { + public ProcessArgumentBuilder Arguments { get; set; } + + public string ToolName + { + get => Settings.ToolName; + set => Settings.ToolName = value; + } + + public ICollection ToolExecutableNames + { + get => Settings.ToolExecutableNames; + set => Settings.ToolExecutableNames = value; + } + + public CommandRunnerFixture() + : base("dotnet.exe") + { + Arguments = new ProcessArgumentBuilder(); + Settings.ToolName = "dotnet"; + Settings.ToolExecutableNames = new[] { "dotnet.exe", "dotnet" }; + } + + protected override void RunTool() + { + GetRunner().RunCommand(Arguments); + } + + protected CommandRunner GetRunner() + => new CommandRunner( + Settings, + FileSystem, + Environment, + ProcessRunner, + Tools); + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardErrorFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardErrorFixture.cs new file mode 100644 index 0000000000..15944337cd --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardErrorFixture.cs @@ -0,0 +1,18 @@ +// 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.Tests.Fixtures.Tools.Command +{ + internal class CommandRunnerStandardErrorFixture : CommandRunnerStandardOutputFixture + { + public string StandardError { get; private set; } + + protected override void RunTool() + { + ExitCode = GetRunner().RunCommand(Arguments, out var standardOutput, out var standardError); + StandardOutput = standardOutput; + StandardError = standardError; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutFixture.cs new file mode 100644 index 0000000000..048501dbbd --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutFixture.cs @@ -0,0 +1,18 @@ +// 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.Tests.Fixtures.Tools.Command +{ + internal class CommandRunnerStandardOutputFixture : CommandRunnerFixture + { + public int ExitCode { get; protected set; } + public string StandardOutput { get; protected set; } + + protected override void RunTool() + { + ExitCode = GetRunner().RunCommand(Arguments, out var standardOutput); + StandardOutput = standardOutput; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutputFixtureExtentions.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutputFixtureExtentions.cs new file mode 100644 index 0000000000..edf6cdb1f4 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutputFixtureExtentions.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. + +namespace Cake.Common.Tests.Fixtures.Tools.Command +{ + internal static class CommandRunnerStandardOutputFixtureExtentions + { + public static T GivenStandardOutput(this T fixture, params string[] standardOutput) + where T : CommandRunnerStandardOutputFixture + { + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + return fixture; + } + + public static T GivenStandardError(this T fixture, params string[] standardError) + where T : CommandRunnerStandardOutputFixture + { + fixture.ProcessRunner.Process.SetStandardError(standardError); + return fixture; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Install/DotNetWorkloadInstallerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Install/DotNetWorkloadInstallerFixture.cs new file mode 100644 index 0000000000..8a541e9c05 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Install/DotNetWorkloadInstallerFixture.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.Collections.Generic; +using Cake.Common.Tools.DotNet.Workload.Install; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Install +{ + internal sealed class DotNetWorkloadInstallerFixture : DotNetFixture + { + public IEnumerable WorkloadIds { get; set; } + + protected override void RunTool() + { + var tool = new DotNetWorkloadInstaller(FileSystem, Environment, ProcessRunner, Tools); + tool.Install(WorkloadIds, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/List/DotNetWorkloadListerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/List/DotNetWorkloadListerFixture.cs new file mode 100644 index 0000000000..241002d69e --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/List/DotNetWorkloadListerFixture.cs @@ -0,0 +1,45 @@ +// 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.Workload.List; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.List +{ + internal sealed class DotNetWorkloadListerFixture : DotNetFixture + { + public IEnumerable Workloads { get; set; } + + public void GivenInstalledWorkloadsResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Installed Workload Ids Manifest Version Installation Source", + "--------------------------------------------------------------------------------------", + "maui-ios 6.0.312/6.0.300 VS 17.3.32804.467, VS 17.4.32804.182", + "maui-windows 6.0.312/6.0.300 VS 17.3.32804.467, VS 17.4.32804.182", + "android 32.0.301/6.0.300 VS 17.3.32804.467, VS 17.4.32804.182", + "", + "Use `dotnet workload search` to find additional workloads to install." + }); + } + + public void GivenEmptyInstalledWorkloadsResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Installed Workload Ids Manifest Version Installation Source", + "---------------------------------------------------------------------", + "", + "Use `dotnet workload search` to find additional workloads to install." + }); + } + + protected override void RunTool() + { + var tool = new DotNetWorkloadLister(FileSystem, Environment, ProcessRunner, Tools); + Workloads = tool.List(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairerFixture.cs new file mode 100644 index 0000000000..86d25af11c --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairerFixture.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. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Workload.Repair; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Repair +{ + internal sealed class DotNetWorkloadRepairerFixture : DotNetFixture + { + protected override void RunTool() + { + var tool = new DotNetWorkloadRepairer(FileSystem, Environment, ProcessRunner, Tools); + tool.Repair(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorerFixture.cs new file mode 100644 index 0000000000..4e8ef88acc --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorerFixture.cs @@ -0,0 +1,19 @@ +// 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.Workload.Restore; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Restore +{ + internal sealed class DotNetWorkloadRestorerFixture : DotNetFixture + { + public string Project { get; set; } + + protected override void RunTool() + { + var tool = new DotNetWorkloadRestorer(FileSystem, Environment, ProcessRunner, Tools); + tool.Restore(Project, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallerFixture.cs new file mode 100644 index 0000000000..745b70853b --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallerFixture.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.Collections.Generic; +using Cake.Common.Tools.DotNet.Workload.Uninstall; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Uninstall +{ + internal sealed class DotNetWorkloadUninstallerFixture : DotNetFixture + { + public IEnumerable WorkloadIds { get; set; } + + protected override void RunTool() + { + var tool = new DotNetWorkloadUninstaller(FileSystem, Environment, ProcessRunner, Tools); + tool.Uninstall(WorkloadIds); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Update/DotNetWorkloadUpdaterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Update/DotNetWorkloadUpdaterFixture.cs new file mode 100644 index 0000000000..3dc3f1cec3 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Update/DotNetWorkloadUpdaterFixture.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. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Workload.Update; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Update +{ + internal sealed class DotNetWorkloadUpdaterFixture : DotNetFixture + { + protected override void RunTool() + { + var tool = new DotNetWorkloadUpdater(FileSystem, Environment, ProcessRunner, Tools); + tool.Update(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/Command/CommandRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Command/CommandRunnerTests.cs new file mode 100644 index 0000000000..58c423a434 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/Command/CommandRunnerTests.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. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools.Command; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.Command +{ + public sealed class CommandRunnerTests + { + public sealed class TheRunCommandMethod + { + [Fact] + public void Should_Throw_If_Arguments_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + Arguments = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "arguments"); + } + + [Fact] + public void Should_Throw_If_Settings_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + Settings = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_ToolName_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + ToolName = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "ToolName"); + } + } + + [Fact] + public void Should_Throw_If_ToolExecutableNames_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + ToolExecutableNames = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "ToolExecutableNames"); + } + + [Fact] + public void Should_Throw_If_ToolExecutableNames_Was_Empty() + { + // Given + var fixture = new CommandRunnerFixture + { + ToolExecutableNames = Array.Empty() + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "ToolExecutableNames"); + } + + [Fact] + public void Should_Call_Settings_PostAction() + { + // Given + var called = false; + var fixture = new CommandRunnerFixture + { + Settings = { PostAction = _ => called = true } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.True(called, "Settings PostAction not called"); + } + + [Fact] + public void Should_Return_StandardOutput() + { + // Given + const string expectedStandardOutput = "LINE1"; + const int expectedExitCode = 0; + + var fixture = new CommandRunnerStandardOutputFixture() + .GivenStandardOutput(expectedStandardOutput); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + + [Fact] + public void Should_Return_StandardOutput_ExitCode() + { + // Given + const string expectedStandardOutput = "LINE1"; + const int expectedExitCode = 1337; + + var fixture = new CommandRunnerStandardOutputFixture + { + Settings = + { + HandleExitCode = exitCode => exitCode == expectedExitCode + } + } + .GivenStandardOutput(expectedStandardOutput); + + fixture.ProcessRunner.Process.SetExitCode(expectedExitCode); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + + [Fact] + public void Should_Return_StandardError() + { + // Given + const string expectedStandardOutput = "LINE1"; + const string expectedStandardError = "ERRORLINE1"; + const int expectedExitCode = 0; + + var fixture = new CommandRunnerStandardErrorFixture() + .GivenStandardError(expectedStandardError) + .GivenStandardOutput(expectedStandardOutput); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedStandardError, fixture.StandardError); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + + [Fact] + public void Should_Return_StandardError_ExitCode() + { + // Given + const string expectedStandardOutput = "LINE1"; + const string expectedStandardError = "ERRORLINE1"; + const int expectedExitCode = 1337; + + var fixture = new CommandRunnerStandardErrorFixture + { + Settings = + { + HandleExitCode = exitCode => exitCode == expectedExitCode + } + } + .GivenStandardError(expectedStandardError) + .GivenStandardOutput(expectedStandardOutput); + + fixture.ProcessRunner.Process.SetExitCode(expectedExitCode); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedStandardError, fixture.StandardError); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Install/DotNetWorkloadInstallTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Install/DotNetWorkloadInstallTests.cs new file mode 100644 index 0000000000..fe2a1c6061 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Install/DotNetWorkloadInstallTests.cs @@ -0,0 +1,131 @@ +// 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.Workload.Install; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Install +{ + public sealed class DotNetWorkloadInstallTests + { + public sealed class TheWorkloadInstallMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + 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 DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + 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_Add_WorkloadIds_Argument() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui-android", "maui-ios" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload install maui-android maui-ios", result.Args); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Empty() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Null() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.SkipManifestUpdate = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = Common.Tools.DotNet.DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload install maui --configfile \"/Working/nuget.config\" --disable-parallel --ignore-failed-sources --include-previews --interactive --no-cache --skip-manifest-update"; + expected += " --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/List/DotNetWorkloadListTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/List/DotNetWorkloadListTests.cs new file mode 100644 index 0000000000..4dd79f8dbd --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/List/DotNetWorkloadListTests.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.Workload.List; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.List +{ + public sealed class DotNetWorkloadListTests + { + public sealed class TheWorkloadListMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + 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 DotNetWorkloadListerFixture(); + 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 DotNetWorkloadListerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Verbosity_Argument() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.Settings.Verbosity = Common.Tools.DotNet.DotNetVerbosity.Normal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload list --verbosity normal", result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_Workloads() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.GivenInstalledWorkloadsResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.Workloads, + item => + { + Assert.Equal(item.Id, "maui-ios"); + Assert.Equal(item.ManifestVersion, "6.0.312/6.0.300"); + Assert.Equal(item.InstallationSource, "VS 17.3.32804.467, VS 17.4.32804.182"); + }, + item => + { + Assert.Equal(item.Id, "maui-windows"); + Assert.Equal(item.ManifestVersion, "6.0.312/6.0.300"); + Assert.Equal(item.InstallationSource, "VS 17.3.32804.467, VS 17.4.32804.182"); + }, + item => + { + Assert.Equal(item.Id, "android"); + Assert.Equal(item.ManifestVersion, "32.0.301/6.0.300"); + Assert.Equal(item.InstallationSource, "VS 17.3.32804.467, VS 17.4.32804.182"); + }); + } + + [Fact] + public void Should_Return_Empty_List_Of_Workloads() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.GivenEmptyInstalledWorkloadsResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Empty(fixture.Workloads); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairTests.cs new file mode 100644 index 0000000000..bbd4efda00 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairTests.cs @@ -0,0 +1,85 @@ +// 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.Workload.Repair; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Repair +{ + public sealed class DotNetWorkloadRepairTests + { + public sealed class TheWorkloadRepairMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadRepairerFixture(); + 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 DotNetWorkloadRepairerFixture(); + 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 DotNetWorkloadRepairerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadRepairerFixture(); + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload repair --configfile \"/Working/nuget.config\" --disable-parallel --ignore-failed-sources --include-previews --interactive --no-cache"; + expected += " --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreTests.cs new file mode 100644 index 0000000000..492be031a4 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreTests.cs @@ -0,0 +1,151 @@ +// 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.Workload.Restore; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Workload.Restore; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Restore +{ + public sealed class DotNetWorkloadRestoreTests + { + public sealed class TheBuildMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + 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 DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + 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 DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Project_Is_Null() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Settings = new DotNetWorkloadRestoreSettings(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "project"); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload restore \"./src/*\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.SkipManifestUpdate = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload restore \"./src/*\" --configfile \"/Working/nuget.config\" --disable-parallel --ignore-failed-sources --include-previews --interactive --no-cache --skip-manifest-update --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./src/*", "workload restore \"./src/*\"")] + [InlineData("./src/cake build/", "workload restore \"./src/cake build/\"")] + [InlineData("./src/cake build/cake cli", "workload restore \"./src/cake build/cake cli\"")] + public void Should_Quote_Project_Path(string text, string expected) + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics workload restore \"./src/*\"", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallTests.cs new file mode 100644 index 0000000000..a1f31ae00d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallTests.cs @@ -0,0 +1,88 @@ +// 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.Workload.Uninstall; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Uninstall +{ + public sealed class DotNetWorkloadUninstallTests + { + public sealed class TheWorkloadSearchMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + 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 DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + 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_Add_WorkloadIds_Argument() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { "maui-android", "maui-ios" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload uninstall maui-android maui-ios", result.Args); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Empty() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Null() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateTests.cs new file mode 100644 index 0000000000..764bbea4a3 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateTests.cs @@ -0,0 +1,86 @@ +// 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.Workload.Update; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Update +{ + public sealed class DotNetWorkloadUpdateTests + { + public sealed class TheWorkloadUpdateMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadUpdaterFixture(); + 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 DotNetWorkloadUpdaterFixture(); + 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 DotNetWorkloadUpdaterFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadUpdaterFixture(); + fixture.Settings.AdvertisingManifestsOnly = true; + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.FromPreviousSdk = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload update --advertising-manifests-only --configfile \"/Working/nuget.config\" --disable-parallel --from-previous-sdk --ignore-failed-sources --include-previews --interactive --no-cache --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/MSBuild/DotNetCoreMSBuildBuilderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/MSBuild/DotNetCoreMSBuildBuilderTests.cs index 870a0dfd0a..73f2823d07 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/MSBuild/DotNetCoreMSBuildBuilderTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/MSBuild/DotNetCoreMSBuildBuilderTests.cs @@ -185,7 +185,7 @@ public void Should_Add_Multiple_Property_Arguments_When_Multiple_Values() var result = fixture.Run(); // Then - Assert.Equal("msbuild /property:A=B /property:A=E /property:C=D", result.Args); + Assert.Equal("msbuild /property:A=B;E /property:C=D", result.Args); } [Theory] diff --git a/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs index bc31ab6428..a2e9f7acb0 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs @@ -284,6 +284,7 @@ public void Should_Tolerate_Bad_Json_Set() InformationalVersion = "0.1.1+Branch.master.Sha.f2467748c78b3c8b37972ad0b30df2e15dfbf2cb", BranchName = "master", Sha = "f2467748c78b3c8b37972ad0b30df2e15dfbf2cb", + ShortSha = "f2467748", NuGetVersionV2 = "0.1.1", NuGetVersion = "0.1.1", CommitsSinceVersionSource = null, @@ -315,6 +316,7 @@ public void Should_Tolerate_Bad_Json_Set() " \"InformationalVersion\":\"0.1.1+Branch.master.Sha.f2467748c78b3c8b37972ad0b30df2e15dfbf2cb\",", " \"BranchName\":\"master\",", " \"Sha\":\"f2467748c78b3c8b37972ad0b30df2e15dfbf2cb\",", + " \"ShortSha\":\"f2467748\",", " \"NuGetVersionV2\":\"0.1.1\",", " \"NuGetVersion\":\"0.1.1\",", " \"CommitsSinceVersionSource\":\"\",", @@ -348,6 +350,7 @@ public void Should_Tolerate_Bad_Json_Set() Assert.Equal(expect.InformationalVersion, result.InformationalVersion); Assert.Equal(expect.BranchName, result.BranchName); Assert.Equal(expect.Sha, result.Sha); + Assert.Equal(expect.ShortSha, result.ShortSha); Assert.Equal(expect.NuGetVersionV2, result.NuGetVersionV2); Assert.Equal(expect.NuGetVersion, result.NuGetVersion); Assert.Equal(expect.CommitsSinceVersionSource, result.CommitsSinceVersionSource); diff --git a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs index 7481e95f7a..87432bfd71 100644 --- a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs @@ -777,7 +777,22 @@ public void Should_Use_No_Logo_If_Specified() } [Fact] - public void Should_Append_Targets_To_Process_Arguments() + public void Should_Add_Single_Target_With_AddTarget() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithTarget("A"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_Multiple_Targets_With_AddTarget() { // Given var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); @@ -792,6 +807,149 @@ public void Should_Append_Targets_To_Process_Arguments() "\"C:/Working/src/Solution.sln\"", result.Args); } + [Fact] + public void Should_Not_Add_Duplicate_Target_With_AddTarget() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithTarget("A"); + fixture.Settings.WithTarget("A"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_Single_Target_With_Initializer() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A", + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Remove_Existing_Targets_When_Set_After_Initializer() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A", + } + }; + + fixture.Settings.Target = "B"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_Multiple_Targets_With_Initializer() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A;B", + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A;B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_Multiple_Targets_With_Initializer_And_AddTarget() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A;B", + } + }; + + fixture.Settings.WithTarget("C"); + fixture.Settings.WithTarget("D"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A;B;C;D " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Not_Add_Duplicate_Target_With_Initializer() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A", + } + }; + + fixture.Settings.Target = "A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Remove_Whitespace_From_Targets_With_Initializer() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = " A ; B ;;", + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A;B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + [Fact] public void Should_Append_Property_To_Process_Arguments() { @@ -820,7 +978,39 @@ public void Should_Append_Property_With_Multiple_Values_To_Process_Arguments() var result = fixture.Run(); // Then - Assert.Equal("/v:normal /p:A=B /p:A=E /p:C=D /target:Build " + + Assert.Equal("/v:normal /p:A=B;E /p:C=D /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Concatenate_Property_With_Multiple_Arguments_To_Process_Argument() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("A", "B", "E"); + fixture.Settings.WithProperty("C", "D", "F", "G"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:A=B;E /p:C=D;F;G /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Not_Escape_Semicolons_For_Specified_Property_Arguments_When_Appending_To_Process_Argument() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("DefineConstants", "A;B"); + fixture.Settings.WithProperty("A", "A;B"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:DefineConstants=A;B /p:A=A%3BB /target:Build " + "\"C:/Working/src/Solution.sln\"", result.Args); } diff --git a/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs b/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs index f73cc2218c..620203d88c 100644 --- a/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs @@ -144,6 +144,24 @@ public void Should_Append_Filters() "-register:user -output:\"/Working/result.xml\"", result.Args); } + [Fact] + public void Should_Append_CaseSensitive_Filters() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.Filters.Add("Value"); + fixture.Settings.Filters.Add("value"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-filter:\"Value value\" " + + "-register:user -output:\"/Working/result.xml\"", result.Args); + } + [Fact] public void Should_Append_Attribute_Filters() { diff --git a/src/Cake.Common/Tools/Command/CommandAliases.cs b/src/Cake.Common/Tools/Command/CommandAliases.cs new file mode 100644 index 0000000000..7f1f3fb069 --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandAliases.cs @@ -0,0 +1,458 @@ +// 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.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.Command +{ + /// + /// Contains generic functionality for simplifying the execution tools with no dedicated alias available yet. + /// + [CakeAliasCategory("Command")] + public static class CommandAliases + { + /// + /// Executes a generic tool/process based on arguments and settings. + /// + /// The context. + /// The tool executable names. + /// The optional arguments. + /// The expected exit code (default 0). + /// The optional settings customization (default null). + /// Thrown if or is null or empty. + /// + /// + /// // Example with ProcessArgumentBuilder + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// Command( + /// new []{ "dpi", "dpi.exe"}, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "TABLE") + /// .Append("analyze") + /// ); + /// + /// + /// // Example with implicit ProcessArgumentBuilder + /// Command( + /// new []{ "dotnet", "dotnet.exe"}, + /// "--version" + /// ); + /// + /// + /// // Example specify expected exit code + /// Command( + /// new []{ "dotnet", "dotnet.exe"}, + /// expectedExitCode: -2147450751 + /// ); + /// + /// + /// // Example settings customization + /// Command( + /// new []{ "dotnet", "dotnet.exe"}, + /// settingsCustomization: settings => settings + /// .WithToolName(".NET tool") + /// .WithExpectedExitCode(1) + /// .WithArgumentCustomization(args => args.Append("tool")) + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static void Command( + this ICakeContext context, + ICollection toolExecutableNames, + ProcessArgumentBuilder arguments = null, + int expectedExitCode = 0, + Func settingsCustomization = null) + => context.Command( + GetSettings(toolExecutableNames, expectedExitCode, settingsCustomization), + arguments); + + /// + /// Executes a generic command based on arguments and settings. + /// + /// The context. + /// The settings. + /// The optional arguments. + /// Thrown if or is null. + /// + /// + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// // Reusable tools settings i.e. created in setup. + /// var settings = new CommandSettings { + /// ToolName = "DPI", + /// ToolExecutableNames = new []{ "dpi", "dpi.exe"}, + /// }; + /// + /// // Example with ProcessArgumentBuilder + /// Command( + /// settings, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "TABLE") + /// .Append("analyze") + /// ); + /// + /// // Example with implicit ProcessArgumentBuilder + /// Command( + /// settings, + /// $"nuget --output TABLE analyze" + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static void Command( + this ICakeContext context, + CommandSettings settings, + ProcessArgumentBuilder arguments = null) + { + var runner = GetRunner(context, settings, ref arguments); + + runner.RunCommand(arguments); + } + + /// + /// Executes a generic tool/process based on arguments, tool executable names and redirects standard output. + /// + /// The context. + /// The tool executable names. + /// The standard output. + /// The optional arguments. + /// The expected exit code (default 0). + /// The optional settings customization. + /// Thrown if , or is null. + /// The exit code. + /// + /// + /// using System.Text.Json.Serialization; + /// using System.Text.Json; + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// new []{ "dpi", "dpi.exe"}, + /// out var standardOutput, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "JSON") + /// .Append("analyze") + /// ); + /// + /// var packageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// standardOutput + /// ); + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// new []{ "dpi", "dpi.exe"}, + /// out var implicitStandardOutput, + /// $"nuget --output JSON analyze" + /// ); + /// + /// var implicitPackageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// implicitStandardOutput + /// ); + /// + /// // Example settings customization + /// var settingsCustomizationExitCode = Command( + /// new []{ "dpi", "dpi.exe"}, + /// out var settingsCustomizationStandardOutput, + /// $"nuget --output JSON analyze", + /// settingsCustomization: settings => settings + /// .WithToolName("DPI") + /// .WithArgumentCustomization(args => args.AppendSwitchQuoted("--buildversion", " ", "1.0.0")) + /// ); + /// + /// var settingsCustomizationPackageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// settingsCustomizationStandardOutput + /// ); + /// + /// // Record used in example above + /// public record DPIPackageReference( + /// [property: JsonPropertyName("source")] + /// string Source, + /// [property: JsonPropertyName("sourceType")] + /// string SourceType, + /// [property: JsonPropertyName("packageId")] + /// string PackageId, + /// [property: JsonPropertyName("version")] + /// string Version + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + ICollection toolExecutableNames, + out string standardOutput, + ProcessArgumentBuilder arguments = null, + int expectedExitCode = 0, + Func settingsCustomization = null) + => context.Command( + GetSettings(toolExecutableNames, expectedExitCode, settingsCustomization), + out standardOutput, + arguments); + + /// + /// Executes a generic tool/process based on arguments, settings and redirects standard output. + /// + /// The context. + /// The settings. + /// The standard output. + /// The optional arguments. + /// Thrown if or is null. + /// The exit code. + /// + /// + /// using System.Text.Json.Serialization; + /// using System.Text.Json; + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// // Reusable tools settings i.e. created in setup. + /// var settings = new CommandSettings { + /// ToolName = "DPI", + /// ToolExecutableNames = new []{ "dpi", "dpi.exe" }, + /// }; + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// settings, + /// out var standardOutput, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "JSON") + /// .Append("analyze") + /// ); + /// + /// var packageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// standardOutput + /// ); + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// settings, + /// out var implicitStandardOutput, + /// $"nuget --output JSON analyze" + /// ); + /// + /// var implicitPackageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// implicitStandardOutput + /// ); + /// + /// // Record used in example above + /// public record DPIPackageReference( + /// [property: JsonPropertyName("source")] + /// string Source, + /// [property: JsonPropertyName("sourceType")] + /// string SourceType, + /// [property: JsonPropertyName("packageId")] + /// string PackageId, + /// [property: JsonPropertyName("version")] + /// string Version + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + CommandSettings settings, + out string standardOutput, + ProcessArgumentBuilder arguments = null) + { + var runner = GetRunner(context, settings, ref arguments); + + return runner.RunCommand(arguments, out standardOutput); + } + + /// + /// Executes a generic tool/process based on arguments, settings, redirects standard output and standard error. + /// + /// The context. + /// The tool executable names. + /// The standard output. + /// The standard error. + /// The optional arguments. + /// The expected exit code (default 0). + /// The optional settings customization (default null). + /// Thrown if or is null. + /// The exit code. + /// + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// new []{ "dotnet", "dotnet.exe" }, + /// out var standardOutput, + /// out var standardError, + /// new ProcessArgumentBuilder() + /// .Append("tool"), + /// expectedExitCode:1 + /// ); + /// + /// Verbose("Exit code: {0}", exitCode); + /// Information("Output: {0}", standardOutput); + /// Error("Error: {0}", standardError); + /// + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// new []{ "dotnet", "dotnet.exe" }, + /// out var implicitStandardOutput, + /// out var implicitStandardError, + /// "tool", + /// expectedExitCode:1 + /// ); + /// + /// Verbose("Exit code: {0}", implicitExitCode); + /// Information("Output: {0}", implicitStandardOutput); + /// Error("Error: {0}", implicitStandardError); + /// + /// + /// // Example settings customization + /// var settingsCustomizationExitCode = Command( + /// new []{ "dotnet", "dotnet.exe" }, + /// out var settingsCustomizationStandardOutput, + /// out var settingsCustomizationStandardError, + /// settingsCustomization: settings => settings + /// .WithToolName(".NET Tool") + /// .WithArgumentCustomization(args => args.Append("tool")) + /// .WithExpectedExitCode(1) + /// ); + /// + /// Verbose("Exit code: {0}", settingsCustomizationExitCode); + /// Information("Output: {0}", settingsCustomizationStandardOutput); + /// Error("Error: {0}", settingsCustomizationStandardError); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + ICollection toolExecutableNames, + out string standardOutput, + out string standardError, + ProcessArgumentBuilder arguments = null, + int expectedExitCode = 0, + Func settingsCustomization = null) + => context.Command( + GetSettings(toolExecutableNames, expectedExitCode, settingsCustomization), + out standardOutput, + out standardError, + arguments); + + /// + /// Executes a generic tool/process based on arguments and settings. + /// + /// The context. + /// The settings. + /// The standard output. + /// The standard error. + /// The optional arguments. + /// Thrown if or is null. + /// The exit code. + /// + /// + /// // Reusable tools settings i.e. created in setup. + /// var settings = new CommandSettings { + /// ToolName = ".NET CLI", + /// ToolExecutableNames = new []{ "dotnet", "dotnet.exe" }, + /// }.WithExpectedExitCode(1); + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// settings, + /// out var standardOutput, + /// out var standardError, + /// new ProcessArgumentBuilder() + /// .Append("tool") + /// ); + /// + /// Verbose("Exit code: {0}", exitCode); + /// Information("Output: {0}", standardOutput); + /// Error("Error: {0}", standardError); + /// + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// settings, + /// out var implicitStandardOutput, + /// out var implicitStandardError, + /// "tool" + /// ); + /// + /// Verbose("Exit code: {0}", implicitExitCode); + /// Information("Output: {0}", implicitStandardOutput); + /// Error("Error: {0}", implicitStandardError); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + CommandSettings settings, + out string standardOutput, + out string standardError, + ProcessArgumentBuilder arguments = null) + { + var runner = GetRunner(context, settings, ref arguments); + + return runner.RunCommand(arguments, out standardOutput, out standardError); + } + + private static CommandSettings GetSettings( + ICollection toolExecutableNames, + int expectedExitCode, + Func settingsCustomization) + { + if (toolExecutableNames is null || toolExecutableNames.Count < 1) + { + throw new ArgumentNullException(nameof(toolExecutableNames)); + } + + var settings = new CommandSettings + { + ToolName = toolExecutableNames.First(), + ToolExecutableNames = toolExecutableNames, + HandleExitCode = exitCode => exitCode == expectedExitCode + }; + + return settingsCustomization?.Invoke(settings) ?? settings; + } + + private static CommandRunner GetRunner(ICakeContext context, CommandSettings settings, ref ProcessArgumentBuilder arguments) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + arguments ??= new ProcessArgumentBuilder(); + + var runner = new CommandRunner( + settings, + context.FileSystem, + context.Environment, + context.ProcessRunner, + context.Tools); + + return runner; + } + } +} diff --git a/src/Cake.Common/Tools/Command/CommandRunner.cs b/src/Cake.Common/Tools/Command/CommandRunner.cs new file mode 100644 index 0000000000..0bb562ad6c --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandRunner.cs @@ -0,0 +1,134 @@ +// 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.Command +{ + /// + /// The generic command runner. + /// + public sealed class CommandRunner : Tool + { + private CommandSettings Settings { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The settings. + /// The file system. + /// The environment. + /// The process runner. + /// The globber. + public CommandRunner( + CommandSettings settings, + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + /// + protected override IEnumerable GetToolExecutableNames() + => Settings.ToolExecutableNames; + + /// + protected override string GetToolName() + => Settings.ToolName; + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + public void RunCommand(ProcessArgumentBuilder arguments) + => RunCommand(arguments, null, null); + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + /// The standard output. + /// The exit code. + public int RunCommand(ProcessArgumentBuilder arguments, out string standardOutput) + => RunCommand( + arguments, + out standardOutput, + out _, + new ProcessSettings + { + RedirectStandardOutput = true + }); + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + /// The standard output. + /// The standard error output. + /// The exit code. + public int RunCommand(ProcessArgumentBuilder arguments, out string standardOutput, out string standardError) + => RunCommand( + arguments, + out standardOutput, + out standardError, + new ProcessSettings + { + RedirectStandardOutput = true, + RedirectStandardError = true + }); + + private int RunCommand(ProcessArgumentBuilder arguments, out string standardOutput, out string standardError, ProcessSettings processSettings) + { + IEnumerable + standardOutputResult = null, standardErrorResult = null; + int returnCode = -1; + + RunCommand(arguments, processSettings, + process => + { + standardOutputResult = processSettings.RedirectStandardOutput ? process.GetStandardOutput() : Array.Empty(); + standardErrorResult = processSettings.RedirectStandardError ? process.GetStandardError() : Array.Empty(); + returnCode = process.GetExitCode(); + }); + + standardOutput = string.Join(Environment.NewLine, standardOutputResult); + standardError = string.Join(Environment.NewLine, standardErrorResult); + + return returnCode; + } + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + /// The process settings. + /// If specified called after process exit. + private void RunCommand(ProcessArgumentBuilder arguments, ProcessSettings processSettings, Action postAction) + { + if (arguments is null) + { + throw new ArgumentNullException(nameof(arguments)); + } + + if (string.IsNullOrWhiteSpace(Settings.ToolName)) + { + throw new ArgumentNullException(nameof(Settings.ToolName)); + } + + if (Settings.ToolExecutableNames == null || !Settings.ToolExecutableNames.Any()) + { + throw new ArgumentNullException(nameof(Settings.ToolExecutableNames)); + } + + Run(Settings, arguments, processSettings, postAction); + } + } +} diff --git a/src/Cake.Common/Tools/Command/CommandSettings.cs b/src/Cake.Common/Tools/Command/CommandSettings.cs new file mode 100644 index 0000000000..5d9129a06f --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandSettings.cs @@ -0,0 +1,26 @@ +// 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 Cake.Core.Tooling; + +namespace Cake.Common.Tools.Command +{ + /// + /// Contains settings used by . + /// + public class CommandSettings : ToolSettings + { + /// + /// Gets or sets the name of the tool. + /// + public virtual string ToolName { get; set; } + + /// + /// Gets or sets the tool executable names. + /// + public virtual ICollection ToolExecutableNames { get; set; } = Array.Empty(); + } +} diff --git a/src/Cake.Common/Tools/Command/CommandSettingsExtensions.cs b/src/Cake.Common/Tools/Command/CommandSettingsExtensions.cs new file mode 100644 index 0000000000..f28046bdb2 --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandSettingsExtensions.cs @@ -0,0 +1,36 @@ +// 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.Tooling; + +namespace Cake.Common.Tools.Command +{ + /// + /// Contains functionality related to . + /// + public static class CommandSettingsExtensions + { + /// + /// Sets the tool executable names. + /// + /// The tools settings. + /// The tool executable names. + /// The ToolSettings type. + /// The tools settings. + public static T WithExecutableNames(this T toolSettings, params string[] toolExecutableNames) + where T : CommandSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.ToolExecutableNames = toolExecutableNames); + + /// + /// Sets the tool name. + /// + /// The tools settings. + /// The tool name. + /// The ToolSettings type. + /// The tools settings. + public static T WithToolName(this T toolSettings, string toolName) + where T : CommandSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.ToolName = toolName); + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs index 0a84cb7bca..d1d06b9f0f 100644 --- a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs @@ -22,7 +22,13 @@ using Cake.Common.Tools.DotNet.Test; using Cake.Common.Tools.DotNet.Tool; using Cake.Common.Tools.DotNet.VSTest; +using Cake.Common.Tools.DotNet.Workload.Install; +using Cake.Common.Tools.DotNet.Workload.List; +using Cake.Common.Tools.DotNet.Workload.Repair; +using Cake.Common.Tools.DotNet.Workload.Restore; using Cake.Common.Tools.DotNet.Workload.Search; +using Cake.Common.Tools.DotNet.Workload.Uninstall; +using Cake.Common.Tools.DotNet.Workload.Update; using Cake.Common.Tools.DotNetCore.Build; using Cake.Common.Tools.DotNetCore.BuildServer; using Cake.Common.Tools.DotNetCore.Clean; @@ -180,7 +186,7 @@ public static void DotNetRestore(this ICakeContext context, string root) /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, /// PackagesDirectory = "./packages", - /// Verbosity = Information, + /// DotNetVerbosity.Information, /// DisableParallel = true, /// InferRuntimes = new[] {"runtime1", "runtime2"} /// }; @@ -209,7 +215,7 @@ public static void DotNetRestore(this ICakeContext context, DotNetRestoreSetting /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, /// PackagesDirectory = "./packages", - /// Verbosity = Information, + /// DotNetVerbosity.Information, /// DisableParallel = true, /// InferRuntimes = new[] {"runtime1", "runtime2"} /// }; @@ -1905,7 +1911,7 @@ public static IEnumerable DotNetWorkloadSearch(this ICakeContext /// /// var settings = new DotNetWorkloadSearchSettings /// { - /// Verbosity = Detailed + /// DotNetVerbosity.Detailed /// }; /// /// var workloads = DotNetWorkloadSearch("maui", settings); @@ -1934,5 +1940,365 @@ public static IEnumerable DotNetWorkloadSearch(this ICakeContext var searcher = new DotNetWorkloadSearcher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); return searcher.Search(searchString, settings); } + + /// + /// Uninstalls a specified workload. + /// + /// The context. + /// The workload ID to uninstall. + /// + /// + /// DotNetWorkloadUninstall("maui"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Uninstall")] + public static void DotNetWorkloadUninstall(this ICakeContext context, string workloadId) + { + context.DotNetWorkloadUninstall(new string[] { workloadId }); + } + + /// + /// Uninstalls one or more workloads. + /// + /// The context. + /// The workload ID or multiple IDs to uninstall. + /// + /// + /// DotNetWorkloadUninstall(new string[] { "maui", "maui-desktop", "maui-mobile" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Uninstall")] + public static void DotNetWorkloadUninstall(this ICakeContext context, IEnumerable workloadIds) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var uninstaller = new DotNetWorkloadUninstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + uninstaller.Uninstall(workloadIds); + } + + /// + /// Installs a specified optional workload. + /// + /// The context. + /// The workload ID to install. + /// + /// + /// DotNetWorkloadInstall("maui"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, string workloadId) + { + context.DotNetWorkloadInstall(workloadId, null); + } + + /// + /// Installs a specified optional workload. + /// + /// The context. + /// The workload ID to install. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadInstallSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadInstall("maui", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, string workloadId, DotNetWorkloadInstallSettings settings) + { + context.DotNetWorkloadInstall(new string[] { workloadId }, settings); + } + + /// + /// Installs one or more optional workloads. + /// + /// The context. + /// The workload ID or multiple IDs to install. + /// + /// + /// DotNetWorkloadInstall(new string[] { "maui", "maui-desktop", "maui-mobile" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, IEnumerable workloadIds) + { + context.DotNetWorkloadInstall(workloadIds, null); + } + + /// + /// Installs one or more optional workloads. + /// + /// The context. + /// The workload ID or multiple IDs to install. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadInstallSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadInstall(new string[] { "maui", "maui-desktop", "maui-mobile" }, settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, IEnumerable workloadIds, DotNetWorkloadInstallSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings == null) + { + settings = new DotNetWorkloadInstallSettings(); + } + + var installer = new DotNetWorkloadInstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + installer.Install(workloadIds, settings); + } + + /// + /// Lists all installed workloads. + /// + /// The context. + /// The list of installed workloads. + /// + /// + /// var workloadIds = DotNetWorkloadList(); + /// + /// foreach (var workloadId in workloadIds) + /// { + /// Information($"Installed Workload Id: {workloadId}"); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.List")] + public static IEnumerable DotNetWorkloadList(this ICakeContext context) + { + return context.DotNetWorkloadList(null); + } + + /// + /// Lists all installed workloads. + /// + /// The context. + /// The settings. + /// The list of installed workloads. + /// + /// + /// var settings = new DotNetWorkloadListSettings + /// { + /// Verbosity = DotNetVerbosity.Detailed + /// }; + /// + /// var workloads = DotNetWorkloadList(settings); + /// + /// foreach (var workload in workloads) + /// { + /// Information($"Installed Workload Id: {workload.Id}\t Manifest Version: {workload.ManifestVersion}\t Installation Source: {workload.InstallationSource}"); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.List")] + public static IEnumerable DotNetWorkloadList(this ICakeContext context, DotNetWorkloadListSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings == null) + { + settings = new DotNetWorkloadListSettings(); + } + + var lister = new DotNetWorkloadLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return lister.List(settings); + } + + /// + /// Repairs all workloads installations. + /// + /// The context. + /// + /// + /// DotNetWorkloadRepair(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Repair")] + public static void DotNetWorkloadRepair(this ICakeContext context) + { + context.DotNetWorkloadRepair(null); + } + + /// + /// Repairs all workloads installations. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadRepairSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadRepair(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Repair")] + public static void DotNetWorkloadRepair(this ICakeContext context, DotNetWorkloadRepairSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings == null) + { + settings = new DotNetWorkloadRepairSettings(); + } + + var repairer = new DotNetWorkloadRepairer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + repairer.Repair(settings); + } + + /// + /// Updates all installed workloads to the newest available version. + /// + /// The context. + /// + /// + /// DotNetWorkloadUpdate(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Update")] + public static void DotNetWorkloadUpdate(this ICakeContext context) + { + context.DotNetWorkloadUpdate(null); + } + + /// + /// Updates all installed workloads to the newest available version. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadUpdateSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadUpdate(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Update")] + public static void DotNetWorkloadUpdate(this ICakeContext context, DotNetWorkloadUpdateSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings == null) + { + settings = new DotNetWorkloadUpdateSettings(); + } + + var updater = new DotNetWorkloadUpdater(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + updater.Update(settings); + } + + /// + /// Installs workloads needed for a project or a solution. + /// + /// The context. + /// The project or solution file to install workloads for. + /// + /// + /// DotNetWorkloadRestore("./src/project"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Restore")] + public static void DotNetWorkloadRestore(this ICakeContext context, string project) + { + context.DotNetWorkloadRestore(project, null); + } + + /// + /// Installs workloads needed for a project or a solution. + /// + /// The context. + /// The project or solution file to install workloads for. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadRestoreSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadRestore("./src/project", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Restore")] + public static void DotNetWorkloadRestore(this ICakeContext context, string project, DotNetWorkloadRestoreSettings settings) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings is null) + { + settings = new DotNetWorkloadRestoreSettings(); + } + + var restorer = new DotNetWorkloadRestorer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + restorer.Restore(project, settings); + } } } diff --git a/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstallSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstallSettings.cs new file mode 100644 index 0000000000..480b30cdbe --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstallSettings.cs @@ -0,0 +1,63 @@ +// 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.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Install +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadInstallSettings : DotNetSettings + { + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { 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 do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether to skip updating the workload manifests. + /// The workload manifests define what assets and versions need to be installed for each workload. + /// + public bool SkipManifestUpdate { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstaller.cs b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstaller.cs new file mode 100644 index 0000000000..682d2f8299 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstaller.cs @@ -0,0 +1,128 @@ +// 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.Workload.Install +{ + /// + /// .NET workloads installer. + /// + public sealed class DotNetWorkloadInstaller : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadInstaller( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Lists the latest available version of the .NET SDK and .NET Runtime, for each feature band. + /// + /// The workload ID or multiple IDs to uninstall. + /// The settings. + public void Install(IEnumerable workloadIds, DotNetWorkloadInstallSettings settings) + { + if (workloadIds == null || !workloadIds.Any()) + { + throw new ArgumentNullException(nameof(workloadIds)); + } + + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + RunCommand(settings, GetArguments(workloadIds, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable workloadIds, DotNetWorkloadInstallSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload install"); + + if (workloadIds != null && workloadIds.Any()) + { + builder.Append(string.Join(" ", workloadIds)); + } + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Skip Manifest Update + if (settings.SkipManifestUpdate) + { + builder.Append("--skip-manifest-update"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListItem.cs b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListItem.cs new file mode 100644 index 0000000000..b572100154 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListItem.cs @@ -0,0 +1,40 @@ +// 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.Workload.List +{ + /// + /// An item as returned by . + /// + public sealed class DotNetWorkloadListItem + { + /// + /// Initializes a new instance of the class. + /// + /// The workload Id. + /// The workload manifest version. + /// The workload installation source. + public DotNetWorkloadListItem(string id, string manifestVersion, string installationSource) + { + Id = id; + ManifestVersion = manifestVersion; + InstallationSource = installationSource; + } + + /// + /// Gets the workload ID. + /// + public string Id { get; } + + /// + /// Gets the manifest version of the workload as string. + /// + public string ManifestVersion { get; } + + /// + /// Gets the installation source of the workload as string. + /// + public string InstallationSource { get; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListSettings.cs new file mode 100644 index 0000000000..daef0e0ea5 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListSettings.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.Workload.List +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadListSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadLister.cs b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadLister.cs new file mode 100644 index 0000000000..5b4471f3f6 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadLister.cs @@ -0,0 +1,104 @@ +// 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.Workload.List +{ + /// + /// .NET workloads lister. + /// + public sealed class DotNetWorkloadLister : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadLister( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Lists all installed workloads. + /// + /// The settings. + /// The list of installed workloads. + public IEnumerable List(DotNetWorkloadListSettings settings) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true + }; + + IEnumerable result = null; + RunCommand(settings, GetArguments(settings), processSettings, + process => result = process.GetStandardOutput()); + + return ParseResult(result).ToList(); + } + + private ProcessArgumentBuilder GetArguments(DotNetWorkloadListSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload list"); + + return builder; + } + + private static IEnumerable ParseResult(IEnumerable result) + { + bool first = true; + int manifestIndex = -1; + int sourceIndex = -1; + foreach (var line in result) + { + if (first) + { + if (line?.StartsWith("Installed Workload Ids") == true + && (manifestIndex = line?.IndexOf("Manifest Version") ?? -1) > 22 + && (sourceIndex = line?.IndexOf("Installation Source") ?? -1) > 39) + { + first = false; + } + continue; + } + + if (string.IsNullOrWhiteSpace(line)) + { + break; + } + + var trimmedLine = line.Trim(); + + if (trimmedLine.Trim().All(c => c == '-')) + { + continue; + } + + yield return new DotNetWorkloadListItem( + string.Concat(trimmedLine.Take(manifestIndex)).TrimEnd(), + string.Concat(trimmedLine.Take(sourceIndex).Skip(manifestIndex)).TrimEnd(), + string.Concat(trimmedLine.Skip(sourceIndex))); + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairSettings.cs new file mode 100644 index 0000000000..f78a0ed532 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairSettings.cs @@ -0,0 +1,57 @@ +// 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.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Repair +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadRepairSettings : DotNetSettings + { + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { 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 do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairer.cs b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairer.cs new file mode 100644 index 0000000000..0d8de40e4c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairer.cs @@ -0,0 +1,110 @@ +// 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.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Repair +{ + /// + /// .NET workloads installations repairer. + /// + public sealed class DotNetWorkloadRepairer : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadRepairer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Repairs all workloads installations. + /// + /// The settings. + public void Repair(DotNetWorkloadRepairSettings settings) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + RunCommand(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(DotNetWorkloadRepairSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload repair"); + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreSettings.cs new file mode 100644 index 0000000000..54aaffdd9a --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreSettings.cs @@ -0,0 +1,63 @@ +// 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.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Restore +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadRestoreSettings : DotNetSettings + { + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { 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 do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether to skip updating the workload manifests. + /// The workload manifests define what assets and versions need to be installed for each workload. + /// + public bool SkipManifestUpdate { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorer.cs b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorer.cs new file mode 100644 index 0000000000..65eda4cb38 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorer.cs @@ -0,0 +1,127 @@ +// 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.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Restore +{ + /// + /// .NET workloads restorer. + /// + public sealed class DotNetWorkloadRestorer : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadRestorer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Installs workloads needed for a project or a solution. + /// + /// The target project or solution file path. + /// The settings. + public void Restore(string project, DotNetWorkloadRestoreSettings settings) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + RunCommand(settings, GetArguments(project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, DotNetWorkloadRestoreSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload restore"); + + // Specific path? + if (project != null) + { + builder.AppendQuoted(project); + } + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Skip Manifest Update + if (settings.SkipManifestUpdate) + { + builder.Append("--skip-manifest-update"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallSettings.cs new file mode 100644 index 0000000000..f30a97d7a0 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallSettings.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.Workload.Uninstall +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadUninstallSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstaller.cs b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstaller.cs new file mode 100644 index 0000000000..5fe7c0d573 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstaller.cs @@ -0,0 +1,63 @@ +// 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.Workload.Uninstall +{ + /// + /// .NET workloads uninstaller. + /// + public sealed class DotNetWorkloadUninstaller : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadUninstaller( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Uninstalls one or more workloads. + /// + /// The workload ID or multiple IDs to uninstall. + public void Uninstall(IEnumerable workloadIds) + { + if (workloadIds == null || !workloadIds.Any()) + { + throw new ArgumentNullException(nameof(workloadIds)); + } + + var settings = new DotNetWorkloadUninstallSettings(); + RunCommand(settings, GetArguments(workloadIds, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable workloadIds, DotNetWorkloadUninstallSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload uninstall"); + + if (workloadIds != null && workloadIds.Any()) + { + builder.Append(string.Join(" ", workloadIds)); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateSettings.cs new file mode 100644 index 0000000000..b09e84e610 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateSettings.cs @@ -0,0 +1,67 @@ +// 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.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Update +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadUpdateSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating whether to downloads advertising manifests but doesn't update any workloads. + /// + public bool AdvertisingManifestsOnly { get; set; } + + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to include workloads installed with previous SDK versions in the update. + /// + public bool FromPreviousSdk { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { 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 do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdater.cs b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdater.cs new file mode 100644 index 0000000000..33c2e24260 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdater.cs @@ -0,0 +1,122 @@ +// 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.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Update +{ + /// + /// .NET workloads updater. + /// + public sealed class DotNetWorkloadUpdater : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadUpdater( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Updates all installed workloads to the newest available version. + /// + /// The settings. + public void Update(DotNetWorkloadUpdateSettings settings) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + RunCommand(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(DotNetWorkloadUpdateSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload update"); + + // Advertising Manifests Only + if (settings.AdvertisingManifestsOnly) + { + builder.Append("--advertising-manifests-only"); + } + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // From Previous SDK + if (settings.FromPreviousSdk) + { + builder.Append("--from-previous-sdk"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNetCore/MSBuild/MSBuildArgumentBuilderExtensions.cs b/src/Cake.Common/Tools/DotNetCore/MSBuild/MSBuildArgumentBuilderExtensions.cs index 54a6a51603..6754689645 100644 --- a/src/Cake.Common/Tools/DotNetCore/MSBuild/MSBuildArgumentBuilderExtensions.cs +++ b/src/Cake.Common/Tools/DotNetCore/MSBuild/MSBuildArgumentBuilderExtensions.cs @@ -46,15 +46,7 @@ public static void AppendMSBuildSettings(this ProcessArgumentBuilder builder, Do throw new ArgumentException("A property must have at least one non-empty value", nameof(settings.Properties)); } - foreach (var value in property.Value) - { - if (string.IsNullOrWhiteSpace(value)) - { - continue; - } - - builder.AppendMSBuildSwitch("property", $"{property.Key}={value.EscapeMSBuildPropertySpecialCharacters()}"); - } + builder.AppendMSBuildSwitch("property", $"{property.Key}={property.BuildMSBuildPropertyParameterString()}"); } // Set the maximum number of processors? diff --git a/src/Cake.Common/Tools/GitVersion/GitVersion.cs b/src/Cake.Common/Tools/GitVersion/GitVersion.cs index 9050d5a30e..0361694e63 100644 --- a/src/Cake.Common/Tools/GitVersion/GitVersion.cs +++ b/src/Cake.Common/Tools/GitVersion/GitVersion.cs @@ -109,6 +109,11 @@ public sealed class GitVersion /// public string Sha { get; set; } + /// + /// Gets or sets the shortened Git SHA. + /// + public string ShortSha { get; set; } + /// /// Gets or sets the NuGet version for v2. /// diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionInternal.cs b/src/Cake.Common/Tools/GitVersion/GitVersionInternal.cs index 1334e0dfcd..3bf9dc300c 100644 --- a/src/Cake.Common/Tools/GitVersion/GitVersionInternal.cs +++ b/src/Cake.Common/Tools/GitVersion/GitVersionInternal.cs @@ -154,6 +154,13 @@ public string Sha set => GitVersion.Sha = value; } + [DataMember] + public string ShortSha + { + get => GitVersion.ShortSha; + set => GitVersion.ShortSha = value; + } + [DataMember] public string NuGetVersionV2 { diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildPropertyExtensions.cs b/src/Cake.Common/Tools/MSBuild/MSBuildPropertyExtensions.cs index c570c18d2b..8bf965153d 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildPropertyExtensions.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildPropertyExtensions.cs @@ -2,11 +2,8 @@ // 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 System.Text; -using System.Threading.Tasks; namespace Cake.Common.Tools.MSBuild { @@ -21,7 +18,38 @@ internal static class MSBuildPropertyExtensions { '\n', "%0A" } }; - internal static string EscapeMSBuildPropertySpecialCharacters(this string value) + private static readonly HashSet _propertiesNotEscapeSemicolons = new HashSet + { + "DefineConstants", + "ExcludeFilesFromDeployment" + }; + + internal static string BuildMSBuildPropertyParameterString(this KeyValuePair property) + where TValue : ICollection + { + var propertyParameterString = new StringBuilder(); + var last = property.Value.Count - 1; + var index = 0; + + var escapeSemicolons = property.Key.AllowEscapeSemicolon(); + foreach (var parameter in property.Value) + { + if (string.IsNullOrEmpty(parameter)) + { + index++; + continue; + } + + propertyParameterString.Append(parameter.EscapeMSBuildPropertySpecialCharacters(escapeSemicolons)); + propertyParameterString.Append(index != last ? ";" : null); + + index++; + } + + return propertyParameterString.ToString(); + } + + private static string EscapeMSBuildPropertySpecialCharacters(this string value, bool escapeSemicolons) { if (string.IsNullOrEmpty(value)) { @@ -31,17 +59,22 @@ internal static string EscapeMSBuildPropertySpecialCharacters(this string value) var escapedBuilder = new StringBuilder(); foreach (var c in value) { - if (_escapeLookup.TryGetValue(c, out string newChar)) + if ((!escapeSemicolons && c.Equals(';')) || !_escapeLookup.TryGetValue(c, out var newChar)) { - escapedBuilder.Append(newChar); + escapedBuilder.Append(c); } else { - escapedBuilder.Append(c); + escapedBuilder.Append(newChar); } } return escapedBuilder.ToString(); } + + private static bool AllowEscapeSemicolon(this string propertyName) + { + return !_propertiesNotEscapeSemicolons.Contains(propertyName); + } } } diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs b/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs index 2b134a7660..b6f04f21fc 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs @@ -293,15 +293,11 @@ private static string GetPlatformName(PlatformTarget platform, bool isSolution) throw new ArgumentOutOfRangeException(nameof(platform), platform, "Invalid platform"); } } - private static IEnumerable GetPropertyArguments(IDictionary> properties) { - foreach (var propertyKey in properties.Keys) + foreach (var property in properties) { - foreach (var propertyValue in properties[propertyKey]) - { - yield return string.Concat("/p:", propertyKey, "=", propertyValue.EscapeMSBuildPropertySpecialCharacters()); - } + yield return string.Concat("/p:", property.Key, "=", property.BuildMSBuildPropertyParameterString()); } } diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs b/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs index 301a4b5181..11c56e61b6 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Cake.Core.Diagnostics; using Cake.Core.Tooling; @@ -47,6 +48,32 @@ public sealed class MSBuildSettings : ToolSettings /// The MSBuild platform. public MSBuildPlatform MSBuildPlatform { get; set; } + /// + /// Gets or sets the MSBuild target. + /// + /// The MSBuild target. + public string Target + { + get => string.Join(";", Targets); + set + { + Targets.Clear(); + + if (string.IsNullOrWhiteSpace(value)) + { + return; + } + + foreach (var target in value + .Split(';') + .Select(target => target.Trim()) + .Where(target => !string.IsNullOrEmpty(target))) + { + Targets.Add(target); + } + } + } + /// /// Gets or sets the tool version. /// diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs index 38ea7e3cc4..dc30666ac1 100644 --- a/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs @@ -115,7 +115,7 @@ public sealed class OpenCoverSettings : ToolSettings /// public OpenCoverSettings() { - _filters = new HashSet(StringComparer.OrdinalIgnoreCase); + _filters = new HashSet(StringComparer.Ordinal); _excludedAttributeFilters = new HashSet(StringComparer.OrdinalIgnoreCase); _excludedFileFilters = new HashSet(StringComparer.Ordinal); Register = new OpenCoverRegisterOptionUser(); diff --git a/src/Cake.Core.Tests/Cake.Core.Tests.csproj b/src/Cake.Core.Tests/Cake.Core.Tests.csproj index c5977c536c..927b10a8b9 100644 --- a/src/Cake.Core.Tests/Cake.Core.Tests.csproj +++ b/src/Cake.Core.Tests/Cake.Core.Tests.csproj @@ -14,14 +14,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.Core.Tests/Unit/Tooling/ToolSettingsExtensionsTests.cs b/src/Cake.Core.Tests/Unit/Tooling/ToolSettingsExtensionsTests.cs new file mode 100644 index 0000000000..558f6326ec --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Tooling/ToolSettingsExtensionsTests.cs @@ -0,0 +1,187 @@ +// 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.IO; +using Cake.Core.Tests.Fixtures; +using Cake.Core.Tooling; +using Xunit; + +namespace Cake.Core.Tests.Unit.Tooling +{ + public sealed class ToolSettingsExtensionsTests + { + [Fact] + public void Should_Call_WithToolSettings() + { + // Given + var fixture = new DummyToolFixture(); + FilePath expect = "/temp/WithToolSettings.exe"; + + // When + fixture.Settings.WithToolSettings(settings => settings.ToolPath = expect); + + // Then + Assert.Equal(expect.FullPath, fixture.Settings.ToolPath.FullPath); + } + + [Fact] + public void Should_Set_ToolPath() + { + // Given + var fixture = new DummyToolFixture(); + FilePath expect = "/temp/WithToolSettings.exe"; + + // When + fixture.Settings.WithToolPath(expect); + + // Then + Assert.Equal(expect.FullPath, fixture.Settings.ToolPath.FullPath); + } + + [Fact] + public void Should_Set_Timeout() + { + // Given + var fixture = new DummyToolFixture(); + var expect = TimeSpan.FromSeconds(42); + + // When + fixture.Settings.WithToolTimeout(expect); + + // Then + Assert.Equal(expect.TotalSeconds, fixture.Settings.ToolTimeout?.TotalSeconds); + } + + [Fact] + public void Should_Set_WorkingDirectory() + { + // Given + var fixture = new DummyToolFixture(); + DirectoryPath expect = "/temp/dir"; + + // When + fixture.Settings.WithWorkingDirectory(expect); + + // Then + Assert.Equal(expect.FullPath, fixture.Settings.WorkingDirectory.FullPath); + } + + [Fact] + public void Should_Set_NoWorkingDirectory() + { + // Given + var fixture = new DummyToolFixture(); + + // When + fixture.Settings.WithNoWorkingDirectory(); + + // Then + Assert.True(fixture.Settings.NoWorkingDirectory); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_NoWorkingDirectory_Arg(bool noWorkingDirectory) + { + // Given + var fixture = new DummyToolFixture(); + + // When + fixture.Settings.WithNoWorkingDirectory(noWorkingDirectory); + + // Then + Assert.Equal(noWorkingDirectory, fixture.Settings.NoWorkingDirectory); + } + + [Fact] + public void Should_Set_ArgumentCustomization() + { + // Given + var fixture = new DummyToolFixture(); + Func expect = _ => _; + + // When + fixture.Settings.WithArgumentCustomization(expect); + + // Then + Assert.Same(expect, fixture.Settings.ArgumentCustomization); + } + + [Fact] + public void Should_Set_EnvironmentVariable() + { + // Given + var fixture = new DummyToolFixture(); + const string key = nameof(key); + const string value = nameof(value); + + // When + fixture.Settings.WithEnvironmentVariable(key, value); + + // Then + Assert.Equal(value, fixture.Settings.EnvironmentVariables[key]); + } + + [Fact] + public void Should_Set_HandleExitCode() + { + // Given + var fixture = new DummyToolFixture(); + Func expect = _ => false; + + // When + fixture.Settings.WithHandleExitCode(expect); + + // Then + Assert.Same(expect, fixture.Settings.HandleExitCode); + } + + [Fact] + public void Should_Set_PostAction() + { + // Given + var fixture = new DummyToolFixture(); + Action expect = _ => { }; + + // When + fixture.Settings.WithPostAction(expect); + + // Then + Assert.Same(expect, fixture.Settings.PostAction); + } + + [Fact] + public void Should_Set_SetupProcessSettings() + { + // Given + var fixture = new DummyToolFixture(); + Action expect = _ => { }; + + // When + fixture.Settings.WithSetupProcessSettings(expect); + + // Then + Assert.Same(expect, fixture.Settings.SetupProcessSettings); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void Should_Set_ReturnTrueForExpectedExitCode(int expectedExitCode) + { + // Given + var fixture = new DummyToolFixture(); + fixture.Settings.WithExpectedExitCode(expectedExitCode); + + // When + var result = fixture.Settings.HandleExitCode(expectedExitCode); + + // Then + Assert.True(result); + } + } +} \ No newline at end of file diff --git a/src/Cake.Core/CakeEngineActions.cs b/src/Cake.Core/CakeEngineActions.cs index 868d56950b..7d5fb77af3 100644 --- a/src/Cake.Core/CakeEngineActions.cs +++ b/src/Cake.Core/CakeEngineActions.cs @@ -7,17 +7,40 @@ namespace Cake.Core { - internal sealed class CakeEngineActions + /// + /// Container for actions required by the engine. + /// + public sealed class CakeEngineActions { private readonly ICakeDataService _data; private readonly HashSet _dataTypes; private readonly List _validations; + /// + /// Gets all registerd setup actions. + /// public List> Setups { get; } + + /// + /// Gets all registered teardown actions. + /// public List> Teardowns { get; } + + /// + /// Gets the registered task setup action. + /// public Action TaskSetup { get; private set; } + + /// + /// Gets the registered task teardown action. + /// public Action TaskTeardown { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// The data context. + /// is null. public CakeEngineActions(ICakeDataService data) { _data = data ?? throw new ArgumentNullException(nameof(data)); @@ -28,11 +51,21 @@ public CakeEngineActions(ICakeDataService data) Teardowns = new List>(); } + /// + /// Register a setup action. + /// + /// The setup action. public void RegisterSetup(Action action) { Setups.Add(action); } + /// + /// Registers a setup action. + /// + /// Type of the data for the setup action. + /// The setup action. + /// was already registered. public void RegisterSetup(Func action) where TData : class { @@ -50,11 +83,20 @@ public void RegisterSetup(Func action) }); } + /// + /// Registers a teardown action. + /// + /// The teardown action. public void RegisterTeardown(Action action) { Teardowns.Add(action); } + /// + /// Registers a teardown action. + /// + /// Type of the data for the teardown action. + /// The teardown action. public void RegisterTeardown(Action action) where TData : class { @@ -65,12 +107,21 @@ public void RegisterTeardown(Action action) }); } + /// + /// Registers the task setup action. + /// + /// The task setup action. public void RegisterTaskSetup(Action action) { EnsureNotRegistered(TaskSetup, "Task Setup"); TaskSetup = action; } + /// + /// Registers the task setup action. + /// + /// Type of the data for the task setup action. + /// The task setup action. public void RegisterTaskSetup(Action action) where TData : class { @@ -82,12 +133,21 @@ public void RegisterTaskSetup(Action action) }; } + /// + /// Registers the task teardown action. + /// + /// The task teardown action. public void RegisterTaskTeardown(Action action) { EnsureNotRegistered(TaskTeardown, "Task Teardown"); TaskTeardown = action; } + /// + /// Registers the task teardown action. + /// + /// Type of the data for the task teardown action. + /// The task teardown action. public void RegisterTaskTeardown(Action action) where TData : class { @@ -99,6 +159,9 @@ public void RegisterTaskTeardown(Action acti }; } + /// + /// Executed all validations. + /// public void Validate() { foreach (var validation in _validations) diff --git a/src/Cake.Core/CakeTaskBuilder.cs b/src/Cake.Core/CakeTaskBuilder.cs index c1c2a9e92e..89cac5644a 100644 --- a/src/Cake.Core/CakeTaskBuilder.cs +++ b/src/Cake.Core/CakeTaskBuilder.cs @@ -18,7 +18,12 @@ public sealed class CakeTaskBuilder internal CakeTask Target { get; } - internal CakeTaskBuilder(CakeTask task) + /// + /// Initializes a new instance of the class. + /// + /// The target task. + /// is null. + public CakeTaskBuilder(CakeTask task) { Target = task ?? throw new ArgumentNullException(nameof(task)); } diff --git a/src/Cake.Core/Polyfill/AssemblyHelper.cs b/src/Cake.Core/Polyfill/AssemblyHelper.cs index f2b4615181..d01a9d5ff0 100644 --- a/src/Cake.Core/Polyfill/AssemblyHelper.cs +++ b/src/Cake.Core/Polyfill/AssemblyHelper.cs @@ -5,8 +5,8 @@ using System; using System.Reflection; using System.Runtime.InteropServices; +using Cake.Core.Diagnostics; using Cake.Core.IO; -using Cake.Core.Reflection; namespace Cake.Core.Polyfill { @@ -26,7 +26,7 @@ public static Assembly LoadAssembly(AssemblyName assemblyName) return Assembly.Load(assemblyName); } - public static Assembly LoadAssembly(ICakeEnvironment environment, IFileSystem fileSystem, FilePath path) + public static Assembly LoadAssembly(ICakeEnvironment environment, IFileSystem fileSystem, ICakeLog log, FilePath path) { if (path == null) { @@ -49,9 +49,37 @@ public static Assembly LoadAssembly(ICakeEnvironment environment, IFileSystem fi return Assembly.LoadFrom(path.FullPath); } - if (environment.Platform.IsUnix()) + if (_useLoadMacOsLibrary) { - LoadUnixLibrary(path.FullPath, RTLD_NOW); + LoadUnixLibrary(LoadMacOSLibrary, path); + LogNativeLoad(log, nameof(LoadMacOSLibrary), path); + } + else if (_useLoadUnixLibrary1) + { + LoadUnixLibrary(LoadUnixLibrary1, path); + LogNativeLoad(log, nameof(LoadUnixLibrary1), path); + } + else if (_useLoadUnixLibrary2) + { + LoadUnixLibrary(LoadUnixLibrary2, path); + LogNativeLoad(log, nameof(LoadUnixLibrary2), path); + } + else if (environment.Platform.IsUnix()) + { + if (environment.Platform.IsOSX() && TryLoadUnixLibrary(LoadMacOSLibrary, path, out _useLoadMacOsLibrary)) + { + LogNativeLoad(log, nameof(LoadMacOSLibrary), path); + return null; + } + + if (TryLoadUnixLibrary(LoadUnixLibrary2, path, out _useLoadUnixLibrary2)) + { + LogNativeLoad(log, nameof(LoadUnixLibrary2), path); + return null; + } + + TryLoadUnixLibrary(LoadUnixLibrary1, path, out _useLoadUnixLibrary1); + LogNativeLoad(log, nameof(LoadUnixLibrary1), path); } else { @@ -60,21 +88,60 @@ public static Assembly LoadAssembly(ICakeEnvironment environment, IFileSystem fi return null; } - catch (System.IO.FileLoadException) + catch (System.IO.FileLoadException ex) { - // TODO: LOG + log.Debug(Verbosity.Diagnostic, + logAction => logAction("Caught error while loading {0}\r\n{1}", + path.FullPath, + ex)); return null; } } + private static void LogNativeLoad(ICakeLog log, string loadUnixLibrary, FilePath path) + { + log.Debug(Verbosity.Diagnostic, + logAction => logAction("Native {0}: {1}", + loadUnixLibrary, + path.FullPath)); + } + #pragma warning disable SA1310 // Field names should not contain underscore private const int RTLD_NOW = 0x002; #pragma warning restore SA1310 // Field names should not contain underscore +#pragma warning disable SA1303 // Field names should start with uppercase + private const string dlopen = nameof(dlopen); +#pragma warning restore SA1303 // Field names should start with uppercase + + private static bool _useLoadUnixLibrary1; + private static bool _useLoadUnixLibrary2; + private static bool _useLoadMacOsLibrary; - [DllImport("libdl", EntryPoint = "dlopen")] - private static extern IntPtr LoadUnixLibrary(string path, int flags); + [DllImport("libdl.so", EntryPoint = dlopen)] + private static extern IntPtr LoadUnixLibrary1(string path, int flags); + + [DllImport("libdl.so.2", EntryPoint = dlopen)] + private static extern IntPtr LoadUnixLibrary2(string path, int flags); + + [DllImport("/usr/lib/libSystem.dylib", EntryPoint = dlopen)] + private static extern IntPtr LoadMacOSLibrary(string path, int flags); [DllImport("kernel32", EntryPoint = "LoadLibrary")] private static extern IntPtr LoadWindowsLibrary(string path); + + private static void LoadUnixLibrary(Func dlOpen, FilePath path) => dlOpen(path.FullPath, RTLD_NOW); + + private static bool TryLoadUnixLibrary(Func dlOpen, FilePath path, out bool result) + { + try + { + LoadUnixLibrary(dlOpen, path); + return result = true; + } + catch (DllNotFoundException) + { + return result = false; + } + } } } \ No newline at end of file diff --git a/src/Cake.Core/Reflection/AssemblyLoader.cs b/src/Cake.Core/Reflection/AssemblyLoader.cs index d53746f703..ef71ce3ba2 100644 --- a/src/Cake.Core/Reflection/AssemblyLoader.cs +++ b/src/Cake.Core/Reflection/AssemblyLoader.cs @@ -4,6 +4,7 @@ using System.Reflection; using Cake.Core.Configuration; +using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Polyfill; @@ -17,6 +18,7 @@ public sealed class AssemblyLoader : IAssemblyLoader private readonly ICakeEnvironment _environment; private readonly IFileSystem _fileSystem; private readonly IAssemblyVerifier _verifier; + private readonly ICakeLog _log; /// /// Initializes a new instance of the class. @@ -24,11 +26,13 @@ public sealed class AssemblyLoader : IAssemblyLoader /// The environment. /// The file system. /// The assembly verifier. - public AssemblyLoader(ICakeEnvironment environment, IFileSystem fileSystem, IAssemblyVerifier verifier) + /// The cake log. + public AssemblyLoader(ICakeEnvironment environment, IFileSystem fileSystem, IAssemblyVerifier verifier, ICakeLog log) { _environment = environment; _fileSystem = fileSystem; _verifier = verifier; + _log = log; } /// @@ -40,7 +44,7 @@ public Assembly Load(AssemblyName assemblyName) /// public Assembly Load(FilePath path, bool verify) { - var assembly = AssemblyHelper.LoadAssembly(_environment, _fileSystem, path); + var assembly = AssemblyHelper.LoadAssembly(_environment, _fileSystem, _log, path); if (verify && assembly != null) { _verifier.Verify(assembly); diff --git a/src/Cake.Core/Scripting/ScriptConventions.cs b/src/Cake.Core/Scripting/ScriptConventions.cs index d3e146d103..dea181f53f 100644 --- a/src/Cake.Core/Scripting/ScriptConventions.cs +++ b/src/Cake.Core/Scripting/ScriptConventions.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Cake.Core.Configuration; using Cake.Core.IO; using Cake.Core.Reflection; @@ -50,8 +49,9 @@ public IReadOnlyList GetDefaultNamespaces() "System.IO", "Cake.Core", "Cake.Core.IO", + "Cake.Core.Diagnostics", "Cake.Core.Scripting", - "Cake.Core.Diagnostics" + "Cake.Core.Tooling" }; } diff --git a/src/Cake.Core/Tooling/ToolSettingsExtensions.cs b/src/Cake.Core/Tooling/ToolSettingsExtensions.cs new file mode 100644 index 0000000000..4011095fd8 --- /dev/null +++ b/src/Cake.Core/Tooling/ToolSettingsExtensions.cs @@ -0,0 +1,152 @@ +// 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.IO; + +namespace Cake.Core.Tooling +{ + /// + /// Contains functionality related to . + /// + public static class ToolSettingsExtensions + { + /// + /// Provides fluent null guarded tool settings action. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tools settings action. + /// The tools settings. + /// Thrown if or is null. + public static T WithToolSettings(this T toolSettings, Action toolSettingsAction) + where T : ToolSettings + { + if (toolSettings is null) + { + throw new ArgumentNullException(nameof(toolSettings)); + } + + if (toolSettingsAction is null) + { + throw new ArgumentNullException(nameof(toolSettingsAction)); + } + + toolSettingsAction.Invoke(toolSettings); + + return toolSettings; + } + + /// + /// Sets the tool path. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool path. + /// The tools settings. + public static T WithToolPath(this T toolSettings, FilePath toolPath) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.ToolPath = toolPath); + + /// + /// Sets the tool timeout. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool timeout. + /// The tools settings. + public static T WithToolTimeout(this T toolSettings, TimeSpan toolTimeout) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.ToolTimeout = toolTimeout); + + /// + /// Sets the tool working directory. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool working directory. + /// The tools settings. + public static T WithWorkingDirectory(this T toolSettings, DirectoryPath workingDirectory) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.WorkingDirectory = workingDirectory); + + /// + /// Sets whether the tool should use a working directory or not. + /// + /// The ToolSettings type. + /// The tools settings. + /// Flag for no working directory (default true). + /// The tools settings. + public static T WithNoWorkingDirectory(this T toolSettings, bool noWorkingDirectory = true) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.NoWorkingDirectory = noWorkingDirectory); + + /// + /// Sets the tool argument customization delegate. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool argument customization delegate. + /// The tools settings. + public static T WithArgumentCustomization(this T toolSettings, Func argumentCustomization) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.ArgumentCustomization = argumentCustomization); + + /// + /// Sets or adds tool environment variable. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool environment variable key. + /// The tool environment variable value. + /// The tools settings. + public static T WithEnvironmentVariable(this T toolSettings, string key, string value) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.EnvironmentVariables[key] = value); + + /// + /// Sets delegate whether the exit code from the tool process causes an exception to be thrown. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool handle exit code delegate. + /// The tools settings. + public static T WithHandleExitCode(this T toolSettings, Func handleExitCode) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.HandleExitCode = handleExitCode); + + /// + /// Sets a delegate which is executed after the tool process was started. + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool argument customization delegate. + /// The tools settings. + public static T WithPostAction(this T toolSettings, Action postAction) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.PostAction = postAction); + + /// + /// Sets a delegate to configure the process settings. + /// + /// The ToolSettings type. + /// The tools settings. + /// The setup process settings delegate. + /// The tools settings. + public static T WithSetupProcessSettings(this T toolSettings, Action setupProcessSettings) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.SetupProcessSettings = setupProcessSettings); + + /// + /// Sets expected exit code using . + /// + /// The ToolSettings type. + /// The tools settings. + /// The tool expected exit code. + /// The tools settings. + public static T WithExpectedExitCode(this T toolSettings, int expectExitCode) + where T : ToolSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.HandleExitCode = exitCode => exitCode == expectExitCode); + } +} \ No newline at end of file diff --git a/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj b/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj index 96cae6ced5..3bc427f335 100644 --- a/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj +++ b/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj @@ -16,13 +16,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.Frosting.Template/templates/cakefrosting/build/Build.csproj b/src/Cake.Frosting.Template/templates/cakefrosting/build/Build.csproj index 5c1b91e7fb..cc0d1f730b 100644 --- a/src/Cake.Frosting.Template/templates/cakefrosting/build/Build.csproj +++ b/src/Cake.Frosting.Template/templates/cakefrosting/build/Build.csproj @@ -5,6 +5,6 @@ $(MSBuildProjectDirectory) - + \ No newline at end of file diff --git a/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj b/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj index e2db5e902e..01cab2ddc3 100644 --- a/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj +++ b/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj @@ -8,15 +8,15 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/Cake.Frosting/Cake.Frosting.csproj b/src/Cake.Frosting/Cake.Frosting.csproj index ff0866174f..339a076351 100644 --- a/src/Cake.Frosting/Cake.Frosting.csproj +++ b/src/Cake.Frosting/Cake.Frosting.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj b/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj index 0bdfc651a5..22ee588ed6 100644 --- a/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj +++ b/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj @@ -15,14 +15,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.NuGet/Cake.NuGet.csproj b/src/Cake.NuGet/Cake.NuGet.csproj index 863c0d1c02..44fccf8e9b 100644 --- a/src/Cake.NuGet/Cake.NuGet.csproj +++ b/src/Cake.NuGet/Cake.NuGet.csproj @@ -18,18 +18,18 @@ - - - - - - + + + + + + - + - + All diff --git a/src/Cake.NuGet/NuGetContentResolver.cs b/src/Cake.NuGet/NuGetContentResolver.cs index 631897c6e6..6544e5f55f 100644 --- a/src/Cake.NuGet/NuGetContentResolver.cs +++ b/src/Cake.NuGet/NuGetContentResolver.cs @@ -75,7 +75,13 @@ private IReadOnlyCollection GetAddinAssemblies(DirectoryPath path, Packag var tfm = NuGetFramework.Parse(_environment.Runtime.BuiltFramework.FullName, DefaultFrameworkNameProvider.Instance); // Get current runtime identifier. - string rid = _environment.Runtime.IsCoreClr ? Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier() : null; + var rid = _environment.Runtime.IsCoreClr +#if NETCOREAPP3_1 + ? Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier() +#else + ? System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier +#endif + : null; // Get all candidate files. var pathComparer = PathComparer.Default; diff --git a/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj b/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj index b5c57f8455..4ca967f60b 100644 --- a/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj +++ b/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Cake.Tests/Cake.Tests.csproj b/src/Cake.Tests/Cake.Tests.csproj index 3129807382..30e5849d29 100644 --- a/src/Cake.Tests/Cake.Tests.csproj +++ b/src/Cake.Tests/Cake.Tests.csproj @@ -10,14 +10,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.Tests/Unit/Features/InfoFeatureTests.cs b/src/Cake.Tests/Unit/Features/InfoFeatureTests.cs index d477ac45d0..3d655f9ba1 100644 --- a/src/Cake.Tests/Unit/Features/InfoFeatureTests.cs +++ b/src/Cake.Tests/Unit/Features/InfoFeatureTests.cs @@ -22,5 +22,20 @@ public void Should_Output_Version_To_Console() Assert.Contains("Version: 1.2.3", console.Messages); Assert.Contains("Details: 3.2.1", console.Messages); } + + [Fact] + public void Should_Throw_If_Console_Is_Null() + { + // Given + var resolver = new FakeVersionResolver("1.2.3", "3.2.1"); + var feature = new InfoFeature(resolver); + + // When + var result = Record.Exception(() => + feature.Run(null)); + + // Then + AssertEx.IsArgumentNullException(result, "console"); + } } } diff --git a/src/Cake.Tests/Unit/Features/VersionFeatureTests.cs b/src/Cake.Tests/Unit/Features/VersionFeatureTests.cs index 552377f316..028bbf4939 100644 --- a/src/Cake.Tests/Unit/Features/VersionFeatureTests.cs +++ b/src/Cake.Tests/Unit/Features/VersionFeatureTests.cs @@ -21,5 +21,20 @@ public void Should_Output_Version_To_Console() // Then Assert.Contains("1.2.3", console.Messages); } + + [Fact] + public void Should_Throw_If_Console_Is_Null() + { + // Given + var resolver = new FakeVersionResolver("1.2.3", "3.2.1"); + var feature = new VersionFeature(resolver); + + // When + var result = Record.Exception(() => + feature.Run(null)); + + // Then + AssertEx.IsArgumentNullException(result, "console"); + } } } diff --git a/src/Cake/Cake.csproj b/src/Cake/Cake.csproj index 5cf4a6061f..db6f8b58d5 100644 --- a/src/Cake/Cake.csproj +++ b/src/Cake/Cake.csproj @@ -26,12 +26,12 @@ - + - - - - + + + + \ No newline at end of file diff --git a/src/Cake/Infrastructure/Composition/ModuleSearcher.cs b/src/Cake/Infrastructure/Composition/ModuleSearcher.cs index f47110b5a5..89bd552609 100644 --- a/src/Cake/Infrastructure/Composition/ModuleSearcher.cs +++ b/src/Cake/Infrastructure/Composition/ModuleSearcher.cs @@ -75,7 +75,7 @@ private Type LoadModule(FilePath path, ICakeConfiguration configuration) return null; } - var loader = new AssemblyLoader(_environment, _fileSystem, new AssemblyVerifier(configuration, _log)); + var loader = new AssemblyLoader(_environment, _fileSystem, new AssemblyVerifier(configuration, _log), _log); var assembly = loader.Load(path, true); var attribute = assembly.GetCustomAttributes().FirstOrDefault(); diff --git a/src/Cake/Infrastructure/Scripting/RoslynScriptSession.cs b/src/Cake/Infrastructure/Scripting/RoslynScriptSession.cs index 6153104195..5c641fa24e 100644 --- a/src/Cake/Infrastructure/Scripting/RoslynScriptSession.cs +++ b/src/Cake/Infrastructure/Scripting/RoslynScriptSession.cs @@ -98,29 +98,17 @@ public void ImportNamespace(string @namespace) public void Execute(Script script) { var scriptName = _settings.Script.GetFilename(); - var cacheDLLFileName = $"{scriptName}.dll"; - var cacheHashFileName = $"{scriptName}.hash"; - var cachedAssembly = _scriptCachePath.CombineWithFilePath(cacheDLLFileName); - var hashFile = _scriptCachePath.CombineWithFilePath(cacheHashFileName); - string scriptHash = default; + FilePath cachedAssembly = _scriptCacheEnabled + ? GetCachedAssemblyPath(script, scriptName) + : default; + if (_scriptCacheEnabled && _fileSystem.Exist(cachedAssembly) && !_regenerateCache) { - _log.Verbose($"Cache enabled: Checking cache build script ({cacheDLLFileName})"); - scriptHash = FastHash.GenerateHash(Encoding.UTF8.GetBytes(string.Concat(script.Lines))); - var cachedHash = _fileSystem.Exist(hashFile) - ? _fileSystem.GetFile(hashFile).ReadLines(Encoding.UTF8).FirstOrDefault() - : string.Empty; - if (scriptHash.Equals(cachedHash, StringComparison.Ordinal)) - { - _log.Verbose("Running cached build script..."); - RunScriptAssembly(cachedAssembly.FullPath); - return; - } - else - { - _log.Verbose("Cache check failed."); - } + _log.Verbose("Running cached build script..."); + RunScriptAssembly(cachedAssembly.FullPath); + return; } + // Generate the script code. var generator = new RoslynCodeGenerator(); var code = generator.Generate(script); @@ -205,19 +193,11 @@ public void Execute(Script script) { _fileSystem.GetDirectory(_scriptCachePath).Create(); } - if (string.IsNullOrWhiteSpace(scriptHash)) - { - scriptHash = FastHash.GenerateHash(Encoding.UTF8.GetBytes(string.Concat(script.Lines))); - } + var emitResult = compilation.Emit(cachedAssembly.FullPath); if (emitResult.Success) { - using (var stream = _fileSystem.GetFile(hashFile).OpenWrite()) - using (var writer = new StreamWriter(stream, Encoding.UTF8)) - { - writer.Write(scriptHash); - } RunScriptAssembly(cachedAssembly.FullPath); } } @@ -227,6 +207,15 @@ public void Execute(Script script) } } + private FilePath GetCachedAssemblyPath(Script script, FilePath scriptName) + => _scriptCachePath.CombineWithFilePath( + string.Join( + '.', + scriptName.GetFilenameWithoutExtension().FullPath, + _host.GetType().Name, + FastHash.GenerateHash(Encoding.UTF8.GetBytes(string.Concat(script.Lines))), + "dll")); + private void RunScriptAssembly(string assemblyPath) { var assembly = _loader.Load(assemblyPath, false); diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 9ebd693775..dfccd8b979 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -10,7 +10,7 @@ using System.Reflection; [assembly: AssemblyProduct("Cake")] -[assembly: AssemblyVersion("2.2.0.0")] -[assembly: AssemblyFileVersion("2.2.0.0")] -[assembly: AssemblyInformationalVersion("2.2.0-beta.1+0.Branch.release-2.2.0.Sha.8861b463a4eb651f9e5149ee7f7ada47d03ae1ad")] +[assembly: AssemblyVersion("2.3.0.0")] +[assembly: AssemblyFileVersion("2.3.0.0")] +[assembly: AssemblyInformationalVersion("2.3.0-beta.1+0.Branch.release-2.3.0.Sha.c4a384c17133aec06e40e0771ae4795313bfc20f")] [assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] diff --git a/tests/integration/Cake.Common/Tools/Command/CommandAliases.cake b/tests/integration/Cake.Common/Tools/Command/CommandAliases.cake new file mode 100644 index 0000000000..835911d09d --- /dev/null +++ b/tests/integration/Cake.Common/Tools/Command/CommandAliases.cake @@ -0,0 +1,174 @@ +#load "./../utilities/xunit.cake" + +////////////////////////////////////////////////////////////////////////////// + +Setup( + context => new CommandSettings { + ToolName = "dotnet", + ToolExecutableNames = new []{ "dotnet", "dotnet.exe" }, + } +); + +Task("Cake.Common.Tools.Command.CommandAliases.Command") + .Does(static (ctx, settings) => +{ + // Given, When, Then + ctx.Command(settings.ToolExecutableNames, "--version"); +}); + +Task("Cake.Common.Tools.Command.CommandAliases.Command.Settings") + .Does(static (ctx, settings) => +{ + // Given, When, Then + ctx.Command(settings, "--version"); +}); + +Task("Cake.Common.Tools.Command.CommandAliases.CommandStandardOutput") + .Does(static (ctx, settings) => +{ + // Given + const string expectStandardOutput = @"Description: + List tools installed globally or locally. + +Usage: + dotnet tool list [options] + +Options:"; + + // When + var exitCode = ctx.Command(settings.ToolExecutableNames, out var standardOutput, "tool list -h"); + + // Then + Assert.Equal(0, exitCode); + Assert.StartsWith( + expectStandardOutput.NormalizeLineEndings(), + standardOutput.NormalizeLineEndings()); +}); + +Task("Cake.Common.Tools.Command.CommandAliases.CommandStandardOutput.Settings") + .Does(static (ctx, settings) => +{ + // Given + const string expectStandardOutput = @"Description: + List tools installed globally or locally. + +Usage: + dotnet tool list [options] + +Options:"; + + // When + var exitCode = ctx.Command(settings, out var standardOutput, "tool list -h"); + + // Then + Assert.Equal(0, exitCode); + Assert.StartsWith( + expectStandardOutput.NormalizeLineEndings(), + standardOutput.NormalizeLineEndings()); +}); + +Task("Cake.Common.Tools.Command.CommandAliases.CommandStandardOutput.SettingsCustomization") + .Does(static (ctx, settings) => +{ + // Given + const string expectStandardOutput = @"Description: + List tools installed globally or locally. + +Usage: + dotnet tool list [options] + +Options:"; + + // When + var exitCode = ctx.Command( + settings.ToolExecutableNames, + out var standardOutput, + settingsCustomization: settings => settings.WithArgumentCustomization(args => "tool list -h") + ); + + // Then + Assert.Equal(0, exitCode); + Assert.StartsWith( + expectStandardOutput.NormalizeLineEndings(), + standardOutput.NormalizeLineEndings()); +}); + +Task("Cake.Common.Tools.Command.CommandAliases.CommandStandardError") + .Does(static (ctx, settings) => +{ + // Given + const string expectStandardOutput = @"Description: + Install or work with tools that extend the .NET experience. + +Usage: + dotnet tool [command] [options] + +Options: + -?, -h, --help Show command line help. + +Commands:"; + const string expectStandardError = "Required command was not provided."; + const int expectExitCode = 1; + + // When + var result = ctx.Command( + settings.ToolExecutableNames, + out var standardOutput, + out var standardError, + "tool", + expectExitCode); + + // Then + Assert.Equal(expectExitCode, result); + Assert.StartsWith( + expectStandardOutput.NormalizeLineEndings(), + standardOutput.NormalizeLineEndings()); + Assert.Equal( + expectStandardError.NormalizeLineEndings(), + standardError.NormalizeLineEndings()); +}); + +Task("Cake.Common.Tools.Command.CommandAliases.CommandStandardError.Settings") + .Does(static (ctx, settings) => +{ + // Given + const string expectStandardOutput = @"Description: + Install or work with tools that extend the .NET experience. + +Usage: + dotnet tool [command] [options] + +Options: + -?, -h, --help Show command line help. + +Commands:"; + const string expectStandardError = "Required command was not provided."; + const int expectExitCode = 1; + var errorSettings = new CommandSettings { + ToolName = settings.ToolName, + ToolExecutableNames = settings.ToolExecutableNames, + }.WithExpectedExitCode(expectExitCode); + + // When + var result = ctx.Command(errorSettings, out var standardOutput, out var standardError, "tool"); + + // Then + Assert.Equal(expectExitCode, result); + Assert.StartsWith( + expectStandardOutput.NormalizeLineEndings(), + standardOutput.NormalizeLineEndings()); + Assert.Equal( + expectStandardError.NormalizeLineEndings(), + standardError.NormalizeLineEndings()); +}); + +////////////////////////////////////////////////////////////////////////////// + +Task("Cake.Common.Tools.Command.CommandAliases") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases.Command") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases.Command.Settings") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases.CommandStandardOutput") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases.CommandStandardOutput.Settings") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases.CommandStandardOutput.SettingsCustomization") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases.CommandStandardError") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases.CommandStandardError.Settings"); \ No newline at end of file diff --git a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake index 4afd636041..bba4a7b6a0 100644 --- a/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake +++ b/tests/integration/Cake.Common/Tools/DotNet/DotNetAliases.cake @@ -280,6 +280,34 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadSearch") } }); +Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadRepair") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.Setup") + .Does(() => +{ + // When + DotNetWorkloadRepair(); +}); + +Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadUpdate") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.Setup") + .Does(() => +{ + // When + DotNetWorkloadUpdate(); +}); + +Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadRestore") + .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"); + + // When + DotNetWorkloadRestore(project.FullPath); +}); + Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetRestore") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuild") @@ -298,6 +326,9 @@ Task("Cake.Common.Tools.DotNet.DotNetAliases.DotNetBuildServerShutdown") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetFormat") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetSDKCheck") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadSearch") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadRepair") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadUpdate") + .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases.DotNetWorkloadRestore") .Does(() => { // When diff --git a/tests/integration/Cake.Core/Scripting/AddinDirective.cake b/tests/integration/Cake.Core/Scripting/AddinDirective.cake index d0c9f94a3f..bc0f3c831a 100644 --- a/tests/integration/Cake.Core/Scripting/AddinDirective.cake +++ b/tests/integration/Cake.Core/Scripting/AddinDirective.cake @@ -55,8 +55,35 @@ Task("Cake.Core.Scripting.AddinDirective.CallDuplicatedMethod") var result = context.EnvironmentVariable("CAKE_DOES_ROCK", true); }); +Task("Cake.Core.Scripting.AddinDirective.LoadNativeAssemblies") + .Does(() => +{ + FilePath cakeCore = typeof(ICakeContext).GetTypeInfo().Assembly.Location; + FilePath cake = cakeCore.GetDirectory().CombineWithFilePath("Cake.dll"); + var script = @"#addin nuget:?package=Cake.Git&version=2.0.0 + +var repoRoot = GitFindRootFromPath(Context.EnvironmentVariable(""CAKE_TEST_DIR"")); + +var hasUncommittedChanges = GitHasUncommitedChanges(repoRoot);"; + + CakeExecuteExpression(script, + new CakeSettings { + EnvironmentVariables = new Dictionary{ + {"CAKE_PATHS_ADDINS", $"{Paths.Temp}/native/tools/Addins"}, + {"CAKE_PATHS_TOOLS", $"{Paths.Temp}/native/tools"}, + {"CAKE_PATHS_MODULES", $"{Paths.Temp}/native/tools/Modules"}, + {"NUGET_PACKAGES", $"{Paths.Temp}/nuget/Packages"}, + {"NUGET_HTTP_CACHE_PATH ", $"{Paths.Temp}/nuget/Cache"}, + {"CAKE_TEST_DIR", Context.Environment.WorkingDirectory.FullPath} + }, + ToolPath = cake, + Verbosity = Context.Log.Verbosity + }); +}); + ////////////////////////////////////////////////////////////////////////////// Task("Cake.Core.Scripting.AddinDirective") .IsDependentOn("Cake.Core.Scripting.AddinDirective.LoadTargetedAddin") - .IsDependentOn("Cake.Core.Scripting.AddinDirective.CallDuplicatedMethod"); \ No newline at end of file + .IsDependentOn("Cake.Core.Scripting.AddinDirective.CallDuplicatedMethod") + .IsDependentOn("Cake.Core.Scripting.AddinDirective.LoadNativeAssemblies"); \ No newline at end of file diff --git a/tests/integration/Cake/ScriptCache.cake b/tests/integration/Cake/ScriptCache.cake index f97268f064..33f9519bfb 100644 --- a/tests/integration/Cake/ScriptCache.cake +++ b/tests/integration/Cake/ScriptCache.cake @@ -5,19 +5,17 @@ using System.Diagnostics; public class ScriptCacheData { public FilePath ScriptPath { get; } - public FilePath ScriptCacheAssemblyPath { get; } - public FilePath ScriptCacheHashPath { get; } + public GlobPattern ScriptCacheAssemblyPattern { get; } public FilePath ConfigScriptPath { get; } public DirectoryPath ConfigScriptCachePath { get; } - public FilePath ConfigScriptCacheAssemblyPath { get; } - public FilePath ConfigScriptCacheHashPath { get; } + public GlobPattern ConfigScriptCacheAssemblyPattern { get; } public (TimeSpan Elapsed, string Hash) CompileResult { get; set; } public (TimeSpan Elapsed, string Hash) ExecuteResult { get; set; } public (TimeSpan Elapsed, string Hash) ReCompileResult { get; set; } public (TimeSpan Elapsed, string Hash) ConfigCompileResult { get; set; } public CakeSettings Settings { get; } private Action CakeExecuteScript { get; } - private Func CalculateFileHash { get; } + private Func CalculateFileHash { get; } public TimeSpan Time(Action action) { @@ -45,25 +43,35 @@ public class ScriptCacheData scriptPath ?? ScriptPath, Settings); }), - CalculateFileHash(ScriptCacheAssemblyPath).ToHex() + CalculateFileHash(ScriptCacheAssemblyPattern).ToHex() ); public ScriptCacheData( DirectoryPath scriptDirectoryPath, Action cakeExecuteScript, - Func calculateFileHash + Func calculateFileHash ) { - ScriptPath = scriptDirectoryPath.CombineWithFilePath("build.cake"); - var cacheDirectoryPath = scriptDirectoryPath.Combine("tools").Combine("cache"); - ScriptCacheAssemblyPath = cacheDirectoryPath.CombineWithFilePath("build.cake.dll"); - ScriptCacheHashPath = cacheDirectoryPath.CombineWithFilePath("build.cake.hash"); var configScriptDirectoryPath = scriptDirectoryPath.Combine("Config"); - ConfigScriptPath = configScriptDirectoryPath.CombineWithFilePath("build.cake"); var configCacheRootPath = configScriptDirectoryPath.Combine("CacheRootPath"); - ConfigScriptCachePath = configCacheRootPath.Combine("cake-build").Combine("CacheLeafPath"); - ConfigScriptCacheAssemblyPath = ConfigScriptCachePath.CombineWithFilePath("build.cake.dll"); - ConfigScriptCacheHashPath = ConfigScriptCachePath.CombineWithFilePath("build.cake.hash"); + + ScriptPath = scriptDirectoryPath + .CombineWithFilePath("build.cake"); + ScriptCacheAssemblyPattern = scriptDirectoryPath + .Combine("tools") + .Combine("cache") + .CombineWithFilePath($"build.BuildScriptHost.*.dll") + .FullPath; + + ConfigScriptPath = configScriptDirectoryPath + .CombineWithFilePath("build.cake"); + ConfigScriptCachePath = configCacheRootPath + .Combine("cake-build") + .Combine("CacheLeafPath"); + ConfigScriptCacheAssemblyPattern = ConfigScriptCachePath + .CombineWithFilePath($"build.BuildScriptHost.*.dll") + .FullPath; + Settings = new CakeSettings { EnvironmentVariables = new Dictionary { { "CAKE_SETTINGS_ENABLESCRIPTCACHE", "true" }, @@ -79,11 +87,13 @@ public class ScriptCacheData Setup(context => new ScriptCacheData( - Paths.Temp - .Combine("./Cake/ScriptCache"), - context.CakeExecuteScript, - context.CalculateFileHash - )); + Paths + .Temp + .Combine("./Cake/ScriptCache"), + context.CakeExecuteScript, + globberPattern => context.CalculateFileHash(context.GetFiles(globberPattern).OrderByDescending(file => System.IO.File.GetLastWriteTime(file.FullPath)).FirstOrDefault()) + ) + ); Task("Cake.ScriptCache.Setup") .Does(() => @@ -111,8 +121,8 @@ Task("Cake.ScriptCache.Compile") data.CompileResult = data.TimeCakeExecuteScript(); // Then - Assert.True(FileExists(data.ScriptCacheAssemblyPath), $"Script Cache Assembly Path {data.ScriptCacheAssemblyPath} missing."); - Assert.True(FileExists(data.ScriptCacheHashPath), $"Script Cache Hash Path {data.ScriptCacheHashPath} missing."); + var count = GetFiles(data.ScriptCacheAssemblyPattern).Count(); + Assert.True(1 == count, $"Script Cache Assembly Path {data.ScriptCacheAssemblyPattern.Pattern} expected 1 got {count}."); }); var scriptCacheExecute = Task("Cake.ScriptCache.Execute"); @@ -149,8 +159,8 @@ Task("Cake.ScriptCache.Config") data.ConfigCompileResult = data.TimeCakeExecuteScript(data.ConfigScriptPath); // Then - Assert.True(FileExists(data.ConfigScriptCacheAssemblyPath), $"Script Cache Assembly Path {data.ConfigScriptCacheAssemblyPath} missing."); - Assert.True(FileExists(data.ConfigScriptCacheHashPath), $"Script Cache Hash Path {data.ConfigScriptCacheHashPath} missing."); + var count = GetFiles(data.ConfigScriptCacheAssemblyPattern).Count(); + Assert.True(1 == count, $"Script Cache Assembly Path {data.ConfigScriptCacheAssemblyPattern.Pattern} expected 1 got {count}."); }); Task("Cake.ScriptCache") diff --git a/tests/integration/build.cake b/tests/integration/build.cake index 3f7a2c536a..d923d6455d 100644 --- a/tests/integration/build.cake +++ b/tests/integration/build.cake @@ -25,6 +25,7 @@ #load "./Cake.Common/Solution/Project/XmlDoc/XmlDocAliases.cake" #load "./Cake.Common/Text/TextTransformationAliases.cake" #load "./Cake.Common/Tools/Cake/CakeAliases.cake" +#load "./Cake.Common/Tools/Command/CommandAliases.cake" #load "./Cake.Common/Tools/DotNet/DotNetAliases.cake" #load "./Cake.Common/Tools/DotNetCore/DotNetCoreAliases.cake" #load "./Cake.Common/Tools/NuGet/NuGetAliases.cake" @@ -91,6 +92,7 @@ Task("Cake.Common") .IsDependentOn("Cake.Common.Solution.Project.XmlDoc.XmlDocAliases") .IsDependentOn("Cake.Common.Text.TextTransformationAliases") .IsDependentOn("Cake.Common.Tools.Cake.CakeAliases") + .IsDependentOn("Cake.Common.Tools.Command.CommandAliases") .IsDependentOn("Cake.Common.Tools.DotNet.DotNetAliases") .IsDependentOn("Cake.Common.Tools.DotNetCore.DotNetCoreAliases") .IsDependentOn("Cake.Common.Tools.NuGet.NuGetAliases") diff --git a/tests/integration/resources/Cake.Common/Tools/DotNet/hwapp.tests/hwapp.tests.csproj b/tests/integration/resources/Cake.Common/Tools/DotNet/hwapp.tests/hwapp.tests.csproj index 681b019590..ace9d98420 100644 --- a/tests/integration/resources/Cake.Common/Tools/DotNet/hwapp.tests/hwapp.tests.csproj +++ b/tests/integration/resources/Cake.Common/Tools/DotNet/hwapp.tests/hwapp.tests.csproj @@ -8,7 +8,7 @@ - +