// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using System.Xml; using EpicGames.BuildGraph; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace AutomationTool.Tasks { static class StringExtensions { public static bool CaseInsensitiveContains(this string text, string value) { return System.Globalization.CultureInfo.InvariantCulture.CompareInfo.IndexOf(text, value, System.Globalization.CompareOptions.IgnoreCase) >= 0; } } /// /// Parameters for a task that calls another UAT command /// public class CommandTaskParameters { /// /// The command name to execute. /// [TaskParameter] public string Name { get; set; } /// /// Arguments to be passed to the command. /// [TaskParameter(Optional = true)] public string Arguments { get; set; } /// /// If non-null, instructs telemetry from the command to be merged into the telemetry for this UAT instance with the given prefix. May be an empty (non-null) string. /// [TaskParameter(Optional = true)] public string MergeTelemetryWithPrefix { get; set; } } /// /// Invokes an AutomationTool child process to run the given command. /// [TaskElement("Command", typeof(CommandTaskParameters))] public class CommandTask : BgTaskImpl { /// /// Parameters for this task /// readonly CommandTaskParameters _parameters; /// /// Construct a new CommandTask. /// /// Parameters for this task public CommandTask(CommandTaskParameters 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 Task ExecuteAsync(JobContext job, HashSet buildProducts, Dictionary> tagNameToFileSet) { // If we're merging telemetry from the child process, get a temp filename for it FileReference telemetryFile = null; if (_parameters.MergeTelemetryWithPrefix != null) { telemetryFile = FileReference.Combine(Unreal.RootDirectory, "Engine", "Intermediate", "UAT", "Telemetry.json"); DirectoryReference.CreateDirectory(telemetryFile.Directory); } // Run the command StringBuilder commandLine = new StringBuilder(); if (_parameters.Arguments == null || (!_parameters.Arguments.CaseInsensitiveContains("-p4") && !_parameters.Arguments.CaseInsensitiveContains("-nop4"))) { commandLine.AppendFormat("{0} ", CommandUtils.P4Enabled ? "-p4" : "-nop4"); } if (_parameters.Arguments == null || (!_parameters.Arguments.CaseInsensitiveContains("-submit") && !_parameters.Arguments.CaseInsensitiveContains("-nosubmit"))) { if (GlobalCommandLine.Submit) { commandLine.Append("-submit "); } if (GlobalCommandLine.NoSubmit) { commandLine.Append("-nosubmit "); } } if (_parameters.Arguments == null || !_parameters.Arguments.CaseInsensitiveContains("-uselocalbuildstorage")) { if (GlobalCommandLine.UseLocalBuildStorage) { commandLine.Append("-uselocalbuildstorage "); } } commandLine.Append("-NoCompile "); commandLine.Append(_parameters.Name); if (!String.IsNullOrEmpty(_parameters.Arguments)) { commandLine.AppendFormat(" {0}", _parameters.Arguments); } if (telemetryFile != null) { commandLine.AppendFormat(" -Telemetry={0}", CommandUtils.MakePathSafeToUseWithCommandLine(telemetryFile.FullName)); } CommandUtils.RunUAT(CommandUtils.CmdEnv, commandLine.ToString(), Identifier: _parameters.Name); // Merge in any new telemetry data that was produced if (telemetryFile != null && FileReference.Exists(telemetryFile)) { Logger.LogDebug("Merging telemetry from {TelemetryFile}", telemetryFile); TelemetryData newTelemetry; if (TelemetryData.TryRead(telemetryFile, out newTelemetry)) { CommandUtils.Telemetry.Merge(_parameters.MergeTelemetryWithPrefix, newTelemetry); } else { Logger.LogWarning("Unable to read UAT telemetry file from {TelemetryFile}", telemetryFile); } } return Task.CompletedTask; } /// /// Gets the name of this trace /// /// Name of the trace public override string GetTraceName() { return String.Format("{0}.{1}", base.GetTraceName(), _parameters.Name.ToLowerInvariant()); } /// /// 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 { /// /// Runs another UAT command /// /// The execution state /// Name of the command to run /// Arguments for the command /// If non-null, instructs telemetry from the command to be merged into the telemetry for this UAT instance with the given prefix. May be an empty (non-null) string. public static async Task CommandAsync(this BgContext state, string name, string arguments = null, string mergeTelemetryWithPrefix = null) { _ = state; CommandTaskParameters parameters = new CommandTaskParameters(); parameters.Name = name; parameters.Arguments = arguments ?? parameters.Arguments; parameters.MergeTelemetryWithPrefix = mergeTelemetryWithPrefix ?? parameters.MergeTelemetryWithPrefix; await ExecuteAsync(new CommandTask(parameters)); } /// /// Runs another UAT command /// /// Name of the command to run /// Arguments for the command /// If non-null, instructs telemetry from the command to be merged into the telemetry for this UAT instance with the given prefix. May be an empty (non-null) string. public static async Task CommandAsync(string name, string arguments = null, string mergeTelemetryWithPrefix = null) { CommandTaskParameters parameters = new CommandTaskParameters(); parameters.Name = name; parameters.Arguments = arguments ?? parameters.Arguments; parameters.MergeTelemetryWithPrefix = mergeTelemetryWithPrefix ?? parameters.MergeTelemetryWithPrefix; await ExecuteAsync(new CommandTask(parameters)); } } }