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 @@
-
+