Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/BuildGraph/BuildGraph.cs
Ben Marsh 3dbefdf14d Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3047776)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3021930 on 2016/06/21 by Ben.Marsh

	BuildGraph: Better diagnostic message if the source directory for copies does not exist.

Change 3022391 on 2016/06/21 by Ben.Marsh

	Rework copy task slightly so that all code paths result in files being tagged.

Change 3026592 on 2016/06/24 by Ben.Marsh

	BuildGraph: Add a ForEach element, which will assign a local property to each of a semicolon separated list of values, and expand the elements within it. Added an example in Properties.xml.

Change 3028708 on 2016/06/27 by Matthew.Griffin

	Converting Engine build process to BuildGraph script
	Added Tag Receipts task to retrieve list of build products/dependencies from *.target files.
	Changed Pak File task so that you can specify an existing response file, rather than creating one from the file list.
	Changed base task so that you can resolve filespec from a list of file patterns if you already have them separated, which was the case with wildcards in runtime dependencies.
	Added EngineMajorVersion, EngineMinorVersion and EnginePatchVersion as default properties available to BuildGraph
	Added FinalizeInstalledBuild command to write out InstalledBuild.txt file and config entries for installed platforms
	Included .exe.config and exe.mdb files to build products of CsCompile task if they exist
	Added TagReferences option to CsCompile so that you can get any external references projects have that need to be included when staging
	Added RunOptions parameter to SpawnTask, so that you can specify these for the exe you want to run
	Added missing Runtime Dependency for ICU on Mac

Change 3030209 on 2016/06/28 by Matthew.Griffin

	Renamed EngineBuild.xml to InstalledEngineBuild.xml to make its purpose more clear.
	Removed reference to xcodeunlock.sh from Mac Installed build dependencies as the file itself has been deleted.
	Added myself to list of notifiers for failures in the UE4 Binary build.

Change 3034068 on 2016/06/30 by Ben.Marsh

	BuildGraph: Change scoping rules for properties. Local properties can no longer shadow global properties with the same name (or vice versa), and local properties are always modified in the scope that they were first declared, rather than being re-declared in a narrower scope.

Change 3034070 on 2016/06/30 by Ben.Marsh

	BuildGraph: Warn when referencing a property which is not defined, and add new attributes to the <Property> element to set the default value for a property if it's not already set, and validating that it's one of a list of valid values if it is (eg. <Property Name="WithWin64" Restrict="true;false" Default="false"/>).

Change 3034110 on 2016/06/30 by Matthew.Griffin

	Updated Installed Build so that properties are consistently named Exceptions and that the right versions are used
	Added Filter and Exception properties for each target platform to add any files that can't be figured out via dependencies
	Added Default values for various properties used across Engine build scripts - IsReleaseBranch, IsPreflight, OutputDir, BuildLabel, WithWin64 etc.
	Tagged Generated Includes from each target so that they can be included in Installed Build
	Added additional Android architectures to Shipping build
	Changed SwarmCoordinator to build for Any CPU
	Removed Local HostPlatform property from DDC nodes
	Changed Installed Build target platforms to use Do blocks so that we only have to check With... property once
	Reordered stripping and signing process so that we use the Exception check in less places

Change 3035499 on 2016/07/01 by Ben.Marsh

	BuildGraph: Remove the <Local> element, and just make all <Property> declarations scoped. Also add an error if a property is later declared in a parent scope, since the earlier assignment won't be visible to the later one.

Change 3035520 on 2016/07/01 by Ben.Marsh

	BuildGraph: Add support for <, <=, >, >= operators in condition expressions.

Change 3035666 on 2016/07/01 by Matthew.Griffin

	Added more parameters to Chunk and Label Build tasks
	Updated all remaining uses of Local to Property in Installed Build script
	Made sure Feature Packs use paths compatible with Mac and also changed the node to use a ForEach element

