// Copyright Epic Games, Inc. All Rights Reserved. using AutomationTool; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; using EpicGames.Core; using UnrealBuildTool; using EpicGames.BuildGraph; using AutomationTool.Tasks; namespace AutomationTool.Tasks { /// /// Parameters for a spawn task /// public class SpawnTaskParameters { /// /// Executable to spawn. /// [TaskParameter] public string Exe; /// /// Arguments for the newly created process. /// [TaskParameter(Optional = true)] public string Arguments; /// /// Working directory for spawning the new task /// [TaskParameter(Optional = true)] public string WorkingDir; /// /// Environment variables to set /// [TaskParameter(Optional = true)] public string Environment; /// /// File to read environment from /// [TaskParameter(Optional = true)] public string EnvironmentFile; /// /// Write output to the log /// [TaskParameter(Optional = true)] public bool LogOutput = true; /// /// The minimum exit code, which is treated as an error. /// [TaskParameter(Optional = true)] public int ErrorLevel = 1; } /// /// Base class for tasks that run an external tool /// public abstract class SpawnTaskBase : BgTaskImpl { /// /// Execute 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 (EnvironmentFile != null) { 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 EqualsIdx = Environment.IndexOf('=', BaseIdx); if (EqualsIdx == -1) { throw new AutomationException("Missing value in environment variable string '{0}'", Environment); } int EndIdx = Environment.IndexOf(Separator, EqualsIdx + 1); if (EndIdx == -1) { EndIdx = Environment.Length; } string Name = Environment.Substring(BaseIdx, EqualsIdx - BaseIdx).Trim(); string Value = Environment.Substring(EqualsIdx + 1, EndIdx - (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 /// SpawnTaskParameters Parameters; /// /// Construct a spawn task /// /// Parameters for the task public SpawnTask(SpawnTaskParameters InParameters) { Parameters = InParameters; } /// /// Execute 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 { /// /// Execute 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)); } } }