Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/BuildGraph/Node.cs
Ben Marsh c254db71a7 Copying //UE4/Dev-Build to //UE4/Main (Source: //UE4/Dev-Build @ 2944521)
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2909886 on 2016/03/15 by Matthew.Griffin

	Adding a build exception to give a message instead of crashing when trying to generate all project files from an installed build.

Change 2911727 on 2016/03/16 by Matthew.Griffin

	Added Platform Type and Architecture to Installed Platform Info
	Reworked the different IsValid... functions to use lamdas to reduce duplicated code looping and checking receipts
	Moved the code to write config file entries into InstalledPlatformInfo so that it can be reused by anyone wanting to make installed builds
	Added temporary hack to write Android architecture until I can get it from build process

Change 2913692 on 2016/03/17 by Ben.Marsh

	UAT: Move script to archive a build for UGS into a public folder.

Change 2915445 on 2016/03/18 by Ben.Marsh

	UAT: Reduce the number of redundant log warnings/errors after a reported build failure, and simplify calls to ParallelExecutor which don't need retrying.

Change 2915450 on 2016/03/18 by Ben.Marsh

	UAT: Suppress warning messages trying to kill child processes if the operation failed because it's already exited.

Change 2925830 on 2016/03/29 by Matthew.Griffin

	Added new selective download tags
	Added a test for whether installed platforms are missing required files so that we can try to open the launcher to the installer settings

Change 2926437 on 2016/03/29 by Ben.Marsh

	PR #2210: Fix "Rebuild.bat" for paths with parentheses (Contributed by amcofi)

Change 2927399 on 2016/03/30 by Matthew.Griffin

	Updating use of PDBCopy to look in VS2015 folder and fall back to VS2013 version if it doesn't exist.

Change 2933093 on 2016/04/05 by Ben.Marsh

	PR #2232: Updated copyright text to 2016 (Contributed by erikbye)

Change 2936221 on 2016/04/07 by Matthew.Griffin

	Adding checks on architecture for android config options

Change 2938021 on 2016/04/08 by Ben.Marsh

	UAT: Prevent UnauthorizedAccessException when enumerating crash files on Mac from a restricted user account.

Change 2939332 on 2016/04/11 by Matthew.Griffin

	Added AdditionalBundleResources to external file list so that they should be included in Launcher releases

Change 2939767 on 2016/04/11 by Ben.Marsh

	BuildGraph: Add a -preprocess option, which will cause the preprocessed and culled graph out to an XML file for debugging.

Change 2941611 on 2016/04/12 by Ben.Marsh

	UAT: Prevent warning about commands requiring P4 if -p4 is specified on the command line.

Change 2942037 on 2016/04/13 by Ben.Marsh

	UBT: Only print 'Detailed Action Stats' message footer if there were any detailed action stats.

Change 2942640 on 2016/04/13 by Ben.Marsh

	GUBP: Trigger GitHub promotions by triggering a new procedure rather than scanning for labels.

Change 2942728 on 2016/04/13 by Ben.Marsh

	BuildGraph: Rename "AgentGroup" to "Agent" for consistency with XML.

Change 2942735 on 2016/04/13 by Ben.Marsh

	BuildGraph: Few renames to match class names (Build.cs -> BuildGraph.cs, AgentGroup.cs -> Agent.cs)

Change 2943568 on 2016/04/14 by Ben.Marsh

	EC: Print out the log folder at the start of each job.

Change 2944421 on 2016/04/14 by Ben.Marsh

	EC: Add GitHub dashboard page which shows the current syncing state

#lockdown Nick.Penwarden

[CL 2944733 by Ben Marsh in Main branch]
2016-04-14 20:35:31 -04:00