Change 3037020 on 2016/07/04 by Matthew.Griffin

	Ensured that TempStorageFileList uses forward slashes as its path separators so that it's easily used on Mac and Windows
	Was causing the results of the Make Feature Packs node to be tagged using Windows style paths, meaning they would throw an error if you tried to copy them on Mac

Change 3037052 on 2016/07/04 by Ben.Marsh

	Move FJsonValue::ErrorMessage into cpp file, since it depends on the log class defined in Json.h (which includes it).

Change 3037283 on 2016/07/05 by Matthew.Griffin

	Removed EnterScope and LeaveScope from ReadGraphBody so that included files are treated as being in the same scope (allows use of properties across files)

Change 3037547 on 2016/07/05 by Ben.Marsh

	UAT: Allow CommandUtils.Run() to check directories listed in the PATH environment variable for the executable before failing.

Change 3037552 on 2016/07/05 by Ben.Marsh

	BuildGraph: Add an <Unzip> task, which extracts a zip file to an output directory.

Change 3039109 on 2016/07/06 by Matthew.Griffin

	Moved tagging of UAT build products to the Installed Build step as it's the only thing that needs them
	Moved Strip and Sign filters to the filters file, made sure they're used for all operations and added stripping back to UE4Editor nodes
	Changed BuildPatchTool to be built in shipping mode
	Changed all C# projects to be compiled for AnyCPU as they ended up in different output folders otherwise
	Added all files referenced by C# projects to avoid having to filter them manually
	Changed filters to get files included for Linux closer to the old pattern
	Changed Build DDC command to ignore empty entries in FeaturePacks list, don't want to fail the process if a list begins with a ;
	Changed UE4Game to use shipping PhysX libs for Shipping builds
	Added glut32.dll as a Runtime Dependency for PhysX
	Added libsteam_api.so as a Runtime Dependency for Steamworks on Linux

Change 3039676 on 2016/07/06 by Ben.Marsh

	Core: Move definitions for FORCEINLINE'd FMath functions into UnrealMathUtility. Prevents link errors if including one without the other.

Change 3039681 on 2016/07/06 by Ben.Marsh

	Core: Move implementation of GetTypeHash(FTimespan) into CPP file, to remove implicit dependency on the inline implementation of GetTypeHash(int64) being included.

Change 3039735 on 2016/07/06 by Ben.Marsh

	Core: Move USE_DELEGATE_TRYGETBOUNDFUNCTIONNAME into a separate header, so delegate headers can be included separately.

Change 3039878 on 2016/07/06 by Ben.Marsh

	Core: Move FOperatorFunctionID out of TOperatorJumpTable to allow MSVC to compile it and catch errors before the template is instantiated.

Change 3040156 on 2016/07/06 by Ben.Marsh

	Core: Move FDateTime::GetTypeHash() into cpp file to eliminate dependency on TypeHash.h being included before it.

Change 3041009 on 2016/07/07 by Matthew.Griffin

	Changed UE4Game to only use shipping PhysX libraries on Windows

Change 3041015 on 2016/07/07 by Leigh.Swift

	UBT: Support creating C# programs that will be included in the UE4.sln Programs list.
	To have your program listed, remove the sln file that may have been created for you, and add a file named "UE4CSharp.prog" next to your csproj file.

Change 3041234 on 2016/07/07 by Matthew.Griffin

	Added building of Launcher Samples to BuildGraph system
	Added Command to Build Sample projects, which distills to temp directory, builds DDC if needed and then chunks/posts to MCP

Change 3041244 on 2016/07/07 by Ben.Marsh

	Core: Change PlatformIncludes.h to include all the individual PlatformMemory.h, PlatformTime.h, etc... headers rather than including separate per-platform headers which include them all. Makes it much easier to optimize header file usage, and eliminates redundant typedefs in the individual Platform*.h files. Also fixes some headers that previously didn't compile.

Change 3042518 on 2016/07/08 by Matthew.Griffin

	Added content modifiers to those notified about Sample failures
	Throw exception if RocketPromoteBuild tries to promote all samples
	Throw exceptions for missing parameters in BuildLauncherSample command, corrected EngineDir parameter name.

