// 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; } } }