summaryrefslogtreecommitdiff
path: root/src/marlin.core/models/plan
diff options
context:
space:
mode:
Diffstat (limited to 'src/marlin.core/models/plan')
-rw-r--r--src/marlin.core/models/plan/CommandResult.cs17
-rw-r--r--src/marlin.core/models/plan/CommandResultSelector.cs13
-rw-r--r--src/marlin.core/models/plan/CommandRole.cs8
-rw-r--r--src/marlin.core/models/plan/ExecutionCommand.cs15
-rw-r--r--src/marlin.core/models/plan/ExecutionPlan.cs174
5 files changed, 227 insertions, 0 deletions
diff --git a/src/marlin.core/models/plan/CommandResult.cs b/src/marlin.core/models/plan/CommandResult.cs
new file mode 100644
index 0000000..3385e84
--- /dev/null
+++ b/src/marlin.core/models/plan/CommandResult.cs
@@ -0,0 +1,17 @@
+namespace marlin.core.models.plan;
+
+[Flags]
+public enum CommandResult : byte
+{
+ Success = 1,
+ Failure = 2,
+ Unconditional = Success | Failure,
+}
+
+public static class CommandResultExtensions
+{
+ public static bool Matches(this CommandResult self, CommandResult contains)
+ {
+ return (self & contains) != 0;
+ }
+} \ No newline at end of file
diff --git a/src/marlin.core/models/plan/CommandResultSelector.cs b/src/marlin.core/models/plan/CommandResultSelector.cs
new file mode 100644
index 0000000..92c41c7
--- /dev/null
+++ b/src/marlin.core/models/plan/CommandResultSelector.cs
@@ -0,0 +1,13 @@
+namespace marlin.core.models.plan;
+
+public class CommandResultSelector
+{
+ public ExecutionCommand Command { get; }
+ public CommandResult Result { get; }
+
+ public CommandResultSelector(ExecutionCommand command, CommandResult result)
+ {
+ this.Command = command;
+ this.Result = result;
+ }
+} \ No newline at end of file
diff --git a/src/marlin.core/models/plan/CommandRole.cs b/src/marlin.core/models/plan/CommandRole.cs
new file mode 100644
index 0000000..8005532
--- /dev/null
+++ b/src/marlin.core/models/plan/CommandRole.cs
@@ -0,0 +1,8 @@
+namespace marlin.core.models.plan;
+
+public enum CommandRole
+{
+ Step = 0,
+ ArtifactHandler = 1,
+ Cleanup = 2
+} \ No newline at end of file
diff --git a/src/marlin.core/models/plan/ExecutionCommand.cs b/src/marlin.core/models/plan/ExecutionCommand.cs
new file mode 100644
index 0000000..124da63
--- /dev/null
+++ b/src/marlin.core/models/plan/ExecutionCommand.cs
@@ -0,0 +1,15 @@
+namespace marlin.core.models.plan;
+
+using meta;
+
+public class ExecutionCommand
+{
+ public required string Id;
+ public required string Name;
+ public required string Command;
+ public required CommandRole Role;
+ public required bool Deferred;
+ public CommandResultSelector? ResultSelector;
+
+ public required FailBehavior FailBehavior;
+} \ No newline at end of file
diff --git a/src/marlin.core/models/plan/ExecutionPlan.cs b/src/marlin.core/models/plan/ExecutionPlan.cs
new file mode 100644
index 0000000..24c40e4
--- /dev/null
+++ b/src/marlin.core/models/plan/ExecutionPlan.cs
@@ -0,0 +1,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);
+ }
+}