diff options
| author | Melodi <62381002+Melodi17@users.noreply.github.com> | 2026-09-13 13:22:20 +1000 |
|---|---|---|
| committer | Melodi <62381002+Melodi17@users.noreply.github.com> | 2026-09-13 13:22:20 +1000 |
| commit | df0737e55afeb5c01eb39909605970d1d3a73ffc (patch) | |
| tree | 09823eaa310c60eefd2420dc305acae45c81900f /src/marlin.core/models/plan | |
| parent | ee69ce25b2fba871920260028bb10c2d6b62b26f (diff) | |
refactor: cleanup; feat: connection to agent
Diffstat (limited to 'src/marlin.core/models/plan')
| -rw-r--r-- | src/marlin.core/models/plan/CommandResult.cs | 17 | ||||
| -rw-r--r-- | src/marlin.core/models/plan/CommandResultSelector.cs | 13 | ||||
| -rw-r--r-- | src/marlin.core/models/plan/CommandRole.cs | 8 | ||||
| -rw-r--r-- | src/marlin.core/models/plan/ExecutionCommand.cs | 15 | ||||
| -rw-r--r-- | src/marlin.core/models/plan/ExecutionPlan.cs | 174 |
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); + } +} |
