diff --git a/src/Cake.Cli/Hosts/TreeScriptHost.cs b/src/Cake.Cli/Hosts/TreeScriptHost.cs index f07a819623..1f232f5920 100644 --- a/src/Cake.Cli/Hosts/TreeScriptHost.cs +++ b/src/Cake.Cli/Hosts/TreeScriptHost.cs @@ -9,14 +9,13 @@ using Cake.Core; using Cake.Core.Graph; using Cake.Core.Scripting; +using ThreadingTask = System.Threading.Tasks.Task; -namespace Cake.Cli -{ +namespace Cake.Cli { /// /// The script host used for showing task descriptions. /// - public sealed class TreeScriptHost : ScriptHost - { + public sealed class TreeScriptHost : ScriptHost { private const int _maxDepth = 0; private const string _cross = "├─"; private const string _corner = "└─"; @@ -31,97 +30,105 @@ public sealed class TreeScriptHost : ScriptHost /// The console. public TreeScriptHost(ICakeEngine engine, ICakeContext context, IConsole console) : base(engine, context) - { + { _console = console ?? throw new ArgumentNullException(nameof(console)); } /// - public override Task RunTargetAsync(string target) - { + public override Task RunTargetAsync(string target) { PrintTaskTree(); - - return System.Threading.Tasks.Task.FromResult(null); + return ThreadingTask.FromResult(null); } /// - public override Task RunTargetsAsync(IEnumerable targets) - { + public override Task RunTargetsAsync(IEnumerable targets) { PrintTaskTree(); - - return System.Threading.Tasks.Task.FromResult(null); + return ThreadingTask.FromResult(null); } private void PrintTaskTree() { - var topLevelTasks = GetTopLevelTasks(); + // Build the full graph once (includes Dependencies + Dependees) + var graph = CakeGraphBuilder.Build(Tasks); + var topLevelTasks = GetTopLevelTasks(graph); + _console.WriteLine(); - foreach (ICakeTaskInfo task in topLevelTasks) + foreach (var task in topLevelTasks) { - PrintTask(task, string.Empty, false, 0); + // root tasks start at depth 0, no branch characters yet + PrintTask(task, graph, string.Empty, isLast: false, depth: 0); _console.WriteLine(); } } - private List GetTopLevelTasks() + private List GetTopLevelTasks(CakeGraph graph) { // Display "Default" first, then alphabetical - var graph = CakeGraphBuilder.Build(Tasks); - return Tasks.Where(task => !graph.Edges.Any( - edge => edge.Start.Equals(task.Name, StringComparison.OrdinalIgnoreCase))) + // Top-level = tasks that never appear as Start (no outgoing edges) + return Tasks + .Where(task => !graph.Edges.Any( + edge => edge.Start.Equals(task.Name, StringComparison.OrdinalIgnoreCase))) .OrderByDescending(task => task.Name.Equals("Default", StringComparison.OrdinalIgnoreCase)) .ThenBy(task => task.Name, StringComparer.OrdinalIgnoreCase) .ToList(); } - private void PrintTask(ICakeTaskInfo task, string indent, bool isLast, int depth) - { + private void PrintTask( + ICakeTaskInfo task, + CakeGraph graph, + string indent, + bool isLast, + int depth) + { // Builds ASCII graph _console.Write(indent); - if (isLast) - { + if (isLast) { _console.Write(_corner); indent += " "; - } - else if (depth > 0) - { + } else if (depth > 0) { _console.Write(_cross); indent += _vertical; } PrintName(task, depth); - if ((_maxDepth > 0) && (depth >= _maxDepth)) - { + if ((_maxDepth > 0) && (depth >= _maxDepth)) { return; } - for (var i = 0; i < task.Dependencies.Count; i++) - { - // First() is safe as CakeGraphBuilder has already validated graph is valid - var childTask = Tasks - .Where(x => x.Name.Equals(task.Dependencies[i].Name, StringComparison.OrdinalIgnoreCase)) - .First(); + // Children = all tasks that have an edge Start -> End = current task. + // This respects both IsDependentOn and IsDependeeOf, + // because CakeGraphBuilder already encoded both into the graph. + var childTasks = graph.Edges + .Where(edge => edge.End.Equals(task.Name, StringComparison.OrdinalIgnoreCase)) + .Select(edge => Tasks.First(t => + t.Name.Equals(edge.Start, StringComparison.OrdinalIgnoreCase))) + .Distinct() + .OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + + for (var i = 0; i < childTasks.Count; i++) + { + var childTask = childTasks[i]; + var childIsLast = i == childTasks.Count - 1; - PrintTask(childTask, indent, i == (task.Dependencies.Count - 1), depth + 1); + PrintTask(childTask, graph, indent, childIsLast, depth + 1); } } - private void PrintName(ICakeTaskInfo task, int depth) - { + private void PrintName(ICakeTaskInfo task, int depth) { var originalColor = _console.ForegroundColor; if (depth == 0) - { + { _console.ForegroundColor = ConsoleColor.Cyan; } else if (task is CakeTask cakeTask && - (cakeTask.Actions.Any() || cakeTask.DelayedActions.Any())) - { + (cakeTask.Actions.Any() || cakeTask.DelayedActions.Any())) + { _console.ForegroundColor = ConsoleColor.Green; - } - else - { + } else { _console.ForegroundColor = ConsoleColor.Gray; } @@ -129,4 +136,4 @@ private void PrintName(ICakeTaskInfo task, int depth) _console.ForegroundColor = originalColor; } } -} \ No newline at end of file +}