Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.BuildGraph/BgGraphDef.cs
2025-05-18 13:04:45 +08:00

850 lines
27 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
[assembly: InternalsVisibleTo("EpicGames.BuildGraph.Tests")]
namespace EpicGames.BuildGraph
{
/// <summary>
/// Options for how the graph should be printed
/// </summary>
[Flags]
public enum GraphPrintOptions
{
/// <summary>
/// No options specified
/// </summary>
None = 0,
/// <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>
/// Definition of a graph.
/// </summary>
public class BgGraphDef
{
/// <summary>
/// List of options, in the order they were specified
/// </summary>
public List<BgOptionDef> Options { get; } = new List<BgOptionDef>();
/// <summary>
/// List of agents containing nodes to execute
/// </summary>
public List<BgAgentDef> Agents { get; } = new List<BgAgentDef>();
/// <summary>
/// Mapping from name to agent
/// </summary>
public Dictionary<string, BgAgentDef> NameToAgent { get; set; } = new Dictionary<string, BgAgentDef>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Mapping of names to the corresponding node.
/// </summary>
public Dictionary<string, BgNodeDef> NameToNode { get; set; } = new Dictionary<string, BgNodeDef>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Mapping of names to the corresponding report.
/// </summary>
public Dictionary<string, BgReport> NameToReport { get; private set; } = new Dictionary<string, BgReport>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Mapping of names to their corresponding node output.
/// </summary>
public Dictionary<string, BgNodeOutput> TagNameToNodeOutput { get; private set; } = new Dictionary<string, BgNodeOutput>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Mapping of aggregate names to their respective nodes
/// </summary>
public Dictionary<string, BgAggregateDef> NameToAggregate { get; private set; } = new Dictionary<string, BgAggregateDef>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Mapping of artifact names to their definitions. Artifacts will be produced from matching tag names.
/// </summary>
public List<BgArtifactDef> Artifacts { get; } = new List<BgArtifactDef>();
/// <summary>
/// List of badges that can be displayed for this build
/// </summary>
public List<BgBadgeDef> Badges { get; } = new List<BgBadgeDef>();
/// <summary>
/// List of labels that can be displayed for this build
/// </summary>
public List<BgLabelDef> Labels { get; } = new List<BgLabelDef>();
/// <summary>
/// Diagnostics at graph scope
/// </summary>
public List<BgDiagnosticDef> Diagnostics { get; } = new List<BgDiagnosticDef>();
/// <summary>
/// Default constructor
/// </summary>
public BgGraphDef()
{
}
/// <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) || NameToAggregate.ContainsKey(name);
}
/// <summary>
/// Gets diagnostics from all graph structures
/// </summary>
/// <returns>List of diagnostics</returns>
public List<BgDiagnosticDef> GetAllDiagnostics()
{
List<BgDiagnosticDef> diagnostics = new List<BgDiagnosticDef>(Diagnostics);
foreach (BgAgentDef agent in Agents)
{
diagnostics.AddRange(agent.Diagnostics);
foreach (BgNodeDef node in agent.Nodes)
{
diagnostics.AddRange(node.Diagnostics);
}
}
return diagnostics;
}
/// <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, [NotNullWhen(true)] out BgNodeDef[]? outNodes)
{
// Check if it's a tag reference or node reference
if (name.StartsWith("#", StringComparison.Ordinal))
{
// Check if it's a regular node or output name
BgNodeOutput? output;
if (TagNameToNodeOutput.TryGetValue(name, out output))
{
outNodes = new BgNodeDef[] { output.ProducingNode };
return true;
}
}
else
{
// Check if it's a regular node or output name
BgNodeDef? node;
if (NameToNode.TryGetValue(name, out node))
{
outNodes = new BgNodeDef[] { node };
return true;
}
// Check if it's an aggregate name
BgAggregateDef? aggregate;
if (NameToAggregate.TryGetValue(name, out aggregate))
{
outNodes = aggregate.RequiredNodes.ToArray();
return true;
}
// Check if it's a group name
BgAgentDef? 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, [NotNullWhen(true)] out BgNodeOutput[]? outOutputs)
{
// Check if it's a tag reference or node reference
if (name.StartsWith("#", StringComparison.Ordinal))
{
// Check if it's a regular node or output name
BgNodeOutput? output;
if (TagNameToNodeOutput.TryGetValue(name, out output))
{
outOutputs = new BgNodeOutput[] { output };
return true;
}
}
else
{
// Check if it's a regular node or output name
BgNodeDef? node;
if (NameToNode.TryGetValue(name, out node))
{
outOutputs = node.Outputs.Union(node.Inputs).ToArray();
return true;
}
// Check if it's an aggregate name
BgAggregateDef? aggregate;
if (NameToAggregate.TryGetValue(name, out aggregate))
{
outOutputs = aggregate.RequiredNodes.SelectMany(x => x.Outputs.Union(x.Inputs)).Distinct().ToArray();
return true;
}
}
// Otherwise fail
outOutputs = null;
return false;
}
static void AddDependencies(BgNodeDef node, HashSet<BgNodeDef> retainNodes)
{
if (retainNodes.Add(node))
{
foreach (BgNodeDef inputDependency in node.InputDependencies)
{
AddDependencies(inputDependency, retainNodes);
}
}
}
/// <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<BgNodeDef> targetNodes)
{
// Find this node and all its dependencies
HashSet<BgNodeDef> retainNodes = new HashSet<BgNodeDef>();
foreach (BgNodeDef targetNode in targetNodes)
{
AddDependencies(targetNode, retainNodes);
}
// Remove all the nodes which are not marked to be kept
foreach (BgAgentDef 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 (BgReport 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 (BgNodeDef node in retainNodes)
{
node.OrderDependencies.RemoveAll(x => !retainNodes.Contains(x));
}
// Create a new list of aggregates for everything that's left
Dictionary<string, BgAggregateDef> newNameToAggregate = new Dictionary<string, BgAggregateDef>(NameToAggregate.Comparer);
foreach (BgAggregateDef aggregate in NameToAggregate.Values)
{
if (aggregate.RequiredNodes.All(x => retainNodes.Contains(x)))
{
newNameToAggregate[aggregate.Name] = aggregate;
}
}
NameToAggregate = newNameToAggregate;
// Remove any labels that are no longer valid
foreach (BgLabelDef label in Labels)
{
label.RequiredNodes.RemoveWhere(x => !retainNodes.Contains(x));
label.IncludedNodes.RemoveWhere(x => !retainNodes.Contains(x));
}
Labels.RemoveAll(x => x.RequiredNodes.Count == 0);
// Remove any badges which do not have all their dependencies
Badges.RemoveAll(x => x.Nodes.Any(y => !retainNodes.Contains(y)));
// Rebuild the tag name to output dictionary
Dictionary<string, BgNodeOutput> newTagNameToNodeOutput = new Dictionary<string, BgNodeOutput>(TagNameToNodeOutput.Comparer);
foreach (BgNodeOutput output in Agents.SelectMany(x => x.Nodes).SelectMany(x => x.Outputs))
{
newTagNameToNodeOutput.Add(output.TagName, output);
}
TagNameToNodeOutput = newTagNameToNodeOutput;
// Remove any artifacts whose outputs are not produced
Artifacts.RemoveAll(x => x.TagName != null && !TagNameToNodeOutput.ContainsKey(x.TagName));
// Remove any artifacts whose nodes are no longer run
HashSet<string> newNodeNames = Agents.SelectMany(x => x.Nodes).Select(x => x.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
Artifacts.RemoveAll(x => x.NodeName != null && !newNodeNames.Contains(x.NodeName));
}
/// <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="completedNodes">Set of nodes which have been completed</param>
public void Export(FileReference file, HashSet<BgNodeDef> completedNodes)
{
// Find all the nodes which we're actually going to execute. We'll use this to filter the graph.
HashSet<BgNodeDef> nodesToExecute = new HashSet<BgNodeDef>();
foreach (BgNodeDef node in Agents.SelectMany(x => x.Nodes))
{
if (!completedNodes.Contains(node))
{
nodesToExecute.Add(node);
}
}
// Open the output file
using (JsonWriter jsonWriter = new JsonWriter(file.FullName))
{
jsonWriter.WriteObjectStart();
// Write all the agents
jsonWriter.WriteArrayStart("Groups");
foreach (BgAgentDef agent in Agents)
{
BgNodeDef[] nodes = agent.Nodes.Where(x => nodesToExecute.Contains(x)).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 (BgNodeDef node in nodes)
{
jsonWriter.WriteObjectStart();
jsonWriter.WriteValue("Name", node.Name);
jsonWriter.WriteValue("DependsOn", String.Join(";", node.GetDirectOrderDependencies().Where(x => nodesToExecute.Contains(x))));
jsonWriter.WriteValue("RunEarly", node.RunEarly);
jsonWriter.WriteObjectStart("Notify");
jsonWriter.WriteValue("Default", String.Join(";", node.NotifyUsers));
jsonWriter.WriteValue("Submitters", String.Join(";", node.NotifySubmitters));
jsonWriter.WriteValue("Warnings", node.NotifyOnWarnings);
jsonWriter.WriteObjectEnd();
jsonWriter.WriteObjectEnd();
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteObjectEnd();
}
}
jsonWriter.WriteArrayEnd();
// Write all the badges
jsonWriter.WriteArrayStart("Badges");
foreach (BgBadgeDef badge in Badges)
{
BgNodeDef[] dependencies = badge.Nodes.Where(x => nodesToExecute.Contains(x)).ToArray();
if (dependencies.Length > 0)
{
// Reduce that list to the smallest subset of direct dependencies
HashSet<BgNodeDef> directDependencies = new HashSet<BgNodeDef>(dependencies);
foreach (BgNodeDef dependency in dependencies)
{
directDependencies.ExceptWith(dependency.OrderDependencies);
}
jsonWriter.WriteObjectStart();
jsonWriter.WriteValue("Name", badge.Name);
if (!String.IsNullOrEmpty(badge.Project))
{
jsonWriter.WriteValue("Project", badge.Project);
}
if (badge.Change != 0)
{
jsonWriter.WriteValue("Change", badge.Change);
}
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 (BgReport report in NameToReport.Values)
{
BgNodeDef[] dependencies = report.Nodes.Where(x => nodesToExecute.Contains(x)).ToArray();
if (dependencies.Length > 0)
{
// Reduce that list to the smallest subset of direct dependencies
HashSet<BgNodeDef> directDependencies = new HashSet<BgNodeDef>(dependencies);
foreach (BgNodeDef 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();
}
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteObjectEnd();
}
}
/// <summary>
/// Export the build graph to a Json file for parsing by Horde
/// </summary>
/// <param name="file">Output file to write</param>
public void ExportForHorde(FileReference file)
{
DirectoryReference.CreateDirectory(file.Directory);
using (JsonWriter jsonWriter = new JsonWriter(file.FullName))
{
jsonWriter.WriteObjectStart();
jsonWriter.WriteArrayStart("Artifacts");
foreach (BgArtifactDef artifact in Artifacts)
{
jsonWriter.WriteObjectStart();
jsonWriter.WriteValue("Name", artifact.Name);
if (!String.IsNullOrEmpty(artifact.Type))
{
jsonWriter.WriteValue("Type", artifact.Type);
}
if (!String.IsNullOrEmpty(artifact.Description))
{
jsonWriter.WriteValue("Description", artifact.Description);
}
if (!String.IsNullOrEmpty(artifact.BasePath))
{
jsonWriter.WriteValue("BasePath", artifact.BasePath);
}
if (artifact.Keys.Count > 0)
{
jsonWriter.WriteArrayStart("Keys");
foreach (string key in artifact.Keys)
{
jsonWriter.WriteValue(key);
}
jsonWriter.WriteArrayEnd();
}
if (artifact.Metadata.Count > 0)
{
jsonWriter.WriteArrayStart("Metadata");
foreach (string metadata in artifact.Metadata)
{
jsonWriter.WriteValue(metadata);
}
jsonWriter.WriteArrayEnd();
}
if (!String.IsNullOrEmpty(artifact.NodeName))
{
jsonWriter.WriteValue("NodeName", artifact.NodeName);
}
if (!String.IsNullOrEmpty(artifact.TagName))
{
jsonWriter.WriteValue("OutputName", artifact.TagName);
}
jsonWriter.WriteObjectEnd();
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("Groups");
foreach (BgAgentDef agent in Agents)
{
jsonWriter.WriteObjectStart();
jsonWriter.WriteArrayStart("Types");
foreach (string possibleType in agent.PossibleTypes)
{
jsonWriter.WriteValue(possibleType);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("Nodes");
foreach (BgNodeDef node in agent.Nodes)
{
jsonWriter.WriteObjectStart();
jsonWriter.WriteValue("Name", node.Name);
jsonWriter.WriteValue("RunEarly", node.RunEarly);
jsonWriter.WriteValue("Warnings", node.NotifyOnWarnings);
jsonWriter.WriteArrayStart("Inputs");
foreach (BgNodeOutput input in node.Inputs)
{
jsonWriter.WriteValue(input.TagName);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("Outputs");
foreach (BgNodeOutput output in node.Outputs)
{
jsonWriter.WriteValue(output.TagName);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("InputDependencies");
foreach (string inputDependency in node.GetDirectInputDependencies().Select(x => x.Name))
{
jsonWriter.WriteValue(inputDependency);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("OrderDependencies");
foreach (string orderDependency in node.GetDirectOrderDependencies().Select(x => x.Name))
{
jsonWriter.WriteValue(orderDependency);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteObjectStart("Annotations");
foreach ((string key, string value) in node.Annotations)
{
jsonWriter.WriteValue(key, value);
}
jsonWriter.WriteObjectEnd();
jsonWriter.WriteObjectEnd();
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteObjectEnd();
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("Aggregates");
foreach (BgAggregateDef aggregate in NameToAggregate.Values)
{
jsonWriter.WriteObjectStart();
jsonWriter.WriteValue("Name", aggregate.Name);
jsonWriter.WriteArrayStart("Nodes");
foreach (BgNodeDef requiredNode in aggregate.RequiredNodes.OrderBy(x => x.Name))
{
jsonWriter.WriteValue(requiredNode.Name);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteObjectEnd();
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("Labels");
foreach (BgLabelDef label in Labels)
{
jsonWriter.WriteObjectStart();
if (!String.IsNullOrEmpty(label.DashboardName))
{
jsonWriter.WriteValue("Name", label.DashboardName);
}
if (!String.IsNullOrEmpty(label.DashboardCategory))
{
jsonWriter.WriteValue("Category", label.DashboardCategory);
}
if (!String.IsNullOrEmpty(label.UgsBadge))
{
jsonWriter.WriteValue("UgsBadge", label.UgsBadge);
}
if (!String.IsNullOrEmpty(label.UgsProject))
{
jsonWriter.WriteValue("UgsProject", label.UgsProject);
}
if (label.Change != BgLabelChange.Current)
{
jsonWriter.WriteValue("Change", label.Change.ToString());
}
jsonWriter.WriteArrayStart("RequiredNodes");
foreach (BgNodeDef requiredNode in label.RequiredNodes.OrderBy(x => x.Name))
{
jsonWriter.WriteValue(requiredNode.Name);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("IncludedNodes");
foreach (BgNodeDef includedNode in label.IncludedNodes.OrderBy(x => x.Name))
{
jsonWriter.WriteValue(includedNode.Name);
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteObjectEnd();
}
jsonWriter.WriteArrayEnd();
jsonWriter.WriteArrayStart("Badges");
foreach (BgBadgeDef badge in Badges)
{
HashSet<BgNodeDef> dependencies = badge.Nodes;
if (dependencies.Count > 0)
{
// Reduce that list to the smallest subset of direct dependencies
HashSet<BgNodeDef> directDependencies = new HashSet<BgNodeDef>(dependencies);
foreach (BgNodeDef dependency in dependencies)
{
directDependencies.ExceptWith(dependency.OrderDependencies);
}
jsonWriter.WriteObjectStart();
jsonWriter.WriteValue("Name", badge.Name);
if (!String.IsNullOrEmpty(badge.Project))
{
jsonWriter.WriteValue("Project", badge.Project);
}
if (badge.Change != 0)
{
jsonWriter.WriteValue("Change", badge.Change);
}
jsonWriter.WriteValue("Dependencies", String.Join(";", directDependencies.Select(x => x.Name)));
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>
/// <param name="logger"></param>
public void Print(HashSet<BgNodeDef> completedNodes, GraphPrintOptions printOptions, ILogger logger)
{
// Print the options
if ((printOptions & GraphPrintOptions.ShowCommandLineOptions) != 0)
{
// Get the list of messages
List<KeyValuePair<string, string>> parameters = new List<KeyValuePair<string, string>>();
foreach (BgOptionDef option in Options)
{
string name = String.Format("-set:{0}=...", option.Name);
StringBuilder description = new StringBuilder(option.Description);
string? defaultValue = null;
if (option is BgBoolOptionDef boolOption)
{
defaultValue = boolOption.DefaultValue.ToString();
}
else if (option is BgIntOptionDef intOption)
{
defaultValue = intOption.DefaultValue.ToString();
}
else if (option is BgStringOptionDef stringOption)
{
defaultValue = stringOption.DefaultValue;
}
if (!String.IsNullOrEmpty(defaultValue))
{
description.AppendFormat(" (Default: {0})", defaultValue);
}
parameters.Add(new KeyValuePair<string, string>(name, description.ToString()));
}
// Format them to the log
if (parameters.Count > 0)
{
logger.LogInformation("");
logger.LogInformation("Options:");
logger.LogInformation("");
List<string> lines = new List<string>();
HelpUtils.FormatTable(parameters, 4, 24, HelpUtils.WindowWidth - 20, lines);
foreach (string line in lines)
{
logger.Log(LogLevel.Information, "{Line}", line);
}
}
}
// Output all the triggers in order
logger.LogInformation("");
logger.LogInformation("Graph:");
foreach (BgAgentDef agent in Agents)
{
logger.LogInformation(" Agent: {Name} ({Types})", agent.Name, String.Join(";", agent.PossibleTypes));
foreach (BgNodeDef node in agent.Nodes)
{
logger.LogInformation(" Node: {Name}{Type}", node.Name, completedNodes.Contains(node) ? " (completed)" : node.RunEarly ? " (early)" : "");
if (printOptions.HasFlag(GraphPrintOptions.ShowDependencies))
{
HashSet<BgNodeDef> inputDependencies = new HashSet<BgNodeDef>(node.GetDirectInputDependencies());
foreach (BgNodeDef inputDependency in inputDependencies)
{
logger.LogInformation(" input> {Name}", inputDependency.Name);
}
HashSet<BgNodeDef> orderDependencies = new HashSet<BgNodeDef>(node.GetDirectOrderDependencies());
foreach (BgNodeDef orderDependency in orderDependencies.Except(inputDependencies))
{
logger.LogInformation(" after> {Name}", orderDependency.Name);
}
}
if (printOptions.HasFlag(GraphPrintOptions.ShowNotifications))
{
string label = node.NotifyOnWarnings ? "warnings" : "errors";
foreach (string user in node.NotifyUsers)
{
logger.LogInformation(" {Name}> {User}", label, user);
}
foreach (string submitter in node.NotifySubmitters)
{
logger.LogInformation(" {Name}> submitters to {Submitter}", label, submitter);
}
}
}
}
logger.LogInformation("");
// Print out all the non-empty aggregates
BgAggregateDef[] aggregates = NameToAggregate.Values.OrderBy(x => x.Name).ToArray();
if (aggregates.Length > 0)
{
logger.LogInformation("Aggregates:");
foreach (string aggregateName in aggregates.Select(x => x.Name))
{
logger.LogInformation(" {Aggregate}", aggregateName);
}
logger.LogInformation("");
}
// Print all the produced artifacts
BgArtifactDef[] artifacts = Artifacts.OrderBy(x => x.Name).ToArray();
if (artifacts.Length > 0)
{
logger.LogInformation("Artifacts:");
foreach (BgArtifactDef artifact in artifacts.OrderBy(x => x.Description))
{
logger.LogInformation(" {Name}", artifact.Name);
}
logger.LogInformation("");
}
}
}
/// <summary>
/// Definition of a graph from bytecode. Can be converted to regular graph definition.
/// </summary>
public class BgGraphExpressionDef
{
/// <summary>
/// Nodes for the graph
/// </summary>
public List<BgNodeExpressionDef> Nodes { get; } = new List<BgNodeExpressionDef>();
/// <summary>
/// Aggregates for the graph
/// </summary>
public List<BgAggregateExpressionDef> Aggregates { get; } = new List<BgAggregateExpressionDef>();
/// <summary>
/// Creates a graph definition from this template
/// </summary>
/// <returns></returns>
public BgGraphDef ToGraphDef()
{
List<BgNodeExpressionDef> nodes = new List<BgNodeExpressionDef>(Nodes);
BgGraphDef graph = new BgGraphDef();
foreach (BgAggregateExpressionDef aggregate in Aggregates)
{
graph.NameToAggregate[aggregate.Name] = aggregate.ToAggregateDef();
nodes.AddRange(aggregate.RequiredNodes.Select(x => (BgNodeExpressionDef)x));
}
HashSet<BgNodeDef> uniqueNodes = new HashSet<BgNodeDef>();
HashSet<BgAgentDef> uniqueAgents = new HashSet<BgAgentDef>();
foreach (BgNodeExpressionDef node in nodes)
{
RegisterNode(graph, node, uniqueNodes, uniqueAgents);
}
HashSet<BgLabelDef> labels = new HashSet<BgLabelDef>();
foreach (BgNodeExpressionDef node in nodes)
{
foreach (BgLabelDef label in node.Labels)
{
label.RequiredNodes.Add(node);
label.IncludedNodes.Add(node);
labels.Add(label);
}
}
foreach (BgAggregateExpressionDef aggregate in Aggregates)
{
if (aggregate.Label != null)
{
aggregate.Label.RequiredNodes.UnionWith(aggregate.RequiredNodes);
aggregate.Label.IncludedNodes.UnionWith(aggregate.RequiredNodes);
labels.Add(aggregate.Label);
}
}
graph.Labels.AddRange(labels);
return graph;
}
static void RegisterNode(BgGraphDef graph, BgNodeExpressionDef node, HashSet<BgNodeDef> uniqueNodes, HashSet<BgAgentDef> uniqueAgents)
{
if (uniqueNodes.Add(node))
{
foreach (BgNodeExpressionDef inputNode in node.InputDependencies.OfType<BgNodeExpressionDef>())
{
RegisterNode(graph, inputNode, uniqueNodes, uniqueAgents);
}
BgAgentDef agent = node.Agent;
if (uniqueAgents.Add(agent))
{
graph.Agents.Add(agent);
graph.NameToAgent.Add(agent.Name, agent);
}
agent.Nodes.Add(node);
graph.NameToNode.Add(node.Name, node);
}
}
}
}