using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnrealBuildTool; using AutomationTool; using System.Xml; namespace AutomationTool { /// /// Reference to an output tag from a particular node /// class NodeOutput { /// /// The node which produces the given output /// public Node ProducingNode; /// /// Name of the tag /// public string TagName; /// /// Constructor /// /// Node which produces the given output /// Name of the tag public NodeOutput(Node InProducingNode, string InTagName) { ProducingNode = InProducingNode; TagName = InTagName; } /// /// Returns a string representation of this output for debugging purposes /// /// The name of this output public override string ToString() { return String.Format("{0}: {1}", ProducingNode.Name, TagName); } } /// /// Defines a node, a container for tasks and the smallest unit of execution that can be run as part of a build graph. /// class Node { /// /// The node's name /// public string Name; /// /// Array of inputs which this node requires to run /// public NodeOutput[] Inputs; /// /// Array of outputs produced by this node /// public NodeOutput[] Outputs; /// /// Nodes which this node has input dependencies on /// public Node[] InputDependencies; /// /// Nodes which this node needs to run after /// public Node[] OrderDependencies; /// /// The trigger which controls whether this node will be executed /// public ManualTrigger ControllingTrigger; /// /// Tickets which must be acquired for this node to run /// public FileReference[] RequiredTickets; /// /// List of tasks to execute /// public List Tasks = new List(); /// /// List of email addresses to notify if this node fails. /// public HashSet NotifyUsers = new HashSet(StringComparer.InvariantCultureIgnoreCase); /// /// If set, anyone that has submitted to one of the given paths will be notified on failure of this node /// public HashSet NotifySubmitters = new HashSet(StringComparer.InvariantCultureIgnoreCase); /// /// Whether to ignore warnings produced by this node /// public bool bNotifyOnWarnings = true; /// /// Constructor /// /// The name of this node /// Inputs that this node depends on /// Names of the outputs that this node produces /// Nodes which this node is dependent on for its inputs /// Nodes which this node needs to run after. Should include all input dependencies. /// The trigger which this node is behind /// Optional ticket which must be required for this node to run public Node(string InName, NodeOutput[] InInputs, string[] InOutputNames, Node[] InInputDependencies, Node[] InOrderDependencies, ManualTrigger InControllingTrigger, FileReference[] InRequiredTickets) { Name = InName; Inputs = InInputs; List AllOutputs = new List(); AllOutputs.Add(new NodeOutput(this, "#" + Name)); AllOutputs.AddRange(InOutputNames.Where(x => String.Compare(x, Name, StringComparison.InvariantCultureIgnoreCase) != 0).Select(x => new NodeOutput(this, x))); Outputs = AllOutputs.ToArray(); InputDependencies = InInputDependencies; OrderDependencies = InOrderDependencies; ControllingTrigger = InControllingTrigger; RequiredTickets = InRequiredTickets; } /// /// Returns the default output for this node, which includes all build products /// public NodeOutput DefaultOutput { get { return Outputs[0]; } } /// /// Build all the tasks for this node /// /// Information about the current job /// Set of build products produced by this node. /// Mapping from tag names to the set of files they include. Should be set to contain the node inputs on entry. /// Whether the task succeeded or not. Exiting with an exception will be caught and treated as a failure. public bool Build(JobContext Job, Dictionary> TagNameToFileSet) { // Allow tasks to merge together MergeTasks(); // Build everything HashSet BuildProducts = TagNameToFileSet[DefaultOutput.TagName]; foreach(CustomTask Task in Tasks) { if(!Task.Execute(Job, BuildProducts, TagNameToFileSet)) { CommandUtils.Log("Failed to execute task."); return false; } } // Remove anything that doesn't exist, since these files weren't explicitly tagged BuildProducts.RemoveWhere(x => !x.Exists()); return true; } /// /// Merge tasks which can be combined together /// void MergeTasks() { List MergedTasks = new List(); while(Tasks.Count > 0) { CustomTask NextTask = Tasks[0]; Tasks.RemoveAt(0); NextTask.Merge(Tasks); MergedTasks.Add(NextTask); } Tasks = MergedTasks; } /// /// Determines the minimal set of direct input dependencies for this node to run /// /// Sequence of nodes that are direct inputs to this node public IEnumerable GetDirectInputDependencies() { HashSet DirectDependencies = new HashSet(InputDependencies); foreach(Node InputDependency in InputDependencies) { DirectDependencies.ExceptWith(InputDependency.InputDependencies); } return DirectDependencies; } /// /// Determines the minimal set of direct order dependencies for this node to run /// /// Sequence of nodes that are direct order dependencies of this node public IEnumerable GetDirectOrderDependencies() { HashSet DirectDependencies = new HashSet(OrderDependencies); foreach(Node OrderDependency in OrderDependencies) { DirectDependencies.ExceptWith(OrderDependency.OrderDependencies); } return DirectDependencies; } /// /// Checks whether this node is downstream of the given trigger /// /// The trigger to check /// True if the node is downstream of the trigger, false otherwise public bool IsControlledBy(ManualTrigger Trigger) { return Trigger == null || ControllingTrigger == Trigger || (ControllingTrigger != null && ControllingTrigger.IsDownstreamFrom(Trigger)); } /// /// Write this node to an XML writer /// /// The writer to output the node to public void Write(XmlWriter Writer) { Writer.WriteStartElement("Node"); Writer.WriteAttributeString("Name", Name); string[] RequireNames = Inputs.Select(x => x.TagName).ToArray(); if (RequireNames.Length > 0) { Writer.WriteAttributeString("Requires", String.Join(";", RequireNames)); } string[] ProducesNames = Outputs.Where(x => x != DefaultOutput).Select(x => x.TagName).ToArray(); if (ProducesNames.Length > 0) { Writer.WriteAttributeString("Produces", String.Join(";", ProducesNames)); } string[] AfterNames = GetDirectOrderDependencies().Except(InputDependencies).Select(x => x.Name).ToArray(); if (AfterNames.Length > 0) { Writer.WriteAttributeString("After", String.Join(";", AfterNames)); } if (!bNotifyOnWarnings) { Writer.WriteAttributeString("NotifyOnWarnings", bNotifyOnWarnings.ToString()); } foreach (CustomTask Task in Tasks) { Task.Write(Writer); } Writer.WriteEndElement(); } /// /// Returns the name of this node /// /// The name of this node public override string ToString() { return Name; } } }