// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using EpicGames.BuildGraph.Expressions;
using EpicGames.Core;
namespace EpicGames.BuildGraph
{
///
/// Reference to an output tag from a particular node
///
public class BgNodeOutput
{
///
/// The node which produces the given output
///
public BgNodeDef ProducingNode { get; }
///
/// Name of the tag
///
public string TagName { get; }
///
/// Constructor
///
/// Node which produces the given output
/// Name of the tag
public BgNodeOutput(BgNodeDef producingNode, string tagName)
{
ProducingNode = producingNode;
TagName = tagName;
}
///
/// Returns a string representation of this output for debugging purposes
///
/// The name of this output
public override string ToString()
{
return String.Format("{0} [{1}]", TagName, ProducingNode.Name);
}
}
///
/// Describes a dependency on a node output
///
[BgObject(typeof(BgNodeOutputExprDefSerializer))]
public class BgNodeOutputExprDef
{
///
/// The producing node
///
public BgNodeExpressionDef ProducingNode { get; }
///
/// The output index. -1 means all inputs and outputs for the node.
///
public int OutputIndex { get; }
///
/// Constructor
///
///
///
public BgNodeOutputExprDef(BgNodeExpressionDef producingNode, int outputIndex)
{
ProducingNode = producingNode;
OutputIndex = outputIndex;
}
///
/// Flattens this expression to a list of outputs
///
///
public IEnumerable Flatten()
{
if (OutputIndex == -1)
{
return ProducingNode.InputDependencies.SelectMany(x => x.Outputs).Concat(ProducingNode.Outputs).ToArray();
}
else
{
return new[] { ProducingNode.Outputs[OutputIndex] };
}
}
}
class BgNodeOutputExprDefSerializer : BgObjectSerializer
{
///
public override BgNodeOutputExprDef Deserialize(BgObjectDef obj)
{
return new BgNodeOutputExprDef(obj.Get(x => x.ProducingNode, null!), obj.Get(x => x.OutputIndex, -1));
}
}
///
/// Defines a node, a container for tasks and the smallest unit of execution that can be run as part of a build graph.
///
public class BgNodeDef
{
///
/// The node's name
///
public string Name { get; }
///
/// Thunk to execute this node.
///
public BgThunkDef? Thunk { get; }
///
/// Array of inputs which this node requires to run
///
public List Inputs { get; } = new List();
///
/// Array of outputs produced by this node
///
public IReadOnlyList Outputs { get; }
///
/// Nodes which this node has input dependencies on
///
public List InputDependencies { get; } = new List();
///
/// Nodes which this node needs to run after
///
public List OrderDependencies { get; } = new List();
///
/// Tokens which must be acquired for this node to run
///
public List RequiredTokens { get; } = new List();
///
/// List of email addresses to notify if this node fails.
///
public HashSet NotifyUsers { get; set; } = 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 { get; set; } = new HashSet(StringComparer.InvariantCultureIgnoreCase);
///
/// Whether to start this node as soon as its dependencies are satisfied, rather than waiting for all of its agent's dependencies to be met.
///
public bool RunEarly { get; set; } = false;
///
/// Whether to ignore warnings produced by this node
///
public bool NotifyOnWarnings { get; set; } = true;
///
/// Custom annotations for this node
///
public Dictionary Annotations { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
///
/// Ignore modified files matching the patterns provided
///
public List IgnoreModified { get; set; } = new List();
///
/// Diagnostics to output if executing this node
///
public List Diagnostics { get; } = new List();
///
/// Constructor
///
public BgNodeDef(string name, BgThunkDef? thunk, IReadOnlyList outputNames)
{
Name = name;
Thunk = thunk;
List allOutputs = new List();
allOutputs.Add(new BgNodeOutput(this, "#" + Name));
allOutputs.AddRange(outputNames.Where(x => !String.Equals(x, Name, StringComparison.OrdinalIgnoreCase)).Select(x => new BgNodeOutput(this, x)));
Outputs = allOutputs.ToArray();
}
///
/// 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.
/// Optional tokens which must be required for this node to run
/// File patterns to ignore when checking for modified timestamps
public BgNodeDef(string name, IReadOnlyList inputs, IReadOnlyList outputNames, IReadOnlyList inputDependencies, IReadOnlyList orderDependencies, IReadOnlyList requiredTokens, IReadOnlyList ignoreModified)
: this(name, null, outputNames)
{
Name = name;
Inputs.AddRange(inputs);
InputDependencies.AddRange(inputDependencies);
OrderDependencies.AddRange(orderDependencies);
RequiredTokens.AddRange(requiredTokens);
IgnoreModified.AddRange(ignoreModified);
}
///
/// Returns the default output for this node, which includes all build products
///
public BgNodeOutput DefaultOutput => Outputs[0];
///
/// 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 (BgNodeDef 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 (BgNodeDef orderDependency in OrderDependencies)
{
directDependencies.ExceptWith(orderDependency.OrderDependencies);
}
return directDependencies;
}
///
/// Returns the name of this node
///
/// The name of this node
public override string ToString()
{
return Name;
}
}
///
/// Node constructed from a bytecode expression
///
[BgObject(typeof(BgNodeExpressionDefSerializer))]
public class BgNodeExpressionDef : BgNodeDef
{
///
/// Agent declaring this node
///
public BgAgentDef Agent { get; }
///
/// Labels to add this node to
///
public List Labels { get; } = new List();
///
/// Input expressions
///
public List InputExprs { get; } = new List();
///
/// Number of outputs from this node
///
public int OutputCount { get; }
///
/// Constructor
///
public BgNodeExpressionDef(BgAgentDef agent, string name, BgThunkDef thunk, int outputCount)
: base(name, thunk, GetOutputNames(name, outputCount))
{
Agent = agent;
OutputCount = outputCount;
}
static string[] GetOutputNames(string name, int numOutputs)
{
return Enumerable.Range(0, numOutputs).Select(x => BgNode.GetDefaultTagName(name, x)).ToArray();
}
}
class BgNodeExpressionDefSerializer : BgObjectSerializer
{
///
public override BgNodeExpressionDef Deserialize(BgObjectDef obj)
{
BgNodeExpressionDef node = new BgNodeExpressionDef(obj.Get(x => x.Agent, null!), obj.Get(x => x.Name, ""), obj.Get(x => x.Thunk!, null!), obj.Get(x => x.OutputCount, 0));
obj.CopyTo(node);
node.Inputs.AddRange(node.InputExprs.SelectMany(x => x.Flatten()));
HashSet inputDependencies = new HashSet();
foreach (BgNodeOutput input in node.Inputs)
{
inputDependencies.Add(input.ProducingNode);
inputDependencies.UnionWith(input.ProducingNode.InputDependencies);
}
inputDependencies.ExceptWith(node.InputDependencies);
node.InputDependencies.AddRange(inputDependencies);
return node;
}
}
}