// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using System.Xml; using EpicGames.Core; namespace AutomationTool.Tasks { /// /// Parameters for a spawn task /// public class SpawnTaskParameters { /// /// Executable to spawn. /// [TaskParameter] public string Exe { get; set; } /// /// Arguments for the newly created process. /// [TaskParameter(Optional = true)] public string Arguments { get; set; } /// /// Working directory for spawning the new task /// [TaskParameter(Optional = true)] public string WorkingDir { get; set; } /// /// Environment variables to set /// [TaskParameter(Optional = true)] public string Environment { get; set; } /// /// File to read environment from /// [TaskParameter(Optional = true)] public string EnvironmentFile { get; set; } /// /// Write output to the log /// [TaskParameter(Optional = true)] public bool LogOutput { get; set; } = true; /// /// The minimum exit code, which is treated as an error. /// [TaskParameter(Optional = true)] public int ErrorLevel { get; set; } = 1; } /// /// Base class for tasks that run an external tool /// public abstract class SpawnTaskBase : BgTaskImpl { /// /// ExecuteAsync a command /// protected static Task ExecuteAsync(string exe, string arguments, string workingDir = null, Dictionary envVars = null, bool logOutput = true, int errorLevel = 1, string input = null, ProcessResult.SpewFilterCallbackType spewFilterCallback = null) { if (workingDir != null) { workingDir = ResolveDirectory(workingDir).FullName; } CommandUtils.ERunOptions options = CommandUtils.ERunOptions.Default; if (!logOutput) { options |= CommandUtils.ERunOptions.SpewIsVerbose; } IProcessResult result = CommandUtils.Run(exe, arguments, Env: envVars, WorkingDir: workingDir, Options: options, Input: input, SpewFilterCallback: spewFilterCallback); if (result.ExitCode < 0 || result.ExitCode >= errorLevel) { throw new AutomationException("{0} terminated with an exit code indicating an error ({1})", Path.GetFileName(exe), result.ExitCode); } return Task.FromResult(result); } /// /// Parses environment from a property and file /// /// /// /// protected static Dictionary ParseEnvVars(string environment, string environmentFile) { Dictionary envVars = new Dictionary(); if (environment != null) { ParseEnvironment(environment, ';', envVars); } if (!String.IsNullOrEmpty(environmentFile)) { ParseEnvironment(FileUtils.ReadAllText(ResolveFile(environmentFile)), '\n', envVars); } return envVars; } /// /// Parse environment from a string /// /// /// /// static void ParseEnvironment(string environment, char separator, Dictionary envVars) { for (int baseIdx = 0; baseIdx < environment.Length;) { int endIdx = environment.IndexOf(separator, baseIdx); if (endIdx == -1) { endIdx = environment.Length; } string line = environment.Substring(baseIdx, endIdx - baseIdx); if (!String.IsNullOrWhiteSpace(line)) { int equalsIdx = line.IndexOf('=', StringComparison.Ordinal); if (equalsIdx == -1) { throw new AutomationException("Missing value in environment variable string '{0}'", environment.Substring(baseIdx, endIdx - baseIdx)); } string name = line.Substring(0, equalsIdx).Trim(); string value = line.Substring(equalsIdx + 1).Trim(); envVars[name] = value; } baseIdx = endIdx + 1; } } } /// /// Spawns an external executable and waits for it to complete. /// [TaskElement("Spawn", typeof(SpawnTaskParameters))] public class SpawnTask : SpawnTaskBase { /// /// Parameters for this task /// readonly SpawnTaskParameters _parameters; /// /// Construct a spawn task /// /// Parameters for the task public SpawnTask(SpawnTaskParameters parameters) { _parameters = parameters; } /// /// ExecuteAsync the task. /// /// Information about the current job /// Set of build products produced by this node. /// Mapping from tag names to the set of files they include public override async Task ExecuteAsync(JobContext job, HashSet buildProducts, Dictionary> tagNameToFileSet) { await ExecuteAsync(_parameters.Exe, _parameters.Arguments, _parameters.WorkingDir, envVars: ParseEnvVars(_parameters.Environment, _parameters.EnvironmentFile), logOutput: _parameters.LogOutput, errorLevel: _parameters.ErrorLevel); } /// /// Output this task out to an XML writer. /// public override void Write(XmlWriter writer) { Write(writer, _parameters); } /// /// Find all the tags which are used as inputs to this task /// /// The tag names which are read by this task public override IEnumerable FindConsumedTagNames() { yield break; } /// /// Find all the tags which are modified by this task /// /// The tag names which are modified by this task public override IEnumerable FindProducedTagNames() { yield break; } } public static partial class StandardTasks { /// /// ExecuteAsync an external program /// /// Executable to spawn. /// Arguments for the newly created process. /// Working directory for spawning the new task. /// Environment variables to set. /// File to read environment from. /// Write output to the log. /// The minimum exit code which is treated as an error. public static async Task SpawnAsync(string exe, string arguments = null, string workingDir = null, string environment = null, string environmentFile = null, bool? logOutput = null, int? errorLevel = null) { SpawnTaskParameters parameters = new SpawnTaskParameters(); parameters.Exe = exe; parameters.Arguments = arguments; parameters.WorkingDir = workingDir; parameters.Environment = environment; parameters.EnvironmentFile = environmentFile; parameters.LogOutput = logOutput ?? parameters.LogOutput; parameters.ErrorLevel = errorLevel ?? parameters.ErrorLevel; await ExecuteAsync(new SpawnTask(parameters)); } } }