/// <summary> /// Builds all the nodes in the graph /// </summary> /// <param name="Job">Information about the current job</param> /// <param name="Graph">The graph instance</param> /// <param name="Storage">The temp storage backend which stores the shared state</param> /// <returns>True if everything built successfully</returns> bool BuildAllNodes(JobContext Job, Graph Graph, TempStorage Storage) { // Build a flat list of nodes to execute, in order Node[] NodesToExecute = Graph.Agents.SelectMany(x => x.Nodes).ToArray(); // Check the integrity of any local nodes that have been completed. It's common to run formal builds locally between regular development builds, so we may have // stale local state. Rather than failing later, detect and clean them up now. HashSet<Node> CleanedNodes = new HashSet<Node>(); foreach(Node NodeToExecute in NodesToExecute) { if(NodeToExecute.InputDependencies.Any(x => CleanedNodes.Contains(x)) || !Storage.CheckLocalIntegrity(NodeToExecute.Name, NodeToExecute.Outputs.Select(x => x.TagName))) { Storage.CleanLocalNode(NodeToExecute.Name); CleanedNodes.Add(NodeToExecute); } } // Execute them in order int NodeIdx = 0; foreach(Node NodeToExecute in NodesToExecute) { Log("****** [{0}/{1}] {2}", ++NodeIdx, NodesToExecute.Length, NodeToExecute.Name); if(!Storage.IsComplete(NodeToExecute.Name)) { Log(""); if(!BuildNode(Job, Graph, NodeToExecute, Storage, false)) { return false; } Log(""); } } return true; }
/// <summary> /// Build a node /// </summary> /// <param name="Job">Information about the current job</param> /// <param name="Graph">The graph to which the node belongs. Used to determine which outputs need to be transferred to temp storage.</param> /// <param name="Node">The node to build</param> /// <param name="Storage">The temp storage backend which stores the shared state</param> /// <param name="bWithBanner">Whether to write a banner before and after this node's log output</param> /// <returns>True if the node built successfully, false otherwise.</returns> bool BuildNode(JobContext Job, Graph Graph, Node Node, TempStorage Storage, bool bWithBanner) { DirectoryReference RootDir = new DirectoryReference(CommandUtils.CmdEnv.LocalRoot); // Create the mapping of tag names to file sets Dictionary<string, HashSet<FileReference>> TagNameToFileSet = new Dictionary<string,HashSet<FileReference>>(); // Read all the input tags for this node, and build a list of referenced input storage blocks HashSet<TempStorageBlock> InputStorageBlocks = new HashSet<TempStorageBlock>(); foreach(NodeOutput Input in Node.Inputs) { TempStorageFileList FileList = Storage.ReadFileList(Input.ProducingNode.Name, Input.TagName); TagNameToFileSet[Input.TagName] = FileList.ToFileSet(RootDir); InputStorageBlocks.UnionWith(FileList.Blocks); } // Read all the input storage blocks, keeping track of which block each file came from Dictionary<FileReference, TempStorageBlock> FileToStorageBlock = new Dictionary<FileReference, TempStorageBlock>(); foreach(TempStorageBlock InputStorageBlock in InputStorageBlocks) { TempStorageManifest Manifest = Storage.Retreive(InputStorageBlock.NodeName, InputStorageBlock.OutputName); foreach(FileReference File in Manifest.Files.Select(x => x.ToFileReference(RootDir))) { TempStorageBlock CurrentStorageBlock; if(FileToStorageBlock.TryGetValue(File, out CurrentStorageBlock)) { LogError("File '{0}' was produced by {1} and {2}", File, InputStorageBlock, CurrentStorageBlock); } FileToStorageBlock[File] = InputStorageBlock; } } // Add placeholder outputs for the current node foreach(NodeOutput Output in Node.Outputs) { TagNameToFileSet.Add(Output.TagName, new HashSet<FileReference>()); } // Execute the node if(bWithBanner) { Console.WriteLine(); CommandUtils.Log("========== Starting: {0} ==========", Node.Name); } if(!Node.Build(Job, TagNameToFileSet)) { return false; } if(bWithBanner) { CommandUtils.Log("========== Finished: {0} ==========", Node.Name); Console.WriteLine(); } // Determine all the output files which are required to be copied to temp storage (because they're referenced by nodes in another agent) HashSet<FileReference> ReferencedOutputFiles = new HashSet<FileReference>(); foreach(Agent Agent in Graph.Agents) { bool bSameAgent = Agent.Nodes.Contains(Node); foreach(Node OtherNode in Agent.Nodes) { if(!bSameAgent || Node.ControllingTrigger != OtherNode.ControllingTrigger) { foreach(NodeOutput Input in OtherNode.Inputs.Where(x => x.ProducingNode == Node)) { ReferencedOutputFiles.UnionWith(TagNameToFileSet[Input.TagName]); } } } } // Find a block name for all new outputs Dictionary<FileReference, string> FileToOutputName = new Dictionary<FileReference, string>(); foreach(NodeOutput Output in Node.Outputs) { HashSet<FileReference> Files = TagNameToFileSet[Output.TagName]; foreach(FileReference File in Files) { if(!FileToStorageBlock.ContainsKey(File) && File.IsUnderDirectory(RootDir)) { if(Output == Node.DefaultOutput) { if(!FileToOutputName.ContainsKey(File)) { FileToOutputName[File] = ""; } } else { string OutputName; if(FileToOutputName.TryGetValue(File, out OutputName) && OutputName.Length > 0) { FileToOutputName[File] = String.Format("{0}+{1}", OutputName, Output.TagName.Substring(1)); } else { FileToOutputName[File] = Output.TagName.Substring(1); } } } } } // Invert the dictionary to make a mapping of storage block to the files each contains Dictionary<string, HashSet<FileReference>> OutputStorageBlockToFiles = new Dictionary<string, HashSet<FileReference>>(); foreach(KeyValuePair<FileReference, string> Pair in FileToOutputName) { HashSet<FileReference> Files; if(!OutputStorageBlockToFiles.TryGetValue(Pair.Value, out Files)) { Files = new HashSet<FileReference>(); OutputStorageBlockToFiles.Add(Pair.Value, Files); } Files.Add(Pair.Key); } // Write all the storage blocks, and update the mapping from file to storage block foreach(KeyValuePair<string, HashSet<FileReference>> Pair in OutputStorageBlockToFiles) { TempStorageBlock OutputBlock = new TempStorageBlock(Node.Name, Pair.Key); foreach(FileReference File in Pair.Value) { FileToStorageBlock.Add(File, OutputBlock); } Storage.Archive(Node.Name, Pair.Key, Pair.Value.ToArray(), Pair.Value.Any(x => ReferencedOutputFiles.Contains(x))); } // Publish all the output tags foreach(NodeOutput Output in Node.Outputs) { HashSet<FileReference> Files = TagNameToFileSet[Output.TagName]; HashSet<TempStorageBlock> StorageBlocks = new HashSet<TempStorageBlock>(); foreach(FileReference File in Files) { TempStorageBlock StorageBlock; if(FileToStorageBlock.TryGetValue(File, out StorageBlock)) { StorageBlocks.Add(StorageBlock); } } Storage.WriteFileList(Node.Name, Output.TagName, Files, StorageBlocks.ToArray()); } // Mark the node as succeeded Storage.MarkAsComplete(Node.Name); return true; }
/// <summary> /// Find all the nodes in the graph which are already completed /// </summary> /// <param name="Graph">The graph instance</param> /// <param name="Storage">The temp storage backend which stores the shared state</param> HashSet<Node> FindCompletedNodes(Graph Graph, TempStorage Storage) { HashSet<Node> CompletedNodes = new HashSet<Node>(); foreach(Node Node in Graph.Agents.SelectMany(x => x.Nodes)) { if(Storage.IsComplete(Node.Name)) { CompletedNodes.Add(Node); } } return CompletedNodes; }