using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnrealBuildTool; using AutomationTool; namespace AutomationTool { /// /// 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 input names which this node requires to run /// public string[] InputNames; /// /// Array of output names produced by this node /// public string[] OutputNames; /// /// 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; /// /// List of tasks to execute /// public List Tasks; /// /// 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 /// Names of inputs that this node depends on /// Names of 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 public Node(string InName, string[] InInputNames, string[] InOutputNames, Node[] InInputDependencies, Node[] InOrderDependencies, ManualTrigger InControllingTrigger, List InTasks) { Name = InName; InputNames = InInputNames; OutputNames = InOutputNames; InputDependencies = InInputDependencies; OrderDependencies = InOrderDependencies; ControllingTrigger = InControllingTrigger; Tasks = new List(InTasks); } /// /// 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) { bool bResult = true; // Allow tasks to merge together MergeTasks(); // Build everything HashSet BuildProducts = new HashSet(); foreach(CustomTask Task in Tasks) { if(!Task.Execute(Job, BuildProducts, TagNameToFileSet)) { CommandUtils.LogError("Failed to execute task."); return false; } } // Build a mapping of build product to the outputs it belongs to, using the filesets created by the tasks. Dictionary FileToOutputName = new Dictionary(); foreach(string OutputName in OutputNames) { HashSet FileSet = TagNameToFileSet[OutputName]; foreach(FileReference File in FileSet) { string ExistingOutputName; if(FileToOutputName.TryGetValue(File, out ExistingOutputName)) { CommandUtils.LogError("Build product is added to multiple outputs; {0} added to {1} and {2}", File.MakeRelativeTo(new DirectoryReference(CommandUtils.CmdEnv.LocalRoot)), ExistingOutputName, OutputName); bResult = false; continue; } FileToOutputName.Add(File, OutputName); } } // Any build products left over can be added to the default output. TagNameToFileSet[Name].UnionWith(BuildProducts.Where(x => !FileToOutputName.ContainsKey(x))); return bResult; } /// /// 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)); } /// /// Returns the name of this node /// /// The name of this node public override string ToString() { return Name; } } }