diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 34da0a65b..4f7585976 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.Runner.Common; using GitHub.Runner.Sdk; using GitHub.Runner.Worker.Container; @@ -486,9 +487,45 @@ namespace GitHub.Runner.Worker } else if (action.Reference.Type == Pipelines.ActionSourceType.Script) { - definition.Data.Execution = new ScriptActionExecutionData(); - definition.Data.Name = "Run"; - definition.Data.Description = "Execute a script"; + // Load the inputs. + executionContext.Debug("Loading inputs"); + var templateEvaluator = executionContext.ToPipelineTemplateEvaluator(); + var inputs = templateEvaluator.EvaluateStepInputs(action.Inputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions); + + // Check if we are running a Makefile. + // Only works for the default target right now. + if (inputs["script"] == "make") + { + // Get the path of the Makefile in the repository root. + var githubContext = executionContext.ExpressionValues["github"] as GitHubContext; + var workspaceDir = githubContext["workspace"] as StringContextData; + var makefile = Path.Combine(workspaceDir, "Makefile"); + if (!File.Exists(makefile)) + { + // Forget about trying to be smart. Just do the normal thing. + definition.Data.Execution = new ScriptActionExecutionData(); + definition.Data.Name = "Run"; + definition.Data.Description = "Execute a script"; + } + + // Assume the default target is named `all`. + var definitionData = MakefileManager.Load(executionContext, makefile, target: "all"); + if (definitionData is null) + { + // Forget about trying to be smart. Just do the normal thing. + definition.Data.Execution = new ScriptActionExecutionData(); + definition.Data.Name = "Run"; + definition.Data.Description = "Execute a script"; + } + + definition.Data = definitionData; + } + else + { + definition.Data.Execution = new ScriptActionExecutionData(); + definition.Data.Name = "Run"; + definition.Data.Description = "Execute a script"; + } } else { @@ -1143,6 +1180,7 @@ namespace GitHub.Runner.Worker Plugin, Script, Composite, + Makefile, } public sealed class ContainerActionExecutionData : ActionExecutionData @@ -1210,6 +1248,14 @@ namespace GitHub.Runner.Worker public MappingToken Outputs { get; set; } } + public sealed class MakefileExecutionData : ActionExecutionData + { + public override ActionExecutionType ExecutionType => ActionExecutionType.Makefile; + public override bool HasPre => false; + public override bool HasPost => false; + public List Targets { get; set; } + } + public abstract class ActionExecutionData { private string _initCondition = $"{Constants.Expressions.Always}()"; diff --git a/src/Runner.Worker/Handlers/HandlerFactory.cs b/src/Runner.Worker/Handlers/HandlerFactory.cs index 2b3b98142..a29d6bdd9 100644 --- a/src/Runner.Worker/Handlers/HandlerFactory.cs +++ b/src/Runner.Worker/Handlers/HandlerFactory.cs @@ -73,6 +73,11 @@ namespace GitHub.Runner.Worker.Handlers handler = HostContext.CreateService(); (handler as ICompositeActionHandler).Data = data as CompositeActionExecutionData; } + else if (data.ExecutionType == ActionExecutionType.Makefile) + { + handler = HostContext.CreateService(); + (handler as IMakefileHandler).Data = data as MakefileExecutionData; + } else { // This should never happen. diff --git a/src/Runner.Worker/Handlers/MakefileHandler.cs b/src/Runner.Worker/Handlers/MakefileHandler.cs new file mode 100644 index 000000000..9e1c857e3 --- /dev/null +++ b/src/Runner.Worker/Handlers/MakefileHandler.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.Pipelines.ContextData; +using GitHub.DistributedTask.Pipelines.ObjectTemplating; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Common; +using GitHub.Runner.Common.Util; +using GitHub.Runner.Sdk; +using GitHub.Runner.Worker; +using GitHub.Runner.Worker.Expressions; +using Pipelines = GitHub.DistributedTask.Pipelines; + + +namespace GitHub.Runner.Worker.Handlers +{ + [ServiceLocator(Default = typeof(MakefileHandler))] + public interface IMakefileHandler : IHandler + { + MakefileExecutionData Data { get; set; } + } + public sealed class MakefileHandler : Handler, IMakefileHandler + { + public MakefileExecutionData Data { get; set; } + + public async Task RunAsync(ActionRunStage stage) + { + // Validate args + Trace.Entering(); + ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); + ArgUtil.NotNull(Inputs, nameof(Inputs)); + + // Create a script handler for each target + var handlers = Data.Targets.Select(target => + { + var handler = HostContext.CreateService(); + + // IScriptHandler does not need .Action + // handler.Action = action; + handler.Data = new ScriptActionExecutionData(); + handler.Environment = Environment; + handler.RuntimeVariables = RuntimeVariables; + handler.ExecutionContext = ExecutionContext; + handler.StepHost = StepHost; + handler.Inputs = new Dictionary + { + ["script"] = $"make {target}" + }; + handler.ActionDirectory = ActionDirectory; + handler.LocalActionContainerSetupSteps = LocalActionContainerSetupSteps; + + return handler; + }); + + foreach (var handler in handlers) + { + await handler.RunAsync(stage); + } + } + } +} diff --git a/src/Runner.Worker/Handlers/ScriptHandler.cs b/src/Runner.Worker/Handlers/ScriptHandler.cs index da395c27a..e967b8117 100644 --- a/src/Runner.Worker/Handlers/ScriptHandler.cs +++ b/src/Runner.Worker/Handlers/ScriptHandler.cs @@ -31,8 +31,8 @@ namespace GitHub.Runner.Worker.Handlers Inputs.TryGetValue("script", out string contents); contents = contents ?? string.Empty; - if (Action.Type == Pipelines.ActionSourceType.Script) - { + // if (Action.Type == Pipelines.ActionSourceType.Script) + // { var firstLine = contents.TrimStart(' ', '\t', '\r', '\n'); var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' }); if (firstNewLine >= 0) @@ -41,11 +41,11 @@ namespace GitHub.Runner.Worker.Handlers } ExecutionContext.Output($"##[group]Run {firstLine}"); - } - else - { - throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}"); - } + // } + // else + // { + // throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}"); + // } var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n'); foreach (var line in multiLines) diff --git a/src/Runner.Worker/MakefileManager.cs b/src/Runner.Worker/MakefileManager.cs new file mode 100644 index 000000000..c7b833d9d --- /dev/null +++ b/src/Runner.Worker/MakefileManager.cs @@ -0,0 +1,39 @@ +using System.IO; +using System.Linq; + +namespace GitHub.Runner.Worker +{ + public static class MakefileManager + { + // Convert the `all` target to a set of steps of its dependencies. + // Does not recurse into the dependencies of those steps. + public static ActionDefinitionData Load(IExecutionContext executionContext, string makefile, string target) + { + var targetToFind = target + ":"; + var lines = File.ReadLines(makefile); + string targetLine = lines.FirstOrDefault(line => line.TrimStart().StartsWith(targetToFind)); + if (targetLine is null) + { + return null; + } + + var dependencies = targetLine.Split().Skip(1).ToList(); + if (dependencies.Count == 0) + { + return null; + } + + return new ActionDefinitionData + { + Name = $"make {target}", + Description = "Execute a Makefile target", + Execution = new MakefileExecutionData + { + Targets = dependencies, + InitCondition = "always()", + CleanupCondition = "always()", + } + }; + } + } +} \ No newline at end of file