summaryrefslogtreecommitdiff
path: root/src/marlin.core/models/plan/ExecutionPlan.cs
blob: 24c40e438037258cb809da67e32fdada3c037bbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
namespace marlin.core.models.plan;

using meta;
using util;

public class ExecutionPlan
{
    public IEnumerable<ExecutionCommand> Components => [..this._commands, ..this._deferredArtifactHandler, ..this._deferredCleanup];

    private readonly List<ExecutionCommand> _commands;
    private readonly List<ExecutionCommand> _deferredArtifactHandler;
    private readonly List<ExecutionCommand> _deferredCleanup;

    public ExecutionPlan()
    {
        this._commands = new();
        this._deferredArtifactHandler = new();
        this._deferredCleanup = new();
    }

    private void AddCommand(ExecutionCommand command)
    {
        if (!command.Deferred)
            this._commands.Add(command);

        else
        {
            switch (command.Role)
            {
                case CommandRole.Cleanup:
                    this._deferredCleanup.Add(command);
                    break;

                case CommandRole.ArtifactHandler:
                    this._deferredArtifactHandler.Add(command);
                    break;

                default:
                    throw new InvalidOperationException($"Cannot defer {command.Role}");
            }
        }
    }

    public static ExecutionPlan Create(Workflow workflow, Recipe recipe)
    {
        ExecutionPlan plan = new();

        AddToExecutionPlan(plan, workflow, recipe, null);

        return plan;
    }

    private static ExecutionCommand? AddToExecutionPlan(ExecutionPlan plan, Workflow workflow, Recipe recipe, ExecutionCommand? previousCommand)
    {
        foreach (var dependentRecipe in recipe.GetRequires(workflow))
            previousCommand = AddToExecutionPlan(plan, workflow, dependentRecipe, previousCommand);

        Dictionary<string, string> recipeArgs = recipe.CreateArguments();

        foreach (var step in recipe.Steps.Values)
        {
            var command = CreateCommand(step, recipe, workflow, false, recipeArgs, out var commandArgs);
            if (previousCommand != null)
            {
                command.ResultSelector = new CommandResultSelector(previousCommand,
                    previousCommand.FailBehavior switch
                    {
                        FailBehavior.Continue => CommandResult.Unconditional,
                        FailBehavior.Stop => CommandResult.Success,
                        FailBehavior.StopAfter => CommandResult.Success,

                        _ => throw new ArgumentOutOfRangeException()
                    });
            }

            plan.AddCommand(command);

            var cleanupCommand = CreateCleanupCommand(step, command, recipe, workflow, commandArgs);
            if (cleanupCommand != null)
                plan.AddCommand(cleanupCommand);

            previousCommand = command;
        }

        return previousCommand;
    }

    private static ExecutionCommand CreateCommand(
        AbstractStep step,
        Recipe recipe,
        Workflow workflow,
        bool defer,
        Dictionary<string, string> baseArgs,
        out Dictionary<string, string> createdArgs)
    {
        string? name = null;
        string? run = null;
        FailBehavior? fail = null;

        Dictionary<string, string> args = new(baseArgs);

        foreach (var stepHierarchy in step.GetAncestorsAndThis(workflow))
        {
            name ??= stepHierarchy.Name;
            run ??= stepHierarchy.Run;
            fail ??= stepHierarchy.Fail;

            foreach (var kvp in stepHierarchy.With)
                args.TryAdd(kvp.Key, kvp.Value);
        }

        name ??= step.Id;
        fail ??= FailBehavior.Stop;

        if (run == null)
            throw new Exception($"No run found for {recipe.Name}.{name}");

        run = VariableExpander.Expand(run, args);

        ExecutionCommand stepCommand = new ExecutionCommand
        {
            Id = step.Id,
            Name = name,
            Command = run,
            Role = step switch
            {
                ArtifactHandlerStep => CommandRole.ArtifactHandler,
                RecipeStep => CommandRole.Step,
                CleanupStep => CommandRole.Cleanup,
                _ => throw new ArgumentOutOfRangeException()
            },
            FailBehavior = fail.Value,
            Deferred = defer
        };

        createdArgs = args;
        return stepCommand;
    }

    private static ExecutionCommand? CreateCleanupCommand(RecipeStep parentStep,
        ExecutionCommand parentCommand,
        Recipe recipe,
        Workflow workflow,
        Dictionary<string, string> baseArgs)
    {
        var cleanupStep = GetCleanup(parentStep, workflow);
        if (cleanupStep == null)
            return null;

        var cleanupCommand = CreateCommand(cleanupStep, recipe, workflow,
            cleanupStep.After == CleanupAfter.Recipe, baseArgs, out _);

        cleanupCommand.ResultSelector = new CommandResultSelector(parentCommand,
            parentCommand.FailBehavior switch
            {
                FailBehavior.Stop => CommandResult.Success,
                FailBehavior.StopAfter => CommandResult.Unconditional,
                FailBehavior.Continue => CommandResult.Unconditional,

                _ => throw new ArgumentOutOfRangeException()
            });

        return cleanupCommand;
    }

    private static CleanupStep? GetCleanup(RecipeStep step, Workflow workflow)
    {
        return step
            .GetAncestorsAndThis(workflow)
            .OfType<RecipeStep>()
            .Select(x => x.Cleanup)
            .FirstOrDefault(x => x != null);
    }
}