You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3129636 on 2016/09/17 by Ben.Marsh UBT: Add a "-nolink" option which allows compiling object files without linking them into an executable. Useful for non-unity builds, which take a very long time to link or fail on some platforms due to command lines being too long, PDB file having too many records, etc... Change 3129825 on 2016/09/18 by Ben.Marsh UBT: Don't force Linux to build in unity; it seems to build fine without. Change 3129965 on 2016/09/19 by Matthew.Griffin Duplicating CL#3129960 from Release-4.13 Exclude NetworkProfiler when building CS tools for Linux Change3130653on 2016/09/19 by Ben.Marsh UHT: Fix missing "Error:" prefix in output log, causing messages to be ignored for failure emails. Change3130662on 2016/09/19 by Ben.Marsh EC: Prevent UHT failures from being reported twice, and remove the need for special case to show UHT summary output. Change 3131956 on 2016/09/20 by Matthew.Griffin Addtional fixes for compiling Editor as a monolithic executable Change so monolithic editor is output to Project Binaries directory Removed duplicated ReturnContainerIndexFromChannelName function Only check for out of date modules for non monolithic editor Don't define GIsGameAgnosticExe or PER_MODULE_BOILERPLATE for monolithic editor, done elsewhere Correct IMPLEMENT_MODULE for QuadricMeshReduction and AudioCapture modules Change 3132112 on 2016/09/20 by Ben.Marsh Docs: Remove reference to UBT environment variables from configuration docs. Change 3132815 on 2016/09/20 by Ben.Marsh AutomationTool: Delete GUBP. Everything now uses BuildGraph! Change 3132871 on 2016/09/20 by Ben.Marsh UBT: Remove GUBP callbacks from TargetRules instances. Change 3132987 on 2016/09/20 by Ben.Marsh Allow public distribution of the compiled SimplygonMeshUtilities binaries. Change 3133974 on 2016/09/21 by Ben.Marsh Allow public distribution of the SimplygonSwarm module. Requires a separate Simplygon DLL (still in a NotForLicensees folder) to function correctly. Change 3137228 on 2016/09/22 by Ben.Marsh UAT: Merging fix to parallel executor on Linux from 4.13 branch. Change 3139783 on 2016/09/26 by Matthew.Griffin Fixed Xbox support for Installed Builds Corrected typo in Xbox+PS4 filter creation and added XboxOnePackageNameUtil.exe Added Xbox versions of ThirdParty libs that hadn't been specified until now Change 3141721 on 2016/09/27 by Ben.Marsh Remove declaration of circular references between FbxAutomationTestBuilder and LevelEditor; causes LevelEditor to be built differently if plugin is enabled, which results in shared build products being invalidated by switching between games. Change 3141789 on 2016/09/27 by Ben.Marsh UBT: Retain the ".suppressed" part of output file names when building import libraries for circularly referenced modules. Change 3141805 on 2016/09/27 by Ben.Marsh UBT: Allow reusing build ids in version manifests as long as we aren't modifying any engine binaries (building more or fewer is permitted), and merge manifests together if possible. Allows building the entire solution through Visual Studio, when some projects may build more modules than another. Change 3141980 on 2016/09/27 by Ben.Marsh EC: Include a "Steps to Reproduce" section in failure emails, which gives the command line to run to execute the step. Change 3143996 on 2016/09/28 by Ben.Marsh BuildGraph: Fix exported job steps having dependencies on nodes behind triggers, causing jobs to never terminate. Change 3144696 on 2016/09/29 by Matthew.Griffin Adding -NoSharedPCH to NonUnity build steps and split them into separate nodes for UE4Editor and UE4Game Fixed redefinition of __WINDOWS_DS__ Change 3144931 on 2016/09/29 by Ben.Marsh Core: Changes to search paths for DLL loading. * The default binaries directory is now added to the list of search paths. Normally LoadLibrary finds these without needing to preload them, but plugins can change the global search paths by calling SetDefaultDllDirectories(). * Only the top entry of the DLL directory stack is searched. There is typically only one directory here anyway, but the intent is more consistent with the operation of SetDllDirectory(). * Resolved import paths are converted to absolute, so the resulting calls to LoadLibrary will not be influenced by calls to SetDefaultDllDirectories() changing the base directory. * Search paths aren't de-duplicated any more. They don't overlap in practice, and it's not expensive even if they do. Change 3144932 on 2016/09/29 by Ben.Marsh Vulkan: Only add vulkan-1.dll as a delay load dependency from the VulkanRHI module, otherwise it can be added to the linker command-line 20 or more times. Change 3145011 on 2016/09/29 by Ben.Marsh Core: Include the compatible changelist in version manifests, so local builds after syncing with UGS will have the correct compatible changelist numbers post-hotfix. [CL 3146509 by Ben Marsh in Main branch]
699 lines
23 KiB
C#
699 lines
23 KiB
C#
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.IO;
|
|
using AutomationTool;
|
|
using UnrealBuildTool;
|
|
using System.Reflection;
|
|
using System.Xml;
|
|
using System.Linq;
|
|
using System.Diagnostics;
|
|
|
|
namespace AutomationTool
|
|
{
|
|
/// <summary>
|
|
/// Options for how the graph should be printed
|
|
/// </summary>
|
|
enum GraphPrintOptions
|
|
{
|
|
/// <summary>
|
|
/// Includes a list of the graph options
|
|
/// </summary>
|
|
ShowCommandLineOptions = 0x1,
|
|
|
|
/// <summary>
|
|
/// Includes the list of dependencies for each node
|
|
/// </summary>
|
|
ShowDependencies = 0x2,
|
|
|
|
/// <summary>
|
|
/// Includes the list of notifiers for each node
|
|
/// </summary>
|
|
ShowNotifications = 0x4,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Diagnostic message from the graph script. These messages are parsed at startup, then culled along with the rest of the graph nodes before output. Doing so
|
|
/// allows errors and warnings which are only output if a node is part of the graph being executed.
|
|
/// </summary>
|
|
class GraphDiagnostic
|
|
{
|
|
/// <summary>
|
|
/// The diagnostic event type
|
|
/// </summary>
|
|
public LogEventType EventType;
|
|
|
|
/// <summary>
|
|
/// The message to display
|
|
/// </summary>
|
|
public string Message;
|
|
|
|
/// <summary>
|
|
/// The node which this diagnostic is declared in. If the node is culled from the graph, the message will not be displayed.
|
|
/// </summary>
|
|
public Node EnclosingNode;
|
|
|
|
/// <summary>
|
|
/// The agent that this diagnostic is declared in. If the entire agent is culled from the graph, the message will not be displayed.
|
|
/// </summary>
|
|
public Agent EnclosingAgent;
|
|
|
|
/// <summary>
|
|
/// The trigger that this diagnostic is declared in. If this trigger is not being run, the message will not be displayed.
|
|
/// </summary>
|
|
public ManualTrigger EnclosingTrigger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a graph option. These are expanded during preprocessing, but are retained in order to display help messages.
|
|
/// </summary>
|
|
class GraphOption
|
|
{
|
|
/// <summary>
|
|
/// Name of this option
|
|
/// </summary>
|
|
public string Name;
|
|
|
|
/// <summary>
|
|
/// Description for this option
|
|
/// </summary>
|
|
public string Description;
|
|
|
|
/// <summary>
|
|
/// Default value for this option
|
|
/// </summary>
|
|
public string DefaultValue;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="Name">The name of this option</param>
|
|
/// <param name="Description">Description of the option, for display on help pages</param>
|
|
/// <param name="DefaultValue">Default value for the option</param>
|
|
public GraphOption(string Name, string Description, string DefaultValue)
|
|
{
|
|
this.Name = Name;
|
|
this.Description = Description;
|
|
this.DefaultValue = DefaultValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a name of this option for debugging
|
|
/// </summary>
|
|
/// <returns>Name of the option</returns>
|
|
public override string ToString()
|
|
{
|
|
return Name;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Definition of a graph.
|
|
/// </summary>
|
|
class Graph
|
|
{
|
|
/// <summary>
|
|
/// List of options, in the order they were specified
|
|
/// </summary>
|
|
public List<GraphOption> Options = new List<GraphOption>();
|
|
|
|
/// <summary>
|
|
/// List of agents containing nodes to execute
|
|
/// </summary>
|
|
public List<Agent> Agents = new List<Agent>();
|
|
|
|
/// <summary>
|
|
/// All manual triggers that are part of this graph
|
|
/// </summary>
|
|
public Dictionary<string, ManualTrigger> NameToTrigger = new Dictionary<string, ManualTrigger>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping from name to agent
|
|
/// </summary>
|
|
public Dictionary<string, Agent> NameToAgent = new Dictionary<string, Agent>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of names to the corresponding node.
|
|
/// </summary>
|
|
public Dictionary<string, Node> NameToNode = new Dictionary<string,Node>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of names to the corresponding report.
|
|
/// </summary>
|
|
public Dictionary<string, Report> NameToReport = new Dictionary<string, Report>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of names to their corresponding node output.
|
|
/// </summary>
|
|
public HashSet<string> LocalTagNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of names to their corresponding node output.
|
|
/// </summary>
|
|
public Dictionary<string, NodeOutput> TagNameToNodeOutput = new Dictionary<string,NodeOutput>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of aggregate names to their respective nodes
|
|
/// </summary>
|
|
public Dictionary<string, Node[]> AggregateNameToNodes = new Dictionary<string,Node[]>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// List of badges that can be displayed for this build
|
|
/// </summary>
|
|
public List<Badge> Badges = new List<Badge>();
|
|
|
|
/// <summary>
|
|
/// Diagnostic messages for this graph
|
|
/// </summary>
|
|
public List<GraphDiagnostic> Diagnostics = new List<GraphDiagnostic>();
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
public Graph()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether a given name already exists
|
|
/// </summary>
|
|
/// <param name="Name">The name to check.</param>
|
|
/// <returns>True if the name exists, false otherwise.</returns>
|
|
public bool ContainsName(string Name)
|
|
{
|
|
return NameToNode.ContainsKey(Name) || NameToReport.ContainsKey(Name) || AggregateNameToNodes.ContainsKey(Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to resolve the given name to one or more nodes. Checks for aggregates, and actual nodes.
|
|
/// </summary>
|
|
/// <param name="Name">The name to search for</param>
|
|
/// <param name="OutNodes">If the name is a match, receives an array of nodes and their output names</param>
|
|
/// <returns>True if the name was found, false otherwise.</returns>
|
|
public bool TryResolveReference(string Name, out Node[] OutNodes)
|
|
{
|
|
// Check if it's a tag reference or node reference
|
|
if(Name.StartsWith("#"))
|
|
{
|
|
// Check if it's a regular node or output name
|
|
NodeOutput Output;
|
|
if(TagNameToNodeOutput.TryGetValue(Name, out Output))
|
|
{
|
|
OutNodes = new Node[]{ Output.ProducingNode };
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check if it's a regular node or output name
|
|
Node Node;
|
|
if(NameToNode.TryGetValue(Name, out Node))
|
|
{
|
|
OutNodes = new Node[]{ Node };
|
|
return true;
|
|
}
|
|
|
|
// Check if it's an aggregate name
|
|
Node[] Nodes;
|
|
if(AggregateNameToNodes.TryGetValue(Name, out Nodes))
|
|
{
|
|
OutNodes = Nodes;
|
|
return true;
|
|
}
|
|
|
|
// Check if it's a group name
|
|
Agent Agent;
|
|
if(NameToAgent.TryGetValue(Name, out Agent))
|
|
{
|
|
OutNodes = Agent.Nodes.ToArray();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Otherwise fail
|
|
OutNodes = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to resolve the given name to one or more node outputs. Checks for aggregates, and actual nodes.
|
|
/// </summary>
|
|
/// <param name="Name">The name to search for</param>
|
|
/// <param name="OutOutputs">If the name is a match, receives an array of nodes and their output names</param>
|
|
/// <returns>True if the name was found, false otherwise.</returns>
|
|
public bool TryResolveInputReference(string Name, out NodeOutput[] OutOutputs)
|
|
{
|
|
// Check if it's a tag reference or node reference
|
|
if(Name.StartsWith("#"))
|
|
{
|
|
// Check if it's a regular node or output name
|
|
NodeOutput Output;
|
|
if(TagNameToNodeOutput.TryGetValue(Name, out Output))
|
|
{
|
|
OutOutputs = new NodeOutput[]{ Output };
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check if it's a regular node or output name
|
|
Node Node;
|
|
if(NameToNode.TryGetValue(Name, out Node))
|
|
{
|
|
OutOutputs = Node.Outputs.Union(Node.Inputs).ToArray();
|
|
return true;
|
|
}
|
|
|
|
// Check if it's an aggregate name
|
|
Node[] Nodes;
|
|
if(AggregateNameToNodes.TryGetValue(Name, out Nodes))
|
|
{
|
|
OutOutputs = Nodes.SelectMany(x => x.Outputs.Union(x.Inputs)).Distinct().ToArray();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Otherwise fail
|
|
OutOutputs = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cull the graph to only include the given nodes and their dependencies
|
|
/// </summary>
|
|
/// <param name="TargetNodes">A set of target nodes to build</param>
|
|
public void Select(IEnumerable<Node> TargetNodes)
|
|
{
|
|
// Find this node and all its dependencies
|
|
HashSet<Node> RetainNodes = new HashSet<Node>(TargetNodes);
|
|
foreach(Node TargetNode in TargetNodes)
|
|
{
|
|
RetainNodes.UnionWith(TargetNode.InputDependencies);
|
|
}
|
|
|
|
// Remove all the nodes which are not marked to be kept
|
|
foreach(Agent Agent in Agents)
|
|
{
|
|
Agent.Nodes = Agent.Nodes.Where(x => RetainNodes.Contains(x)).ToList();
|
|
}
|
|
|
|
// Remove all the empty agents
|
|
Agents.RemoveAll(x => x.Nodes.Count == 0);
|
|
|
|
// Trim down the list of nodes for each report to the ones that are being built
|
|
foreach (Report Report in NameToReport.Values)
|
|
{
|
|
Report.Nodes.RemoveWhere(x => !RetainNodes.Contains(x));
|
|
}
|
|
|
|
// Remove all the empty reports
|
|
NameToReport = NameToReport.Where(x => x.Value.Nodes.Count > 0).ToDictionary(Pair => Pair.Key, Pair => Pair.Value, StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
// Remove all the order dependencies which are no longer part of the graph. Since we don't need to build them, we don't need to wait for them
|
|
foreach(Node Node in RetainNodes)
|
|
{
|
|
Node.OrderDependencies = Node.OrderDependencies.Where(x => RetainNodes.Contains(x)).ToArray();
|
|
}
|
|
|
|
// Create a new list of triggers from all the nodes which are left
|
|
NameToTrigger = RetainNodes.Where(x => x.ControllingTrigger != null).Select(x => x.ControllingTrigger).Distinct().ToDictionary(x => x.Name, x => x, StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
// Create a new list of aggregates for everything that's left
|
|
AggregateNameToNodes = AggregateNameToNodes.Where(x => x.Value.All(y => RetainNodes.Contains(y))).ToDictionary(Pair => Pair.Key, Pair => Pair.Value, StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
// Remove any badges which do not have all their dependencies
|
|
Badges.RemoveAll(x => x.Nodes.Any(y => !RetainNodes.Contains(y)));
|
|
|
|
// Remove any diagnostics which are no longer part of the graph
|
|
Diagnostics.RemoveAll(x => (x.EnclosingNode != null && !RetainNodes.Contains(x.EnclosingNode)) || (x.EnclosingAgent != null && !Agents.Contains(x.EnclosingAgent)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skips the given triggers, collapsing everything inside them into their parent trigger.
|
|
/// </summary>
|
|
/// <param name="Triggers">Set of triggers to skip</param>
|
|
public void SkipTriggers(HashSet<ManualTrigger> Triggers)
|
|
{
|
|
foreach(ManualTrigger Trigger in Triggers)
|
|
{
|
|
NameToTrigger.Remove(Trigger.Name);
|
|
}
|
|
foreach(Node Node in NameToNode.Values)
|
|
{
|
|
while(Triggers.Contains(Node.ControllingTrigger))
|
|
{
|
|
Node.ControllingTrigger = Node.ControllingTrigger.Parent;
|
|
}
|
|
}
|
|
foreach(GraphDiagnostic Diagnostic in Diagnostics)
|
|
{
|
|
while(Triggers.Contains(Diagnostic.EnclosingTrigger))
|
|
{
|
|
Diagnostic.EnclosingTrigger = Diagnostic.EnclosingTrigger.Parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a preprocessed build graph to a script file
|
|
/// </summary>
|
|
/// <param name="File">The file to load</param>
|
|
/// <param name="SchemaFile">Schema file for validation</param>
|
|
public void Write(FileReference File, FileReference SchemaFile)
|
|
{
|
|
XmlWriterSettings Settings = new XmlWriterSettings();
|
|
Settings.Indent = true;
|
|
Settings.IndentChars = "\t";
|
|
|
|
using (XmlWriter Writer = XmlWriter.Create(File.FullName, Settings))
|
|
{
|
|
Writer.WriteStartElement("BuildGraph", "http://www.epicgames.com/BuildGraph");
|
|
|
|
if (SchemaFile != null)
|
|
{
|
|
Writer.WriteAttributeString("schemaLocation", "http://www.w3.org/2001/XMLSchema-instance", "http://www.epicgames.com/BuildGraph " + SchemaFile.MakeRelativeTo(File.Directory));
|
|
}
|
|
|
|
foreach (Agent Agent in Agents)
|
|
{
|
|
Agent.Write(Writer, null);
|
|
}
|
|
|
|
foreach (ManualTrigger ControllingTrigger in Agents.SelectMany(x => x.Nodes).Where(x => x.ControllingTrigger != null).Select(x => x.ControllingTrigger).Distinct())
|
|
{
|
|
Writer.WriteStartElement("Trigger");
|
|
Writer.WriteAttributeString("Name", ControllingTrigger.QualifiedName);
|
|
foreach (Agent Agent in Agents)
|
|
{
|
|
Agent.Write(Writer, ControllingTrigger);
|
|
}
|
|
Writer.WriteEndElement();
|
|
}
|
|
|
|
foreach (KeyValuePair<string, Node[]> Aggregate in AggregateNameToNodes)
|
|
{
|
|
Writer.WriteStartElement("Aggregate");
|
|
Writer.WriteAttributeString("Name", Aggregate.Key);
|
|
Writer.WriteAttributeString("Requires", String.Join(";", Aggregate.Value.Select(x => x.Name)));
|
|
Writer.WriteEndElement();
|
|
}
|
|
|
|
foreach (Report Report in NameToReport.Values)
|
|
{
|
|
Writer.WriteStartElement("Report");
|
|
Writer.WriteAttributeString("Name", Report.Name);
|
|
Writer.WriteAttributeString("Requires", String.Join(";", Report.Nodes.Select(x => x.Name)));
|
|
Writer.WriteEndElement();
|
|
}
|
|
|
|
foreach (Badge Badge in Badges)
|
|
{
|
|
Writer.WriteStartElement("Badge");
|
|
Writer.WriteAttributeString("Name", Badge.Name);
|
|
if (Badge.Project != null)
|
|
{
|
|
Writer.WriteAttributeString("Project", Badge.Project);
|
|
}
|
|
Writer.WriteAttributeString("Requires", String.Join(";", Badge.Nodes.Select(x => x.Name)));
|
|
Writer.WriteEndElement();
|
|
}
|
|
|
|
Writer.WriteEndElement();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Export the build graph to a Json file, for parallel execution by the build system
|
|
/// </summary>
|
|
/// <param name="File">Output file to write</param>
|
|
/// <param name="Trigger">The trigger whose nodes to run. Null for the default nodes.</param>
|
|
/// <param name="CompletedNodes">Set of nodes which have been completed</param>
|
|
public void Export(FileReference File, ManualTrigger Trigger, HashSet<Node> CompletedNodes)
|
|
{
|
|
// Find all the nodes which we're actually going to execute. We'll use this to filter the graph.
|
|
HashSet<Node> NodesToExecute = new HashSet<Node>();
|
|
foreach(Node Node in Agents.SelectMany(x => x.Nodes))
|
|
{
|
|
if(!CompletedNodes.Contains(Node) && Node.IsBehind(Trigger))
|
|
{
|
|
NodesToExecute.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Open the output file
|
|
using(JsonWriter JsonWriter = new JsonWriter(File.FullName))
|
|
{
|
|
JsonWriter.WriteObjectStart();
|
|
|
|
// Write all the agents
|
|
JsonWriter.WriteArrayStart("Groups");
|
|
foreach(Agent Agent in Agents)
|
|
{
|
|
Node[] Nodes = Agent.Nodes.Where(x => NodesToExecute.Contains(x) && x.ControllingTrigger == Trigger).ToArray();
|
|
if(Nodes.Length > 0)
|
|
{
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", Agent.Name);
|
|
JsonWriter.WriteArrayStart("Agent Types");
|
|
foreach(string AgentType in Agent.PossibleTypes)
|
|
{
|
|
JsonWriter.WriteValue(AgentType);
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
JsonWriter.WriteArrayStart("Nodes");
|
|
foreach(Node Node in Nodes)
|
|
{
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", Node.Name);
|
|
JsonWriter.WriteValue("DependsOn", String.Join(";", Node.GetDirectOrderDependencies().Where(x => NodesToExecute.Contains(x) && x.ControllingTrigger == Trigger)));
|
|
JsonWriter.WriteObjectStart("Notify");
|
|
JsonWriter.WriteValue("Default", String.Join(";", Node.NotifyUsers));
|
|
JsonWriter.WriteValue("Submitters", String.Join(";", Node.NotifySubmitters));
|
|
JsonWriter.WriteValue("Warnings", Node.bNotifyOnWarnings);
|
|
JsonWriter.WriteObjectEnd();
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
|
|
// Write all the badges
|
|
JsonWriter.WriteArrayStart("Badges");
|
|
foreach (Badge Badge in Badges)
|
|
{
|
|
Node[] Dependencies = Badge.Nodes.Where(x => NodesToExecute.Contains(x)).ToArray();
|
|
if (Dependencies.Length > 0)
|
|
{
|
|
// Reduce that list to the smallest subset of direct dependencies
|
|
HashSet<Node> DirectDependencies = new HashSet<Node>(Dependencies);
|
|
foreach (Node Dependency in Dependencies)
|
|
{
|
|
DirectDependencies.ExceptWith(Dependency.OrderDependencies);
|
|
}
|
|
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", Badge.Name);
|
|
if (!String.IsNullOrEmpty(Badge.Project))
|
|
{
|
|
JsonWriter.WriteValue("Project", Badge.Project);
|
|
}
|
|
JsonWriter.WriteValue("AllDependencies", String.Join(";", Agents.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
|
|
JsonWriter.WriteValue("DirectDependencies", String.Join(";", DirectDependencies.Select(x => x.Name)));
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
|
|
// Write all the triggers and reports.
|
|
JsonWriter.WriteArrayStart("Reports");
|
|
foreach (Report Report in NameToReport.Values)
|
|
{
|
|
Node[] Dependencies = Report.Nodes.Where(x => NodesToExecute.Contains(x)).ToArray();
|
|
if (Dependencies.Length > 0)
|
|
{
|
|
// Reduce that list to the smallest subset of direct dependencies
|
|
HashSet<Node> DirectDependencies = new HashSet<Node>(Dependencies);
|
|
foreach (Node Dependency in Dependencies)
|
|
{
|
|
DirectDependencies.ExceptWith(Dependency.OrderDependencies);
|
|
}
|
|
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", Report.Name);
|
|
JsonWriter.WriteValue("AllDependencies", String.Join(";", Agents.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
|
|
JsonWriter.WriteValue("DirectDependencies", String.Join(";", DirectDependencies.Select(x => x.Name)));
|
|
JsonWriter.WriteValue("Notify", String.Join(";", Report.NotifyUsers));
|
|
JsonWriter.WriteValue("IsTrigger", false);
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
foreach (ManualTrigger DownstreamTrigger in NameToTrigger.Values)
|
|
{
|
|
if(DownstreamTrigger.Parent == Trigger)
|
|
{
|
|
// Find all the nodes that this trigger is dependent on
|
|
HashSet<Node> Dependencies = new HashSet<Node>();
|
|
foreach(Node NodeToExecute in NodesToExecute)
|
|
{
|
|
if(NodeToExecute.IsBehind(DownstreamTrigger))
|
|
{
|
|
Dependencies.UnionWith(NodeToExecute.OrderDependencies.Where(x => x.ControllingTrigger == Trigger));
|
|
}
|
|
}
|
|
|
|
// Reduce that list to the smallest subset of direct dependencies
|
|
HashSet<Node> DirectDependencies = new HashSet<Node>(Dependencies);
|
|
foreach(Node Dependency in Dependencies)
|
|
{
|
|
DirectDependencies.ExceptWith(Dependency.OrderDependencies);
|
|
}
|
|
|
|
// Write out the object
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", DownstreamTrigger.Name);
|
|
JsonWriter.WriteValue("AllDependencies", String.Join(";", Agents.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
|
|
JsonWriter.WriteValue("DirectDependencies", String.Join(";", Dependencies.Where(x => DirectDependencies.Contains(x)).Select(x => x.Name)));
|
|
JsonWriter.WriteValue("Notify", String.Join(";", DownstreamTrigger.NotifyUsers));
|
|
JsonWriter.WriteValue("IsTrigger", true);
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Print the contents of the graph
|
|
/// </summary>
|
|
/// <param name="CompletedNodes">Set of nodes which are already complete</param>
|
|
/// <param name="PrintOptions">Options for how to print the graph</param>
|
|
public void Print(HashSet<Node> CompletedNodes, GraphPrintOptions PrintOptions)
|
|
{
|
|
// Print the options
|
|
if((PrintOptions & GraphPrintOptions.ShowCommandLineOptions) != 0)
|
|
{
|
|
// Get the list of messages
|
|
List<string> Messages = new List<string>();
|
|
foreach(GraphOption Option in Options)
|
|
{
|
|
StringBuilder Message = new StringBuilder();
|
|
Message.AppendFormat("-set:{0}=... {1}", Option.Name, Option.Description);
|
|
if(!String.IsNullOrEmpty(Option.DefaultValue))
|
|
{
|
|
Message.AppendFormat(" (Default: {0})", Option.DefaultValue);
|
|
}
|
|
Messages.Add(Message.ToString());
|
|
}
|
|
|
|
// Format them to the log
|
|
if(Messages.Count > 0)
|
|
{
|
|
CommandUtils.Log("");
|
|
CommandUtils.Log("Options:");
|
|
CommandUtils.Log("");
|
|
foreach(string Line in CommandUtils.FormatParams(Messages, 4, 24))
|
|
{
|
|
CommandUtils.Log(Line);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get a list of all the triggers, including the null global one
|
|
List<ManualTrigger> AllTriggers = new List<ManualTrigger>();
|
|
AllTriggers.Add(null);
|
|
AllTriggers.AddRange(NameToTrigger.Values.OrderBy(x => x.QualifiedName));
|
|
|
|
// Output all the triggers in order
|
|
CommandUtils.Log("");
|
|
CommandUtils.Log("Graph:");
|
|
foreach(ManualTrigger Trigger in AllTriggers)
|
|
{
|
|
// Filter everything by this trigger
|
|
Dictionary<Agent, Node[]> FilteredAgentToNodes = new Dictionary<Agent,Node[]>();
|
|
foreach(Agent Agent in Agents)
|
|
{
|
|
Node[] Nodes = Agent.Nodes.Where(x => x.ControllingTrigger == Trigger).ToArray();
|
|
if(Nodes.Length > 0)
|
|
{
|
|
FilteredAgentToNodes[Agent] = Nodes;
|
|
}
|
|
}
|
|
|
|
// Skip this trigger if there's nothing to display
|
|
if(FilteredAgentToNodes.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Print the trigger name
|
|
CommandUtils.Log(" Trigger: {0}", (Trigger == null)? "None" : Trigger.QualifiedName);
|
|
if(Trigger != null && PrintOptions.HasFlag(GraphPrintOptions.ShowNotifications))
|
|
{
|
|
foreach(string User in Trigger.NotifyUsers)
|
|
{
|
|
CommandUtils.Log(" notify> {0}", User);
|
|
}
|
|
}
|
|
|
|
// Output all the agents for this trigger
|
|
foreach(Agent Agent in Agents)
|
|
{
|
|
Node[] Nodes;
|
|
if(FilteredAgentToNodes.TryGetValue(Agent, out Nodes))
|
|
{
|
|
CommandUtils.Log(" Agent: {0} ({1})", Agent.Name, String.Join(";", Agent.PossibleTypes));
|
|
foreach(Node Node in Nodes)
|
|
{
|
|
CommandUtils.Log(" Node: {0}{1}", Node.Name, CompletedNodes.Contains(Node)? " (completed)" : "");
|
|
if(PrintOptions.HasFlag(GraphPrintOptions.ShowDependencies))
|
|
{
|
|
HashSet<Node> InputDependencies = new HashSet<Node>(Node.GetDirectInputDependencies());
|
|
foreach(Node InputDependency in InputDependencies)
|
|
{
|
|
CommandUtils.Log(" input> {0}", InputDependency.Name);
|
|
}
|
|
HashSet<Node> OrderDependencies = new HashSet<Node>(Node.GetDirectOrderDependencies());
|
|
foreach(Node OrderDependency in OrderDependencies.Except(InputDependencies))
|
|
{
|
|
CommandUtils.Log(" after> {0}", OrderDependency.Name);
|
|
}
|
|
}
|
|
if(PrintOptions.HasFlag(GraphPrintOptions.ShowNotifications))
|
|
{
|
|
string Label = Node.bNotifyOnWarnings? "warnings" : "errors";
|
|
foreach(string User in Node.NotifyUsers)
|
|
{
|
|
CommandUtils.Log(" {0}> {1}", Label, User);
|
|
}
|
|
foreach(string Submitter in Node.NotifySubmitters)
|
|
{
|
|
CommandUtils.Log(" {0}> submitters to {1}", Label, Submitter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CommandUtils.Log("");
|
|
|
|
// Print out all the aggregates
|
|
string[] AggregateNames = AggregateNameToNodes.Keys.OrderBy(x => x).ToArray();
|
|
if(AggregateNames.Length > 0)
|
|
{
|
|
CommandUtils.Log("Aggregates:");
|
|
foreach(string AggregateName in AggregateNames)
|
|
{
|
|
CommandUtils.Log(" {0}", AggregateName);
|
|
}
|
|
CommandUtils.Log("");
|
|
}
|
|
}
|
|
}
|
|
}
|