From 80989427baa0fe605d16af4df5eed33bd827f706 Mon Sep 17 00:00:00 2001 From: Ruslan Mogilevskiy Date: Wed, 16 Oct 2024 12:44:29 +0300 Subject: [PATCH 1/3] Allow to define build task chains --- global.json | 2 +- src/Cake.Frosting.Example/TaskChains.cs | 85 +++++++++++++++++ src/Cake.Frosting/Cake.Frosting.csproj | 3 - src/Cake.Frosting/CakeHost.cs | 3 + .../Engines/FrostingDescriptionRunner.cs | 4 +- .../Internal/Engines/FrostingDryRunner.cs | 4 +- .../Internal/Engines/FrostingRunner.cs | 4 +- .../Internal/Engines/FrostingTreeRunner.cs | 4 +- src/Cake.Frosting/Internal/FrostingEngine.cs | 95 +++++-------------- .../TaskChains/CakeHostExtensions.cs | 38 ++++++++ src/Cake.Frosting/TaskChains/Chain.cs | 77 +++++++++++++++ .../TaskChains/ChainedTaskConfigurator.cs | 73 ++++++++++++++ .../TaskChains/DefaultTaskConfigurator.cs | 82 ++++++++++++++++ .../TaskChains/ITaskChainProvider.cs | 7 ++ .../TaskChains/ITaskConfigurator.cs | 19 ++++ .../TaskChains/TaskChainExtensions.cs | 38 ++++++++ src/Cake.Frosting/TaskChains/TaskChainItem.cs | 74 +++++++++++++++ .../TaskChains/TaskChainIterator.cs | 70 ++++++++++++++ 18 files changed, 601 insertions(+), 81 deletions(-) create mode 100644 src/Cake.Frosting.Example/TaskChains.cs create mode 100644 src/Cake.Frosting/TaskChains/CakeHostExtensions.cs create mode 100644 src/Cake.Frosting/TaskChains/Chain.cs create mode 100644 src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs create mode 100644 src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs create mode 100644 src/Cake.Frosting/TaskChains/ITaskChainProvider.cs create mode 100644 src/Cake.Frosting/TaskChains/ITaskConfigurator.cs create mode 100644 src/Cake.Frosting/TaskChains/TaskChainExtensions.cs create mode 100644 src/Cake.Frosting/TaskChains/TaskChainItem.cs create mode 100644 src/Cake.Frosting/TaskChains/TaskChainIterator.cs diff --git a/global.json b/global.json index 1204878d9d..b493f77456 100644 --- a/global.json +++ b/global.json @@ -3,7 +3,7 @@ "src" ], "sdk": { - "version": "8.0.402", + "version": "8.0.400", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/src/Cake.Frosting.Example/TaskChains.cs b/src/Cake.Frosting.Example/TaskChains.cs new file mode 100644 index 0000000000..eaaffffc1a --- /dev/null +++ b/src/Cake.Frosting.Example/TaskChains.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.Core.Diagnostics; +using Cake.Frosting; +using Cake.Frosting.TaskChains; + +public static class ChainedTasksProgram +{ + public static int MainDemo(string[] args) + { + return new CakeHost() + .UseContext() + .UseChainedTaskConfigurator() + .Run(args); + } +} + +class AppTaskChainProvider : ITaskChainProvider +{ + public TaskChainItem GetChain() + { + return Chain + .Task() + .Group("Do something", _ => + { + // Reference the task type. + _.Task() + // Reference the task by name ([TaskName("Task3: do something")]). + .Task("Task3: do something"); + }) + // Task chain inline groups example. + // .Group("Do something else", _ => + // { + // _.Task() + // .Group("Internal group", _ => + // { + // _.Task(); + // }) + // .Task("Task name"); + // }) + // The last task in the chain which the Default task will be dependent on to execute all the chain. + .Task(); + } +} + +[TaskName("Task1 chained")] +public sealed class Task1 : FrostingTask +{ + public override void Run(BuildContext context) + { + context.Log.Information("Task1"); + } +} + +public sealed class Task2 : FrostingTask +{ + public override void Run(BuildContext context) + { + context.Log.Information("Task2"); + } +} + +[TaskName("Task3: do something")] +public sealed class Task3 : FrostingTask +{ + public override void Run(BuildContext context) + { + context.Log.Information("Task3"); + } +} + +public sealed class FinalTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.Log.Information("FinalTask"); + } +} + +[TaskName("Default")] +public class ChainedDefaultTask : FrostingTask +{ +} \ No newline at end of file diff --git a/src/Cake.Frosting/Cake.Frosting.csproj b/src/Cake.Frosting/Cake.Frosting.csproj index 672899f081..35f10ade18 100644 --- a/src/Cake.Frosting/Cake.Frosting.csproj +++ b/src/Cake.Frosting/Cake.Frosting.csproj @@ -21,9 +21,6 @@ - - - true diff --git a/src/Cake.Frosting/CakeHost.cs b/src/Cake.Frosting/CakeHost.cs index 6a4ed7fdea..f610c9a6c3 100644 --- a/src/Cake.Frosting/CakeHost.cs +++ b/src/Cake.Frosting/CakeHost.cs @@ -13,6 +13,7 @@ using Cake.Core.Modules; using Cake.DotNetTool.Module; using Cake.Frosting.Internal; +using Cake.Frosting.TaskChains; using Cake.NuGet; using Microsoft.Extensions.DependencyInjection; using Spectre.Console.Cli; @@ -116,6 +117,8 @@ private ServiceCollection CreateServiceCollection() services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Cake.Frosting/Internal/Engines/FrostingDescriptionRunner.cs b/src/Cake.Frosting/Internal/Engines/FrostingDescriptionRunner.cs index 86e3b144a8..9889adc4c9 100644 --- a/src/Cake.Frosting/Internal/Engines/FrostingDescriptionRunner.cs +++ b/src/Cake.Frosting/Internal/Engines/FrostingDescriptionRunner.cs @@ -6,6 +6,7 @@ using Cake.Cli; using Cake.Core; using Cake.Core.Diagnostics; +using Cake.Frosting.TaskChains; namespace Cake.Frosting.Internal { @@ -14,9 +15,10 @@ internal sealed class FrostingDescriptionRunner : FrostingEngine tasks, + ITaskConfigurator taskConfigurator, IFrostingSetup setup = null, IFrostingTeardown teardown = null, IFrostingTaskSetup taskSetup = null, IFrostingTaskTeardown taskTeardown = null) - : base(host, engine, context, log, tasks, setup, teardown, taskSetup, taskTeardown) + : base(host, engine, context, log, tasks, taskConfigurator, setup, teardown, taskSetup, taskTeardown) { } } diff --git a/src/Cake.Frosting/Internal/Engines/FrostingDryRunner.cs b/src/Cake.Frosting/Internal/Engines/FrostingDryRunner.cs index 1a466b7093..60f4ffd586 100644 --- a/src/Cake.Frosting/Internal/Engines/FrostingDryRunner.cs +++ b/src/Cake.Frosting/Internal/Engines/FrostingDryRunner.cs @@ -6,6 +6,7 @@ using Cake.Cli; using Cake.Core; using Cake.Core.Diagnostics; +using Cake.Frosting.TaskChains; namespace Cake.Frosting.Internal { @@ -14,9 +15,10 @@ internal sealed class FrostingDryRunner : FrostingEngine host, ICakeEngine engine, IFrostingContext context, ICakeLog log, IEnumerable tasks, + ITaskConfigurator taskConfigurator, IFrostingSetup setup = null, IFrostingTeardown teardown = null, IFrostingTaskSetup taskSetup = null, IFrostingTaskTeardown taskTeardown = null) - : base(host, engine, context, log, tasks, setup, teardown, taskSetup, taskTeardown) + : base(host, engine, context, log, tasks, taskConfigurator, setup, teardown, taskSetup, taskTeardown) { } } diff --git a/src/Cake.Frosting/Internal/Engines/FrostingRunner.cs b/src/Cake.Frosting/Internal/Engines/FrostingRunner.cs index e30a3139be..731babe256 100644 --- a/src/Cake.Frosting/Internal/Engines/FrostingRunner.cs +++ b/src/Cake.Frosting/Internal/Engines/FrostingRunner.cs @@ -6,6 +6,7 @@ using Cake.Cli; using Cake.Core; using Cake.Core.Diagnostics; +using Cake.Frosting.TaskChains; namespace Cake.Frosting.Internal { @@ -14,9 +15,10 @@ internal sealed class FrostingRunner : FrostingEngine host, ICakeEngine engine, IFrostingContext context, ICakeLog log, IEnumerable tasks, + ITaskConfigurator taskConfigurator, IFrostingSetup setup = null, IFrostingTeardown teardown = null, IFrostingTaskSetup taskSetup = null, IFrostingTaskTeardown taskTeardown = null) - : base(host, engine, context, log, tasks, setup, teardown, taskSetup, taskTeardown) + : base(host, engine, context, log, tasks, taskConfigurator, setup, teardown, taskSetup, taskTeardown) { } } diff --git a/src/Cake.Frosting/Internal/Engines/FrostingTreeRunner.cs b/src/Cake.Frosting/Internal/Engines/FrostingTreeRunner.cs index bba22de94d..b8c2c7aab9 100644 --- a/src/Cake.Frosting/Internal/Engines/FrostingTreeRunner.cs +++ b/src/Cake.Frosting/Internal/Engines/FrostingTreeRunner.cs @@ -6,6 +6,7 @@ using Cake.Cli; using Cake.Core; using Cake.Core.Diagnostics; +using Cake.Frosting.TaskChains; namespace Cake.Frosting.Internal { @@ -14,9 +15,10 @@ internal sealed class FrostingTreeRunner : FrostingEngine public FrostingTreeRunner(TreeScriptHost host, ICakeEngine engine, IFrostingContext context, ICakeLog log, IEnumerable tasks, + ITaskConfigurator taskConfigurator, IFrostingSetup setup = null, IFrostingTeardown teardown = null, IFrostingTaskSetup taskSetup = null, IFrostingTaskTeardown taskTeardown = null) - : base(host, engine, context, log, tasks, setup, teardown, taskSetup, taskTeardown) + : base(host, engine, context, log, tasks, taskConfigurator, setup, teardown, taskSetup, taskTeardown) { } } diff --git a/src/Cake.Frosting/Internal/FrostingEngine.cs b/src/Cake.Frosting/Internal/FrostingEngine.cs index b2747e2c42..18e85db2f5 100644 --- a/src/Cake.Frosting/Internal/FrostingEngine.cs +++ b/src/Cake.Frosting/Internal/FrostingEngine.cs @@ -4,9 +4,13 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Cake.Common.Diagnostics; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.Scripting; +using Cake.Frosting.TaskChains; namespace Cake.Frosting.Internal { @@ -28,6 +32,7 @@ internal abstract class FrostingEngine : IFrostingEngine private readonly IFrostingTaskTeardown _taskTeardown; private readonly THost _host; private readonly ICakeEngine _engine; + private readonly ITaskConfigurator _taskConfigurator; public ExecutionSettings Settings => _host.Settings; @@ -35,6 +40,7 @@ protected FrostingEngine( THost host, ICakeEngine engine, IFrostingContext context, ICakeLog log, IEnumerable tasks, + ITaskConfigurator taskConfigurator, IFrostingSetup setup = null, IFrostingTeardown teardown = null, IFrostingTaskSetup taskSetup = null, @@ -49,6 +55,7 @@ protected FrostingEngine( _taskSetup = taskSetup; _taskTeardown = taskTeardown; _tasks = new List(tasks ?? Array.Empty()); + _taskConfigurator = taskConfigurator; } public CakeReport Run(IEnumerable targets) @@ -92,88 +99,30 @@ private void ConfigureLifetime() private void ConfigureTasks() { - if (_tasks == null) + if (_tasks != null) { - return; - } - - foreach (var task in _tasks) - { - var name = task.GetTaskName(); - _log.Debug("Registering task: {0}", name); - - // Get the task's context type. - if (!task.HasCompatibleContext(_context)) + foreach (var task in _tasks) { - const string format = "Task cannot be used since the context isn't convertible to {0}."; - _log.Warning(format, task.GetContextType().FullName); - } - else - { - // Register task with the Cake engine. - var cakeTask = _engine.RegisterTask(name); - - var description = task.GetTaskDescription(); - if (!string.IsNullOrWhiteSpace(description)) - { - cakeTask.Description(description); - } - - // Is the run method overridden? - if (task.IsRunOverridden(_context)) - { - cakeTask.Does(task.RunAsync); - } - - // Is the criteria method overridden? - if (task.IsShouldRunOverridden(_context)) - { - cakeTask.WithCriteria(task.ShouldRun, task.SkippedMessage); - } - - // Continue on error? - if (task.IsContinueOnError()) - { - cakeTask.ContinueOnError(); - } - - // Is the on error method overridden? - if (task.IsOnErrorOverridden(_context)) - { - cakeTask.OnError(exception => task.OnError(exception, _context)); - } - - // Is the finally method overridden? - if (task.IsFinallyOverridden(_context)) - { - cakeTask.Finally(() => task.Finally(_context)); - } + var name = task.GetTaskName(); + _log.Debug("Registering task: {0}", name); - // Add dependencies - foreach (var dependency in task.GetDependencies()) + // Get the task's context type. + if (!task.HasCompatibleContext(_context)) { - var dependencyName = dependency.GetTaskName(); - if (!typeof(IFrostingTask).IsAssignableFrom(dependency.Task)) - { - throw new FrostingException($"The dependency '{dependencyName}' is not a valid task."); - } - - cakeTask.IsDependentOn(dependencyName); + const string format = "Task cannot be used since the context isn't convertible to {0}."; + _log.Warning(format, task.GetContextType().FullName); + continue; } - // Add reverse dependencies - foreach (var dependee in task.GetReverseDependencies()) - { - var dependeeName = dependee.GetTaskName(); - if (!typeof(IFrostingTask).IsAssignableFrom(dependee.Task)) - { - throw new FrostingException($"The reverse dependency '{dependeeName}' is not a valid task."); - } + // Register task with the Cake engine. + var cakeTask = _engine.RegisterTask(name); - cakeTask.IsDependeeOf(dependeeName); - } + // Configure the task. + _taskConfigurator.Configure(task, cakeTask); } } + + _taskConfigurator.OnConfiguredAll(); } } } diff --git a/src/Cake.Frosting/TaskChains/CakeHostExtensions.cs b/src/Cake.Frosting/TaskChains/CakeHostExtensions.cs new file mode 100644 index 0000000000..c5f591698f --- /dev/null +++ b/src/Cake.Frosting/TaskChains/CakeHostExtensions.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Cake.Frosting.TaskChains +{ + /// + /// Extension methods for task chains. + /// + public static class CakeHostExtensions + { + /// + /// Registers the task configurator that will configure the build task upon startup. + /// + /// Task configurator type. + /// Cake host. + /// Cake host. + public static CakeHost UseTaskConfigurator(this CakeHost host) where T : class, ITaskConfigurator + { + host.ConfigureServices(services => { services.AddSingleton(); }); + return host; + } + + /// + /// Registers the chained task configurator that will configure the build task chains upon startup. + /// + /// Task configurator type. + /// Cake host. + /// Cake host. + public static CakeHost UseChainedTaskConfigurator(this CakeHost host) where T : class, ITaskChainProvider + { + host.ConfigureServices(services => + { + services.AddSingleton(); + services.AddSingleton(); + }); + return host; + } + } +} \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/Chain.cs b/src/Cake.Frosting/TaskChains/Chain.cs new file mode 100644 index 0000000000..8e99e6377e --- /dev/null +++ b/src/Cake.Frosting/TaskChains/Chain.cs @@ -0,0 +1,77 @@ +using System; + +namespace Cake.Frosting.TaskChains +{ + /// + /// The task chain builder. + /// + public static class Chain + { + /// + /// Adds the task that will be executed first in the build chain. + /// + /// The task type. + /// The task in the chain. + public static TaskChainItem Task() where T : IFrostingTask + { + return new TaskChainItem { TaskType = typeof(T) }; + } + + public static TaskChainItem Task(string taskName) + { + return new TaskChainItem { TaskName = taskName }; + } + + public static TaskChainItem Task(this TaskChainItem parent) where T : IFrostingTask + { + var nextItem = new TaskChainItem { TaskType = typeof(T) }; + parent.Next(nextItem); + return nextItem; + } + + public static TaskChainItem Task(this TaskChainItem parent, string taskName) + { + var nextItem = new TaskChainItem { TaskName = taskName }; + parent.Next(nextItem); + return nextItem; + } + + /// + /// Adds the group that contains the logically related tasks. + /// + /// The tasks in the group are executed sequentially. All the tasks that depend on this group will be executed after the latest + /// task in this group. + /// The parent task after which the tasks in the group will be executed. + /// The callback to configure the tasks in the group. + /// The task in the chain. + public static TaskChainItem Group(this TaskChainItem parent, Action groupChain) + { + return Group(parent, null, groupChain); + } + + /// + /// Adds the group that contains the logically related tasks. + /// + /// + /// The tasks in the group are executed sequentially. All the tasks that depend on this group will be executed after the latest + /// task in this group. + /// + /// The parent task after which the tasks in the group will be executed. + /// The human-readable group description to simplify understanding the purpose of the group. + /// The callback to configure the tasks in the group. + /// + /// Task chain configuration. + /// + public static TaskChainItem Group(this TaskChainItem parent, string description, Action groupChain) + { + var groupItem = new TaskChainProxyItem + { + Description = description + }; + parent.Next(groupItem); + groupItem.AddLeafItems(groupChain); + + return groupItem; + } + } +} \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs b/src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs new file mode 100644 index 0000000000..4f2862f963 --- /dev/null +++ b/src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Cake.Core; + +namespace Cake.Frosting.TaskChains +{ + public class ChainedTaskConfigurator : DefaultTaskConfigurator + { + private readonly TaskChainIterator _chainIterator; + + public ChainedTaskConfigurator(IFrostingContext context, ITaskChainProvider chainProvider) : base(context) + { + var chainLastItem = chainProvider.GetChain(); + if (chainLastItem == null) + { + throw new InvalidOperationException("No tasks are defined in the chain"); + } + _chainIterator = new(chainLastItem); + } + + public override void Configure(IFrostingTask task, CakeTaskBuilder cakeTask) + { + base.Configure(task, cakeTask); + + var chainItem = _chainIterator.FindReferencedItem(task); + if (chainItem != null) + { + foreach (var item in GetPreviousItems(chainItem)) + { + cakeTask.IsDependentOn(item.GetTaskName()); + } + } + + if (cakeTask.Task.Name.Equals("Default", StringComparison.OrdinalIgnoreCase)) + { + var lastTask = _chainIterator.GetLast(); + cakeTask.IsDependentOn(lastTask.GetTaskName()); + } + } + + IEnumerable GetPreviousItems(TaskChainItem forItem) + { + var previous = forItem.Previous; + + if (previous == null) + yield break; + + if (!previous.IsProxy) + { + yield return previous; + yield break; + } + + if (previous.IsGroup) + { + foreach (var item in previous.EnumerateLeafChain()) + { + yield return item; + } + yield break; + } + + var realParent = previous.GetRealParent(); + if (realParent != null) + { + foreach (var item in realParent.EnumerateLeafChain()) + { + yield return item; + } + } + } + } +} diff --git a/src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs b/src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs new file mode 100644 index 0000000000..f9a85cf2ee --- /dev/null +++ b/src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs @@ -0,0 +1,82 @@ +using Cake.Core; +using Cake.Frosting.Internal; + +namespace Cake.Frosting.TaskChains +{ + public class DefaultTaskConfigurator : ITaskConfigurator + { + private readonly IFrostingContext _context; + + public DefaultTaskConfigurator(IFrostingContext context) + { + _context = context; + } + + public void OnConfiguredAll() + { + } + + public virtual void Configure(IFrostingTask task, CakeTaskBuilder cakeTask) + { + var description = task.GetTaskDescription(); + if (!string.IsNullOrWhiteSpace(description)) + { + cakeTask.Description(description); + } + + // Is the run method overridden? + if (task.IsRunOverridden(_context)) + { + cakeTask.Does(task.RunAsync); + } + + // Is the criteria method overridden? + if (task.IsShouldRunOverridden(_context)) + { + cakeTask.WithCriteria(task.ShouldRun, task.SkippedMessage); + } + + // Continue on error? + if (task.IsContinueOnError()) + { + cakeTask.ContinueOnError(); + } + + // Is the on error method overridden? + if (task.IsOnErrorOverridden(_context)) + { + cakeTask.OnError(exception => task.OnError(exception, _context)); + } + + // Is the finally method overridden? + if (task.IsFinallyOverridden(_context)) + { + cakeTask.Finally(() => task.Finally(_context)); + } + + // Add dependencies (if not already added by the task execution chain) + foreach (var dependency in task.GetDependencies()) + { + var dependencyName = dependency.GetTaskName(); + if (!typeof(IFrostingTask).IsAssignableFrom(dependency.Task)) + { + throw new FrostingException($"The dependency '{dependencyName}' is not a valid task."); + } + + cakeTask.IsDependentOn(dependencyName); + } + + // Add reverse dependencies (if not already added by the task execution chain) + foreach (var dependee in task.GetReverseDependencies()) + { + var dependeeName = dependee.GetTaskName(); + if (!typeof(IFrostingTask).IsAssignableFrom(dependee.Task)) + { + throw new FrostingException($"The reverse dependency '{dependeeName}' is not a valid task."); + } + + cakeTask.IsDependeeOf(dependeeName); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/ITaskChainProvider.cs b/src/Cake.Frosting/TaskChains/ITaskChainProvider.cs new file mode 100644 index 0000000000..eb2c840bbb --- /dev/null +++ b/src/Cake.Frosting/TaskChains/ITaskChainProvider.cs @@ -0,0 +1,7 @@ +namespace Cake.Frosting.TaskChains +{ + public interface ITaskChainProvider + { + TaskChainItem GetChain(); + } +} \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/ITaskConfigurator.cs b/src/Cake.Frosting/TaskChains/ITaskConfigurator.cs new file mode 100644 index 0000000000..dbabda94f9 --- /dev/null +++ b/src/Cake.Frosting/TaskChains/ITaskConfigurator.cs @@ -0,0 +1,19 @@ +using Cake.Core; + +namespace Cake.Frosting.TaskChains +{ + public interface ITaskConfigurator + { + /// + /// Configures the specific task after it was added to the execution engine. + /// + /// The task class instance. + /// The task configuration in Cake engine. + void Configure(IFrostingTask task, CakeTaskBuilder cakeTask); + + /// + /// Called when all build tasks have been configured but not executed. + /// + void OnConfiguredAll(); + } +} \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/TaskChainExtensions.cs b/src/Cake.Frosting/TaskChains/TaskChainExtensions.cs new file mode 100644 index 0000000000..0766e6e8d2 --- /dev/null +++ b/src/Cake.Frosting/TaskChains/TaskChainExtensions.cs @@ -0,0 +1,38 @@ +using Cake.Frosting.Internal; + +namespace Cake.Frosting.TaskChains +{ + public static class TaskChainExtensions + { + public static void Next(this TaskChainItem parent, TaskChainItem next) + { + parent.Next = next; + next.Previous = parent; + } + + public static string GetTaskName(this TaskChainItem item) + { + return item.TaskName ?? item.TaskType.GetTaskName(); + } + + /// + /// Finds the first real parent tasks skipping all proxies. + /// + /// The child task which parent to find. + /// The parent task or null. + public static TaskChainItem GetRealParent(this TaskChainItem item) + { + var current = item.Previous; + while (current?.IsProxy == true) + { + var nextPrevious = current.Previous; + if (current.IsGroup && nextPrevious?.IsGroup == true) + { + return nextPrevious; + } + current = nextPrevious; + } + return current; + } + } +} \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/TaskChainItem.cs b/src/Cake.Frosting/TaskChains/TaskChainItem.cs new file mode 100644 index 0000000000..b050430f51 --- /dev/null +++ b/src/Cake.Frosting/TaskChains/TaskChainItem.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using Cake.Frosting.Internal; + +namespace Cake.Frosting.TaskChains +{ + public class TaskChainItem + { + public Type TaskType { get; set; } + public string TaskName { get; set; } + public TaskChainItem Next { get; set; } + public TaskChainItem Previous { get; set; } + protected virtual TaskChainItem Leaf => null; + public bool IsGroup => Leaf != null; + public virtual bool IsProxy => false; + + public virtual IEnumerable EnumerateLeafChain() + { + yield return this; + } + + public bool Equals(IFrostingTask task) + { + return (TaskType != null && TaskType == task.GetType()) || + (TaskName != null && TaskName.Equals(task.GetType().GetTaskName(), StringComparison.OrdinalIgnoreCase)); + } + } + + public class TaskChainProxyItem : TaskChainItem + { + public string Description { get; set; } + + TaskChainItem _leaf; + + public override bool IsProxy => true; + + protected override TaskChainItem Leaf => _leaf; + + public TaskChainProxyItem Owner => GetOwner(this); + + public void AddLeafItems(Action leafChainAction) + { + _leaf = new TaskChainProxyItem + { + Previous = this + }; + leafChainAction(Leaf); + } + + public override IEnumerable EnumerateLeafChain() + { + var nextChild = Owner.Leaf.Next; + while (nextChild != null) + { + foreach (var item in nextChild.EnumerateLeafChain()) + { + yield return item; + } + + nextChild = nextChild.Next; + } + } + + static TaskChainProxyItem GetOwner(TaskChainItem start) + { + var current = start; + while (current != null && !current.IsGroup) + { + current = current.Previous; + } + return (TaskChainProxyItem)current; + } + } +} diff --git a/src/Cake.Frosting/TaskChains/TaskChainIterator.cs b/src/Cake.Frosting/TaskChains/TaskChainIterator.cs new file mode 100644 index 0000000000..f2c7a4b268 --- /dev/null +++ b/src/Cake.Frosting/TaskChains/TaskChainIterator.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; + +namespace Cake.Frosting.TaskChains +{ + public class TaskChainIterator + { + private readonly TaskChainItem _firstItem; + + public TaskChainIterator(TaskChainItem chainItem) + { + ArgumentNullException.ThrowIfNull(chainItem); + + _firstItem = GetChainFirstItem(chainItem); + } + + static TaskChainItem GetChainFirstItem(TaskChainItem chainItem) + { + var current = chainItem; + while (current.Previous != null) + { + current = current.Previous; + } + + return current; + } + + public TaskChainItem FindReferencedItem(IFrostingTask task) + { + var currentItem = _firstItem; + while (currentItem != null) + { + foreach (var item in currentItem.EnumerateLeafChain()) + { + if (item.Equals(task)) + { + return item; + } + } + + currentItem = currentItem.Next; + } + + return null; + } + + public TaskChainItem GetLast() + { + return FindLastItem(_firstItem); + } + + private TaskChainItem FindLastItem(TaskChainItem start) + { + var currentItem = start; + TaskChainItem lastFoundItem = null; + + while (currentItem != null) + { + lastFoundItem = currentItem.EnumerateLeafChain().LastOrDefault() ?? lastFoundItem; + if (currentItem.Next == null) + { + break; + } + currentItem = currentItem.Next; + } + + return lastFoundItem; + } + } +} From 88458287054e847dd0cf582925cb5bf7f59dad84 Mon Sep 17 00:00:00 2001 From: Ruslan Mogilevskiy Date: Fri, 25 Oct 2024 20:13:04 +0300 Subject: [PATCH 2/3] Align the code style with project requirements --- src/Cake.Frosting/Internal/FrostingEngine.cs | 2 - src/Cake.Frosting/TaskChains/Chain.cs | 21 ++++- .../TaskChains/ChainedTaskConfigurator.cs | 24 ++++- .../TaskChains/DefaultTaskConfigurator.cs | 17 +++- .../TaskChains/ITaskChainProvider.cs | 7 ++ .../TaskChains/ITaskConfigurator.cs | 8 +- .../TaskChains/TaskChainExtensions.cs | 13 +++ src/Cake.Frosting/TaskChains/TaskChainItem.cs | 87 +++++++++---------- .../TaskChains/TaskChainIterator.cs | 18 +++- .../TaskChains/TaskChainProxyItem.cs | 76 ++++++++++++++++ 10 files changed, 211 insertions(+), 62 deletions(-) create mode 100644 src/Cake.Frosting/TaskChains/TaskChainProxyItem.cs diff --git a/src/Cake.Frosting/Internal/FrostingEngine.cs b/src/Cake.Frosting/Internal/FrostingEngine.cs index 18e85db2f5..d60bc8e753 100644 --- a/src/Cake.Frosting/Internal/FrostingEngine.cs +++ b/src/Cake.Frosting/Internal/FrostingEngine.cs @@ -121,8 +121,6 @@ private void ConfigureTasks() _taskConfigurator.Configure(task, cakeTask); } } - - _taskConfigurator.OnConfiguredAll(); } } } diff --git a/src/Cake.Frosting/TaskChains/Chain.cs b/src/Cake.Frosting/TaskChains/Chain.cs index 8e99e6377e..93ce1266ac 100644 --- a/src/Cake.Frosting/TaskChains/Chain.cs +++ b/src/Cake.Frosting/TaskChains/Chain.cs @@ -8,20 +8,31 @@ namespace Cake.Frosting.TaskChains public static class Chain { /// - /// Adds the task that will be executed first in the build chain. + /// Adds the task by its type that will be executed first in the build chain. /// /// The task type. - /// The task in the chain. + /// Task configuration. public static TaskChainItem Task() where T : IFrostingTask { return new TaskChainItem { TaskType = typeof(T) }; } + /// + /// Adds the task by its name that will be executed first in the build chain. + /// + /// Name of the task. + /// Task configuration. public static TaskChainItem Task(string taskName) { return new TaskChainItem { TaskName = taskName }; } + /// + /// Adds the task that will be executed after the specified task. + /// + /// The type of the task to execute. + /// The parent task after which the task will be executed. + /// Task configuration. public static TaskChainItem Task(this TaskChainItem parent) where T : IFrostingTask { var nextItem = new TaskChainItem { TaskType = typeof(T) }; @@ -29,6 +40,12 @@ public static TaskChainItem Task(this TaskChainItem parent) where T : IFrosti return nextItem; } + /// + /// Adds the task that will be executed after the specified task. + /// + /// The parent task after which the task will be executed. + /// Name of the task to execute. + /// Task configuration. public static TaskChainItem Task(this TaskChainItem parent, string taskName) { var nextItem = new TaskChainItem { TaskName = taskName }; diff --git a/src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs b/src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs index 4f2862f963..0f3cbe03af 100644 --- a/src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs +++ b/src/Cake.Frosting/TaskChains/ChainedTaskConfigurator.cs @@ -4,10 +4,18 @@ namespace Cake.Frosting.TaskChains { + /// + /// Applies the provided chain of tasks to the build. + /// public class ChainedTaskConfigurator : DefaultTaskConfigurator { private readonly TaskChainIterator _chainIterator; + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The chain provider. public ChainedTaskConfigurator(IFrostingContext context, ITaskChainProvider chainProvider) : base(context) { var chainLastItem = chainProvider.GetChain(); @@ -15,9 +23,14 @@ public ChainedTaskConfigurator(IFrostingContext context, ITaskChainProvider chai { throw new InvalidOperationException("No tasks are defined in the chain"); } - _chainIterator = new(chainLastItem); + _chainIterator = new (chainLastItem); } + /// + /// Configures the specific task after it was added to the execution engine. + /// + /// The task class instance. + /// The task configuration in Cake engine. public override void Configure(IFrostingTask task, CakeTaskBuilder cakeTask) { base.Configure(task, cakeTask); @@ -38,12 +51,19 @@ public override void Configure(IFrostingTask task, CakeTaskBuilder cakeTask) } } - IEnumerable GetPreviousItems(TaskChainItem forItem) + /// + /// Gets all the tasks that are higher in the hierarchy of the provided task. + /// + /// For child task which parents to find. + /// List of parent tasks. + private IEnumerable GetPreviousItems(TaskChainItem forItem) { var previous = forItem.Previous; if (previous == null) + { yield break; + } if (!previous.IsProxy) { diff --git a/src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs b/src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs index f9a85cf2ee..bd023ebcb0 100644 --- a/src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs +++ b/src/Cake.Frosting/TaskChains/DefaultTaskConfigurator.cs @@ -3,19 +3,28 @@ namespace Cake.Frosting.TaskChains { + /// + /// Configures the build task's default behavior. + /// + /// public class DefaultTaskConfigurator : ITaskConfigurator { private readonly IFrostingContext _context; + /// + /// Initializes a new instance of the class. + /// + /// The context. public DefaultTaskConfigurator(IFrostingContext context) { _context = context; } - public void OnConfiguredAll() - { - } - + /// + /// Configures the specific task after it was added to the execution engine. + /// + /// The task class instance. + /// The task configuration in Cake engine. public virtual void Configure(IFrostingTask task, CakeTaskBuilder cakeTask) { var description = task.GetTaskDescription(); diff --git a/src/Cake.Frosting/TaskChains/ITaskChainProvider.cs b/src/Cake.Frosting/TaskChains/ITaskChainProvider.cs index eb2c840bbb..3ea1de9c28 100644 --- a/src/Cake.Frosting/TaskChains/ITaskChainProvider.cs +++ b/src/Cake.Frosting/TaskChains/ITaskChainProvider.cs @@ -1,7 +1,14 @@ namespace Cake.Frosting.TaskChains { + /// + /// Provider the tasks chain to be applied to the build. + /// public interface ITaskChainProvider { + /// + /// Gets the tasks chain. + /// + /// The tasks chain. TaskChainItem GetChain(); } } \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/ITaskConfigurator.cs b/src/Cake.Frosting/TaskChains/ITaskConfigurator.cs index dbabda94f9..7f523cd5b4 100644 --- a/src/Cake.Frosting/TaskChains/ITaskConfigurator.cs +++ b/src/Cake.Frosting/TaskChains/ITaskConfigurator.cs @@ -2,6 +2,9 @@ namespace Cake.Frosting.TaskChains { + /// + /// Configures the build tasks chain. + /// public interface ITaskConfigurator { /// @@ -10,10 +13,5 @@ public interface ITaskConfigurator /// The task class instance. /// The task configuration in Cake engine. void Configure(IFrostingTask task, CakeTaskBuilder cakeTask); - - /// - /// Called when all build tasks have been configured but not executed. - /// - void OnConfiguredAll(); } } \ No newline at end of file diff --git a/src/Cake.Frosting/TaskChains/TaskChainExtensions.cs b/src/Cake.Frosting/TaskChains/TaskChainExtensions.cs index 0766e6e8d2..cd4ceacb79 100644 --- a/src/Cake.Frosting/TaskChains/TaskChainExtensions.cs +++ b/src/Cake.Frosting/TaskChains/TaskChainExtensions.cs @@ -2,14 +2,27 @@ namespace Cake.Frosting.TaskChains { + /// + /// Tasks chain extensions. + /// public static class TaskChainExtensions { + /// + /// Sets the next task to be executed in the chain. + /// + /// The task whose next task to set. + /// The next task to execute. public static void Next(this TaskChainItem parent, TaskChainItem next) { parent.Next = next; next.Previous = parent; } + /// + /// Gets the specified task name. + /// + /// The task which name to get. + /// Task name. public static string GetTaskName(this TaskChainItem item) { return item.TaskName ?? item.TaskType.GetTaskName(); diff --git a/src/Cake.Frosting/TaskChains/TaskChainItem.cs b/src/Cake.Frosting/TaskChains/TaskChainItem.cs index b050430f51..33d2114a18 100644 --- a/src/Cake.Frosting/TaskChains/TaskChainItem.cs +++ b/src/Cake.Frosting/TaskChains/TaskChainItem.cs @@ -4,71 +4,66 @@ namespace Cake.Frosting.TaskChains { + /// + /// Configuration of the task in the chain. + /// public class TaskChainItem { + /// + /// Gets or sets the type of the task to execute in the chain. + /// public Type TaskType { get; set; } + + /// + /// Gets or sets the name of the task to execute in the chain. + /// public string TaskName { get; set; } + + /// + /// Gets or sets the next task to execute. + /// public TaskChainItem Next { get; set; } + + /// + /// Gets or sets the previous task to execute. + /// public TaskChainItem Previous { get; set; } + + /// + /// Gets the child tasks. + /// protected virtual TaskChainItem Leaf => null; + + /// + /// Gets a value indicating whether this task is a group of tasks. + /// + /// The group of tasks organizes other tasks to run sequentially and ensures that subsequent tasks wait until all grouped + /// tasks are finished. public bool IsGroup => Leaf != null; + + /// + /// Gets a value indicating whether the task is a proxy-link between the real tasks. The proxy behaves as a group. + /// public virtual bool IsProxy => false; + /// + /// Enumerates the child tasks in the chain. + /// + /// The child tasks. public virtual IEnumerable EnumerateLeafChain() { yield return this; } + /// + /// Compares the task with the specified task. + /// + /// The task. + /// true if the task is equal to the specified task; otherwise, false. public bool Equals(IFrostingTask task) { return (TaskType != null && TaskType == task.GetType()) || (TaskName != null && TaskName.Equals(task.GetType().GetTaskName(), StringComparison.OrdinalIgnoreCase)); } } - - public class TaskChainProxyItem : TaskChainItem - { - public string Description { get; set; } - - TaskChainItem _leaf; - - public override bool IsProxy => true; - - protected override TaskChainItem Leaf => _leaf; - - public TaskChainProxyItem Owner => GetOwner(this); - - public void AddLeafItems(Action leafChainAction) - { - _leaf = new TaskChainProxyItem - { - Previous = this - }; - leafChainAction(Leaf); - } - - public override IEnumerable EnumerateLeafChain() - { - var nextChild = Owner.Leaf.Next; - while (nextChild != null) - { - foreach (var item in nextChild.EnumerateLeafChain()) - { - yield return item; - } - - nextChild = nextChild.Next; - } - } - - static TaskChainProxyItem GetOwner(TaskChainItem start) - { - var current = start; - while (current != null && !current.IsGroup) - { - current = current.Previous; - } - return (TaskChainProxyItem)current; - } - } } diff --git a/src/Cake.Frosting/TaskChains/TaskChainIterator.cs b/src/Cake.Frosting/TaskChains/TaskChainIterator.cs index f2c7a4b268..8708b65c0d 100644 --- a/src/Cake.Frosting/TaskChains/TaskChainIterator.cs +++ b/src/Cake.Frosting/TaskChains/TaskChainIterator.cs @@ -3,10 +3,17 @@ namespace Cake.Frosting.TaskChains { + /// + /// Iterates the task chain. + /// public class TaskChainIterator { private readonly TaskChainItem _firstItem; + /// + /// Initializes a new instance of the class. + /// + /// The chain item. public TaskChainIterator(TaskChainItem chainItem) { ArgumentNullException.ThrowIfNull(chainItem); @@ -14,7 +21,7 @@ public TaskChainIterator(TaskChainItem chainItem) _firstItem = GetChainFirstItem(chainItem); } - static TaskChainItem GetChainFirstItem(TaskChainItem chainItem) + private static TaskChainItem GetChainFirstItem(TaskChainItem chainItem) { var current = chainItem; while (current.Previous != null) @@ -25,6 +32,11 @@ static TaskChainItem GetChainFirstItem(TaskChainItem chainItem) return current; } + /// + /// Finds the task configuration that references the specified task. + /// + /// The task which reference to find. + /// Task configuration or null if not found. public TaskChainItem FindReferencedItem(IFrostingTask task) { var currentItem = _firstItem; @@ -44,6 +56,10 @@ public TaskChainItem FindReferencedItem(IFrostingTask task) return null; } + /// + /// Gets the last task in the chain. + /// + /// The last task in the chain. public TaskChainItem GetLast() { return FindLastItem(_firstItem); diff --git a/src/Cake.Frosting/TaskChains/TaskChainProxyItem.cs b/src/Cake.Frosting/TaskChains/TaskChainProxyItem.cs new file mode 100644 index 0000000000..516e04ab6a --- /dev/null +++ b/src/Cake.Frosting/TaskChains/TaskChainProxyItem.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; + +namespace Cake.Frosting.TaskChains +{ + /// + /// A proxy-task task type that does nothing but aggregates other tasks. + /// + public class TaskChainProxyItem : TaskChainItem + { + /// + /// Gets or sets the human-readable description of the tasks in the group. + /// + public string Description { get; set; } + + private TaskChainItem _leaf; + + /// + /// Gets a value indicating whether the task is a proxy-link between the real tasks. The proxy behaves as a group. + /// + public override bool IsProxy => true; + + /// + /// Gets the child tasks. + /// + protected override TaskChainItem Leaf => _leaf; + + /// + /// Gets the task that owns this proxy. + /// + public TaskChainProxyItem Owner => GetOwner(this); + + /// + /// Adds the child tasks. + /// + /// An action that configures the child tasks. + public void AddLeafItems(Action leafChainAction) + { + _leaf = new TaskChainProxyItem + { + Previous = this + }; + leafChainAction(Leaf); + } + + /// + /// Enumerates the child tasks in the chain. + /// + /// + /// The child tasks. + /// + public override IEnumerable EnumerateLeafChain() + { + var nextChild = Owner.Leaf.Next; + while (nextChild != null) + { + foreach (var item in nextChild.EnumerateLeafChain()) + { + yield return item; + } + + nextChild = nextChild.Next; + } + } + + private static TaskChainProxyItem GetOwner(TaskChainItem start) + { + var current = start; + while (current != null && !current.IsGroup) + { + current = current.Previous; + } + return (TaskChainProxyItem)current; + } + } +} \ No newline at end of file From f3305413ceaab6695076b88b2e865ae2a5957964 Mon Sep 17 00:00:00 2001 From: Ruslan Mogilevskiy Date: Fri, 25 Oct 2024 20:32:08 +0300 Subject: [PATCH 3/3] Fix code styling --- src/Cake.Frosting.Example/TaskChains.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cake.Frosting.Example/TaskChains.cs b/src/Cake.Frosting.Example/TaskChains.cs index eaaffffc1a..801ad94cb2 100644 --- a/src/Cake.Frosting.Example/TaskChains.cs +++ b/src/Cake.Frosting.Example/TaskChains.cs @@ -17,7 +17,10 @@ public static int MainDemo(string[] args) } } -class AppTaskChainProvider : ITaskChainProvider +/// +/// Tasks chain provider. +/// +public class AppTaskChainProvider : ITaskChainProvider { public TaskChainItem GetChain() {