Change 3042545 on 2016/07/08 by Ben.Marsh

	Core: Push/Pop defines for MAX_uint8, MAX_uint16, MAX_uint32, MAX_int32 around Windows.h includes, so we don't need to be careful about the order in which we include NumericLimits.h.

Change 3042546 on 2016/07/08 by Ben.Marsh

	Core: Put standard CRT includes into their own header, so we can include it without taking all of PlatformIncludes.h (and make any platform-specific additions as needed)

Change 3042548 on 2016/07/08 by Ben.Marsh

	Core: Include PlatformCompilerSetup headers from Platform.h, as well as all the defaults for non-platform overriden defines. Allows including Platform.h to get all the basic types, defines and compile environment set up without having to include a large number of system headers or unnecessary functionality.

Change 3044424 on 2016/07/11 by Ben.Marsh

	Merge fixes for QFE installer (CL 3044412) from 4.11 branch.

Change 3044584 on 2016/07/11 by Ben.Marsh

	Core: Move FMath::FormatIntToHumanReadable() to UnrealMath.cpp, since it's a very large/expensive function to try to inline (and introduce a FString dependency for)

Change 3044603 on 2016/07/11 by Matthew.Griffin

	Added PS4 and XboxOne to installed build as options that will always be disabled by default
	Standardised some of the agent names
	Removed logging from the Installed Build nodes as it takes a huge amount of time to write out the list for little reward

Change 3044608 on 2016/07/11 by Ben.Marsh

	Core: Split out definition of SIMD VectorRegister class into its own header, so it's not forcibly included with UnrealMathUtility.

Change 3044638 on 2016/07/11 by Matthew.Griffin

	Added internal build jobs for all games with compile, cook and package nodes.
	Added Documentation, Localization and NonUnity steps.

Change 3045959 on 2016/07/12 by Matthew.Griffin

	Removed Aggregates from Installed Build script as they weren't used/necessary.

Change 3045961 on 2016/07/12 by Matthew.Griffin

	Fixed various issues with Full Build
	Switch to build non-client/server configurations for some games
	Included PS4 and Xbox game targets in our internal monolithics aggregate
	Added Requirements for steps that need UHT, SCW etc.
	Added list of Packaged Game Nodes that we can build up as they're defined
	Added targets that were previously in the Internal Tools nodes
	Changed APIDocTool to build Release as that's what the solution uses and made use of the path created for it
	Removed -clean from the NonUnity targets as that doesn't actually build anything
	Changed mail notifications so that individual nodes are used for content modifiers, not every preceeding node too

Change 3047068 on 2016/07/12 by Ben.Marsh

	BuildGraph: Reduce the amount of log output when compiling a C# project; use /verbosity:minimal and /nolog, as Visual Studio does.

Change 3047298 on 2016/07/12 by Ben.Marsh

	EC: Add a workspace setting specifying that it should be synced incrementally.

Change 3047626 on 2016/07/13 by Matthew.Griffin

	Added PackageToNetwork property, which will default to false, which determines whether to put staged builds on the P: drive or within the LocalBuilds folder of the root dir
	Also changed WorldExplorers to use P:/Builds/Friday instead of WEX, as no one is now clearing up the WEX folder regularly

Change 3047762 on 2016/07/13 by Matthew.Griffin

	Added -nodebuginfo to all compile tasks with -precompile to reduce the size of libs produced
	Added plugin intermediates to list of files excluded from installed build

[CL 3047809 by Ben Marsh in Main branch]
2016-07-13 09:16:28 -04:00