283 lines
9.2 KiB
C#

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
{
/// <summary>
/// Reference to a node's output
/// </summary>
class NodeOutput
{
/// <summary>
/// The node which produces the given output
/// </summary>
public Node ProducingNode;
/// <summary>
/// Name of the output
/// </summary>
public string Name;
/// <summary>
/// Constructor
/// </summary>
/// <param name="InProducingNode">Node which produces the given output</param>
/// <param name="InName">Name of the output</param>
public NodeOutput(Node InProducingNode, string InName)
{
ProducingNode = InProducingNode;
Name = InName;
}
/// <summary>
/// Returns a string representation of this output for debugging purposes
/// </summary>
/// <returns>The name of this output</returns>
public override string ToString()
{
return (ProducingNode.Name == Name)? Name : String.Format("{0}: {1}", ProducingNode.Name, Name);
}
}
/// <summary>
/// Defines a node, a container for tasks and the smallest unit of execution that can be run as part of a build graph.
/// </summary>
class Node
{
/// <summary>
/// The node's name
/// </summary>
public string Name;
/// <summary>
/// Array of inputs which this node requires to run
/// </summary>
public NodeOutput[] Inputs;
/// <summary>
/// Array of output names produced by this node
/// </summary>
public NodeOutput[] Outputs;
/// <summary>
/// Nodes which this node has input dependencies on
/// </summary>
public Node[] InputDependencies;
/// <summary>
/// Nodes which this node needs to run after
/// </summary>
public Node[] OrderDependencies;
/// <summary>
/// The trigger which controls whether this node will be executed
/// </summary>
public ManualTrigger ControllingTrigger;
/// <summary>
/// List of tasks to execute
/// </summary>
public List<CustomTask> Tasks = new List<CustomTask>();
/// <summary>
/// List of email addresses to notify if this node fails.
/// </summary>
public HashSet<string> NotifyUsers = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
/// <summary>
/// If set, anyone that has submitted to one of the given paths will be notified on failure of this node
/// </summary>
public HashSet<string> NotifySubmitters = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
/// <summary>
/// Whether to ignore warnings produced by this node
/// </summary>
public bool bNotifyOnWarnings = true;
/// <summary>
/// Constructor
/// </summary>
/// <param name="InName">The name of this node</param>
/// <param name="InInputs">Inputs that this node depends on</param>
/// <param name="InOutputNames">Names of the outputs that this node produces</param>
/// <param name="InInputDependencies">Nodes which this node is dependent on for its inputs</param>
/// <param name="InOrderDependencies">Nodes which this node needs to run after. Should include all input dependencies.</param>
/// <param name="InControllingTrigger">The trigger which this node is behind</param>
public Node(string InName, NodeOutput[] InInputs, string[] InOutputNames, Node[] InInputDependencies, Node[] InOrderDependencies, ManualTrigger InControllingTrigger)
{
Name = InName;
Inputs = InInputs;
Outputs = InOutputNames.Select(x => new NodeOutput(this, x)).ToArray();
InputDependencies = InInputDependencies;
OrderDependencies = InOrderDependencies;
ControllingTrigger = InControllingTrigger;
}
/// <summary>
/// Build all the tasks for this node
/// </summary>
/// <param name="Job">Information about the current job</param>
/// <param name="BuildProducts">Set of build products produced by this node.</param>
/// <param name="TagNameToFileSet">Mapping from tag names to the set of files they include. Should be set to contain the node inputs on entry.</param>
/// <returns>Whether the task succeeded or not. Exiting with an exception will be caught and treated as a failure.</returns>
public bool Build(JobContext Job, Dictionary<string, HashSet<FileReference>> TagNameToFileSet)
{
bool bResult = true;
// Allow tasks to merge together
MergeTasks();
// Build everything
HashSet<FileReference> BuildProducts = new HashSet<FileReference>();
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<FileReference, NodeOutput> FileToOutput = new Dictionary<FileReference,NodeOutput>();
foreach(NodeOutput Output in Outputs)
{
HashSet<FileReference> FileSet = TagNameToFileSet[Output.Name];
foreach(FileReference File in FileSet)
{
NodeOutput ExistingOutput;
if(FileToOutput.TryGetValue(File, out ExistingOutput))
{
CommandUtils.LogError("Build product is added to multiple outputs; {0} added to {1} and {2}", File.MakeRelativeTo(new DirectoryReference(CommandUtils.CmdEnv.LocalRoot)), ExistingOutput.Name, Output.Name);
bResult = false;
continue;
}
FileToOutput.Add(File, Output);
}
}
// Add any remaining valid build products into the output channel for this node. Since it's a catch-all output whose build products were not explicitly specified by the user, we can remove
// those which are outside the root directory or which no longer exist (they may have been removed by downstream tasks).
HashSet<FileReference> DefaultOutputs = TagNameToFileSet[Name];
foreach(FileReference BuildProduct in BuildProducts)
{
if(!FileToOutput.ContainsKey(BuildProduct) && BuildProduct.IsUnderDirectory(CommandUtils.RootDirectory) && BuildProduct.Exists())
{
DefaultOutputs.Add(BuildProduct);
}
}
return bResult;
}
/// <summary>
/// Merge tasks which can be combined together
/// </summary>
void MergeTasks()
{
List<CustomTask> MergedTasks = new List<CustomTask>();
while(Tasks.Count > 0)
{
CustomTask NextTask = Tasks[0];
Tasks.RemoveAt(0);
NextTask.Merge(Tasks);
MergedTasks.Add(NextTask);
}
Tasks = MergedTasks;
}
/// <summary>
/// Determines the minimal set of direct input dependencies for this node to run
/// </summary>
/// <returns>Sequence of nodes that are direct inputs to this node</returns>
public IEnumerable<Node> GetDirectInputDependencies()
{
HashSet<Node> DirectDependencies = new HashSet<Node>(InputDependencies);
foreach(Node InputDependency in InputDependencies)
{
DirectDependencies.ExceptWith(InputDependency.InputDependencies);
}
return DirectDependencies;
}
/// <summary>
/// Determines the minimal set of direct order dependencies for this node to run
/// </summary>
/// <returns>Sequence of nodes that are direct order dependencies of this node</returns>
public IEnumerable<Node> GetDirectOrderDependencies()
{
HashSet<Node> DirectDependencies = new HashSet<Node>(OrderDependencies);
foreach(Node OrderDependency in OrderDependencies)
{
DirectDependencies.ExceptWith(OrderDependency.OrderDependencies);
}
return DirectDependencies;
}
/// <summary>
/// Checks whether this node is downstream of the given trigger
/// </summary>
/// <param name="Trigger">The trigger to check</param>
/// <returns>True if the node is downstream of the trigger, false otherwise</returns>
public bool IsControlledBy(ManualTrigger Trigger)
{
return Trigger == null || ControllingTrigger == Trigger || (ControllingTrigger != null && ControllingTrigger.IsDownstreamFrom(Trigger));
}
/// <summary>
/// Write this node to an XML writer
/// </summary>
/// <param name="Writer">The writer to output the node to</param>
public void Write(XmlWriter Writer)
{
Writer.WriteStartElement("Node");
Writer.WriteAttributeString("Name", Name);
Node[] RequireNodes = Inputs.Where(x => x.Name == x.ProducingNode.Name).Select(x => x.ProducingNode).ToArray();
string[] RequireNames = RequireNodes.Select(x => x.Name).Union(Inputs.Where(x => !RequireNodes.Contains(x.ProducingNode)).Select(x => "#" + x.Name)).ToArray();
if (RequireNames.Length > 0)
{
Writer.WriteAttributeString("Requires", String.Join(";", RequireNames));
}
string[] ProducesNames = Outputs.Where(x => x.Name != Name).Select(x => "#" + x.Name).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();
}
/// <summary>
/// Returns the name of this node
/// </summary>
/// <returns>The name of this node</returns>
public override string ToString()
{
return Name;
}
}
}