702 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnrealBuildTool;
using BuildGraph;
using System.Reflection;
using System.Collections;
using System.IO;
namespace AutomationTool
{
/// <summary>
/// Tool to execute build processes for UE4 projects, which can be run locally or in parallel across a build farm (assuming synchronization and resource allocation implemented by a separate system).
///
/// Build graphs are declared using an XML script using syntax similar to MSBuild, ANT or NAnt, and consist of the following components:
///
/// - Tasks: Building blocks which can be executed as part of the build process. Many predefined tasks are provided ('Cook', 'Compile', 'Copy', 'Stage', 'Log', 'PakFile', etc...), and additional tasks may be
/// added be declaring classes derived from AutomationTool.CustomTask in other UAT modules.
/// - Nodes: A named sequence of tasks which are executed in order to produce outputs. Nodes may have dependencies on other nodes for their outputs before they can be executed. Declared with the 'Node' element.
/// - Agents: A machine which can execute a sequence of nodes, if running as part of a build system. Has no effect when building locally. Declared with the 'Agent' element.
/// - Triggers: Container for agents which should only be executed when explicitly triggered (using the -Trigger=... or -SkipTriggers command line argument). Declared with the 'Trigger' element.
/// - Notifiers: Specifies email recipients for failures in one or more nodes, whether they should receive notifications on warnings, and so on.
///
/// Properties can be passed in to a script on the command line, or set procedurally with the &ltProperty Name="Foo" Value="Bar"/&gt; syntax. Properties referenced with the $(Property Name) notation are valid within
/// all strings, and will be expanded as macros when the script is read. If a property name is not set explicitly, it defaults to the contents of an environment variable with the same name.
/// Local properties, which only affect the scope of the containing XML element (node, agent, etc...) are declared with the &lt;Local Name="Foo" Value="Bar"/&gt; element, and will override a similarly named global
/// property for the local property's scope.
///
/// Any elements can be conditionally defined via the "If" attribute, which follows a syntax similar to MSBuild. Literals in conditions may be quoted with single (') or double (") quotes, or an unquoted sequence of
/// letters, digits and underscore characters. All literals are considered identical regardless of how they are declared, and are considered case-insensitive for comparisons (so true equals 'True', equals "TRUE").
/// Available operators are "==", "!=", "And", "Or", "!", "(...)", "Exists(...)" and "HasTrailingSlash(...)". A full grammar is written up in Condition.cs.
///
/// File manipulation is done using wildcards and tags. Any attribute that accepts a list of files may consist of: a Perforce-style wildcard (matching any number of "...", "*" and "?" patterns in any location), a
/// full path name, or a reference to a tagged collection of files, denoted by prefixing with a '#' character. Files may be added to a tag set using the &lt;Tag&gt; Task, which also allows performing set union/difference
/// style operations. Each node can declare multiple outputs in the form of a list of named tags, which other nodes can then depend on.
///
/// Build graphs may be executed in parallel as part build system. To do so, the initial graph configuration is generated by running with the -Export=... argument (producing a JSON file listing the nodes
/// and dependencies to execute). Each participating agent should be synced to the same changelist, and UAT should be re-run with the appropriate -Node=... argument. Outputs from different nodes are transferred between
/// agents via shared storage, typically a network share, the path to which can be specified on the command line using the -SharedStorageDir=... argument. Note that the allocation of machines, and coordination between
/// them, is assumed to be managed by an external system based on the contents of the script generated by -Export=....
///
/// A schema for the known set of tasks can be generated by running UAT with the -Schema=... option. Generating a schema and referencing it from a BuildGraph script allows Visual Studio to validate and auto-complete
/// elements as you type.
/// </summary>
[Help("Tool for creating extensible build processes in UE4 which can be run locally or in parallel across a build farm.")]
[Help("Script=<FileName>", "Path to the script describing the graph")]
[Help("Target=<Name>", "Name of the node or output tag to be built")]
[Help("Schema=<FileName>", "Generate a schema describing valid script documents, including all the known tasks")]
[Help("Set:<Property>=<Value>", "Sets a named property to the given value")]
[Help("Clean", "Cleans all cached state of completed build nodes before running")]
[Help("CleanNode=<Name>[+<Name>...]", "Cleans just the given nodes before running")]
[Help("ListOnly", "Shows the contents of the preprocessed graph, but does not execute it")]
[Help("ShowDeps", "Show node dependencies in the graph output")]
[Help("ShowNotifications", "Show notifications that will be sent for each node in the output")]
[Help("Trigger=<Name>[+<Name>...]", "Activates the given triggers, including all the nodes behind them in the graph")]
[Help("SkipTriggers", "Activate all triggers")]
[Help("TicketSignature=<Name>", "Specifies the signature identifying the current job, to be written to tickets for nodes that require them. Tickets are ignored if this parameter is not specified.")]
[Help("SkipTargetsWithoutTickets", "Excludes targets which we can't acquire tickets for, rather than failing")]
[Help("Preprocess=<FileName>", "Writes the preprocessed graph to the given file")]
[Help("Export=<FileName>", "Exports a JSON file containing the preprocessed build graph, for use as part of a build system")]
[Help("PublicTasksOnly", "Only include built-in tasks in the schema, excluding any other UAT modules")]
[Help("SharedStorageDir=<DirName>", "Sets the directory to use to transfer build products between agents in a build farm")]
[Help("SingleNode=<Name>", "Run only the given node. Intended for use on a build system after running with -Export.")]
[Help("WriteToSharedStorage", "Allow writing to shared storage. If not set, but -SharedStorageDir is specified, build products will read but not written")]
public class BuildGraph : BuildCommand
{
/// <summary>
/// Main entry point for the BuildGraph command
/// </summary>
public override ExitCode Execute()
{
// Parse the command line parameters
string ScriptFileName = ParseParamValue("Script", null);
if(ScriptFileName == null)
{
LogError("Missing -Script= parameter for BuildGraph");
return ExitCode.Error_Unknown;
}
string TargetNames = ParseParamValue("Target", null);
if(TargetNames == null)
{
LogError("Missing -Target= parameter for BuildGraph");
return ExitCode.Error_Unknown;
}
string SchemaFileName = ParseParamValue("Schema", null);
string ExportFileName = ParseParamValue("Export", null);
string PreprocessedFileName = ParseParamValue("Preprocess", null);
string SharedStorageDir = ParseParamValue("SharedStorageDir", null);
string SingleNodeName = ParseParamValue("SingleNode", null);
string[] TriggerNames = ParseParamValue("Trigger", "").Split(new char[]{ '+', ';' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
bool bSkipTriggers = ParseParam("SkipTriggers");
string TicketSignature = ParseParamValue("TicketSignature", null);
bool bSkipTargetsWithoutTickets = ParseParam("SkipTargetsWithoutTickets");
bool bClearHistory = ParseParam("Clean") || ParseParam("ClearHistory");
bool bListOnly = ParseParam("ListOnly");
bool bWriteToSharedStorage = ParseParam("WriteToSharedStorage") || CommandUtils.IsBuildMachine;
bool bPublicTasksOnly = ParseParam("PublicTasksOnly");
string ReportName = ParseParamValue("ReportName", null);
GraphPrintOptions PrintOptions = 0;
if(ParseParam("ShowDeps"))
{
PrintOptions |= GraphPrintOptions.ShowDependencies;
}
if(ParseParam("ShowNotifications"))
{
PrintOptions |= GraphPrintOptions.ShowNotifications;
}
// Parse any specific nodes to clean
List<string> CleanNodes = new List<string>();
foreach(string NodeList in ParseParamValues("CleanNode"))
{
foreach(string NodeName in NodeList.Split('+', ';'))
{
CleanNodes.Add(NodeName);
}
}
// Read any environment variables
Dictionary<string, string> DefaultProperties = new Dictionary<string,string>(StringComparer.InvariantCultureIgnoreCase);
foreach(DictionaryEntry Entry in Environment.GetEnvironmentVariables())
{
DefaultProperties[Entry.Key.ToString()] = Entry.Value.ToString();
}
// Set up the standard properties which build scripts might need
DefaultProperties["Branch"] = P4Enabled ? P4Env.BuildRootP4 : "Unknown";
DefaultProperties["EscapedBranch"] = P4Enabled ? P4Env.BuildRootEscaped : "Unknown";
DefaultProperties["Change"] = P4Enabled ? P4Env.Changelist.ToString() : "0";
DefaultProperties["RootDir"] = CommandUtils.RootDirectory.FullName;
DefaultProperties["IsBuildMachine"] = IsBuildMachine ? "true" : "false";
DefaultProperties["HostPlatform"] = HostPlatform.Current.HostEditorPlatform.ToString();
// Attempt to read existing Build Version information
BuildVersion Version;
if (BuildVersion.TryRead(FileReference.Combine(CommandUtils.RootDirectory, "Engine", "Build", "Build.version").FullName, out Version))
{
DefaultProperties["EngineMajorVersion"] = Version.MajorVersion.ToString();
DefaultProperties["EngineMinorVersion"] = Version.MinorVersion.ToString();
DefaultProperties["EnginePatchVersion"] = Version.PatchVersion.ToString();
}
// Add any additional custom parameters from the command line (of the form -Set:X=Y)
foreach (string Param in Params)
{
const string Prefix = "set:";
if(Param.StartsWith(Prefix, StringComparison.InvariantCultureIgnoreCase))
{
int EqualsIdx = Param.IndexOf('=');
if(EqualsIdx >= 0)
{
DefaultProperties[Param.Substring(Prefix.Length, EqualsIdx - Prefix.Length)] = Param.Substring(EqualsIdx + 1);
}
else
{
LogWarning("Missing value for '{0}'", Param.Substring(Prefix.Length));
}
}
}
// Find all the tasks from the loaded assemblies
Dictionary<string, ScriptTask> NameToTask = new Dictionary<string,ScriptTask>();
if(!FindAvailableTasks(NameToTask, bPublicTasksOnly))
{
return ExitCode.Error_Unknown;
}
// Create a schema for the given tasks
ScriptSchema Schema = new ScriptSchema(NameToTask);
if(SchemaFileName != null)
{
Schema.Export(new FileReference(SchemaFileName));
}
// Read the script from disk
Graph Graph;
if(!ScriptReader.TryRead(new FileReference(ScriptFileName), DefaultProperties, Schema, out Graph))
{
return ExitCode.Error_Unknown;
}
// Create the temp storage handler
DirectoryReference RootDir = new DirectoryReference(CommandUtils.CmdEnv.LocalRoot);
TempStorage Storage = new TempStorage(RootDir, DirectoryReference.Combine(RootDir, "Engine", "Saved", "BuildGraph"), (SharedStorageDir == null)? null : new DirectoryReference(SharedStorageDir), bWriteToSharedStorage);
if(bClearHistory)
{
Storage.CleanLocal();
}
foreach(string CleanNode in CleanNodes)
{
Storage.CleanLocalNode(CleanNode);
}
// Convert the supplied target references into nodes
HashSet<Node> TargetNodes = new HashSet<Node>();
foreach(string TargetName in TargetNames.Split(new char[]{ '+', ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()))
{
Node[] Nodes;
if(!Graph.TryResolveReference(TargetName, out Nodes))
{
LogError("Target '{0}' is not in graph", TargetName);
return ExitCode.Error_Unknown;
}
TargetNodes.UnionWith(Nodes);
}
// Try to acquire tickets for all the target nodes we want to build
if(TicketSignature != null)
{
// Find all the lock files
HashSet<FileReference> RequiredTickets = new HashSet<FileReference>(TargetNodes.SelectMany(x => x.RequiredTickets));
// Try to create all the lock files
List<FileReference> CreatedTickets = new List<FileReference>();
if(!bListOnly)
{
CreatedTickets.AddRange(RequiredTickets.Where(x => WriteTicket(x, TicketSignature)));
}
// Find all the tickets that we don't have
Dictionary<FileReference, string> MissingTickets = new Dictionary<FileReference, string>();
foreach(FileReference RequiredTicket in RequiredTickets)
{
string CurrentOwner = ReadTicket(RequiredTicket);
if(CurrentOwner != null && CurrentOwner != TicketSignature)
{
MissingTickets.Add(RequiredTicket, CurrentOwner);
}
}
// If we want to skip all the nodes with missing locks, adjust the target nodes to account for it
if(MissingTickets.Count > 0)
{
if(bSkipTargetsWithoutTickets)
{
foreach(KeyValuePair<FileReference, string> Pair in MissingTickets)
{
List<Node> SkipNodes = TargetNodes.Where(x => x.RequiredTickets.Contains(Pair.Key)).ToList();
Log("Skipping {0} due to previous build: {1}", String.Join(", ", SkipNodes), Pair.Value);
TargetNodes.ExceptWith(SkipNodes);
}
}
else
{
foreach(KeyValuePair<FileReference, string> Pair in MissingTickets)
{
List<Node> SkipNodes = TargetNodes.Where(x => x.RequiredTickets.Contains(Pair.Key)).ToList();
LogError("Cannot run {0} due to previous build: {1}", String.Join(", ", SkipNodes), Pair.Value);
}
foreach(FileReference CreatedTicket in CreatedTickets)
{
CreatedTicket.Delete();
}
return ExitCode.Error_Unknown;
}
}
}
// Cull the graph to include only those nodes
Graph.Select(TargetNodes);
// If a report for the whole build was requested, insert it into the graph
if (ReportName != null)
{
Report NewReport = new Report(ReportName);
NewReport.Nodes.UnionWith(Graph.Agents.SelectMany(x => x.Nodes));
Graph.NameToReport.Add(ReportName, NewReport);
}
// Write out the preprocessed script
if (PreprocessedFileName != null)
{
Graph.Write(new FileReference(PreprocessedFileName), (SchemaFileName != null)? new FileReference(SchemaFileName) : null);
}
// Find the triggers which are explicitly activated, and all of its upstream triggers.
HashSet<ManualTrigger> Triggers = new HashSet<ManualTrigger>();
foreach(string TriggerName in TriggerNames)
{
ManualTrigger Trigger;
if(!Graph.NameToTrigger.TryGetValue(TriggerName, out Trigger))
{
LogError("Couldn't find trigger '{0}'", TriggerName);
return ExitCode.Error_Unknown;
}
while(Trigger != null)
{
Triggers.Add(Trigger);
Trigger = Trigger.Parent;
}
}
if(bSkipTriggers)
{
Triggers.UnionWith(Graph.NameToTrigger.Values);
}
// If we're just building a single node, find it
Node SingleNode = null;
if(SingleNodeName != null && !Graph.NameToNode.TryGetValue(SingleNodeName, out SingleNode))
{
LogError("Node '{0}' is not in the trimmed graph", SingleNodeName);
return ExitCode.Error_Unknown;
}
// Print out all the diagnostic messages which still apply, unless we're running a step as part of a build system.
if(SingleNode == null)
{
IEnumerable<GraphDiagnostic> Diagnostics = Graph.Diagnostics.Where(x => x.EnclosingTrigger == null || Triggers.Contains(x.EnclosingTrigger));
foreach(GraphDiagnostic Diagnostic in Diagnostics)
{
if(Diagnostic.EventType == LogEventType.Warning)
{
CommandUtils.LogWarning(Diagnostic.Message);
}
else
{
CommandUtils.LogError(Diagnostic.Message);
}
}
if(Diagnostics.Any(x => x.EventType == LogEventType.Error))
{
return ExitCode.Error_Unknown;
}
}
// Execute the command
if(bListOnly)
{
HashSet<Node> CompletedNodes = FindCompletedNodes(Graph, Storage);
Graph.Print(CompletedNodes, PrintOptions);
}
else if(ExportFileName != null)
{
HashSet<Node> CompletedNodes = FindCompletedNodes(Graph, Storage);
Graph.Print(CompletedNodes, PrintOptions);
Graph.Export(new FileReference(ExportFileName), Triggers, CompletedNodes);
}
else if(SingleNode != null)
{
if(!BuildNode(new JobContext(this), Graph, SingleNode, Storage, bWithBanner: true))
{
return ExitCode.Error_Unknown;
}
}
else
{
if(!BuildAllNodes(new JobContext(this), Graph, Storage))
{
return ExitCode.Error_Unknown;
}
}
return ExitCode.Success;
}
/// <summary>
/// Find all the tasks which are available from the loaded assemblies
/// </summary>
/// <param name="TaskNameToReflectionInfo">Mapping from task name to information about how to serialize it</param>
/// <param name="bPublicTasksOnly">Whether to include just public tasks, or all the tasks in any loaded assemblies</param>
static bool FindAvailableTasks(Dictionary<string, ScriptTask> NameToTask, bool bPublicTasksOnly)
{
Assembly[] LoadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
if(bPublicTasksOnly)
{
LoadedAssemblies = LoadedAssemblies.Where(x => IsPublicAssembly(new FileReference(x.Location))).ToArray();
}
foreach (Assembly LoadedAssembly in LoadedAssemblies)
{
Type[] Types = LoadedAssembly.GetTypes();
foreach(Type Type in Types)
{
foreach(TaskElementAttribute ElementAttribute in Type.GetCustomAttributes<TaskElementAttribute>())
{
if(!Type.IsSubclassOf(typeof(CustomTask)))
{
CommandUtils.LogError("Class '{0}' has TaskElementAttribute, but is not derived from 'Task'", Type.Name);
return false;
}
if(NameToTask.ContainsKey(ElementAttribute.Name))
{
CommandUtils.LogError("Found multiple handlers for task elements called '{0}'", ElementAttribute.Name);
return false;
}
NameToTask.Add(ElementAttribute.Name, new ScriptTask(ElementAttribute.Name, Type, ElementAttribute.ParametersType));
}
}
}
return true;
}
/// <summary>
/// Reads the contents of the given ticket
/// </summary>
/// <returns>Contents of the ticket, or null if it does not exist</returns>
public string ReadTicket(FileReference Location)
{
return Location.Exists()? File.ReadAllText(Location.FullName) : null;
}
/// <summary>
/// Attempts to write an owner to a ticket file transactionally
/// </summary>
/// <returns>True if the lock was acquired, false otherwise</returns>
public bool WriteTicket(FileReference Location, string Signature)
{
// Check it doesn't already exist
if(Location.Exists())
{
return false;
}
// Make sure the directory exists
Location.Directory.CreateDirectory();
// Create a temp file containing the owner name
string TempFileName;
for(int Idx = 0;;Idx++)
{
TempFileName = String.Format("{0}.{1}.tmp", Location.FullName, Idx);
try
{
byte[] Bytes = Encoding.UTF8.GetBytes(Signature);
using (FileStream Stream = File.Open(TempFileName, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
Stream.Write(Bytes, 0, Bytes.Length);
}
break;
}
catch(IOException)
{
if(!File.Exists(TempFileName))
{
throw;
}
}
}
// Try to move the temporary file into place.
try
{
File.Move(TempFileName, Location.FullName);
return true;
}
catch
{
if(!File.Exists(TempFileName))
{
throw;
}
return false;
}
}
/// <summary>
/// Checks whether the given assembly is a publically distributed engine assembly.
/// </summary>
/// <param name="File">Assembly location</param>
/// <returns>True if the assembly is distributed publically</returns>
static bool IsPublicAssembly(FileReference File)
{
DirectoryReference EngineDirectory = UnrealBuildTool.UnrealBuildTool.EngineDirectory;
if(File.IsUnderDirectory(EngineDirectory))
{
string[] PathFragments = File.MakeRelativeTo(EngineDirectory).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
if(PathFragments.All(x => !x.Equals("NotForLicensees", StringComparison.InvariantCultureIgnoreCase) && !x.Equals("NoRedist", StringComparison.InvariantCultureIgnoreCase)))
{
return true;
}
}
return false;
}
/// <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;
}
/// <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>
/// <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>
/// <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}", 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>
/// Legacy command name for compatibility.
/// </summary>
public class Build : BuildGraph
{
}
}