You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3059740 on 2016/07/21 by Ben.Marsh Format license violations on separate lines rather than as a single multi-line string, so they can be parsed out correctly by the EC log parser. Change 3063115 on 2016/07/25 by Ben.Marsh Add missing projects to Mono AutomationTool solution. Change 3067125 on 2016/07/27 by Ben.Marsh Clean up and document the engine versioning system. * Version.h has a summary of all the different versioning concepts in UE4, as well as an explanation of what all the macros do. * VersionLocked.h has been removed. ENGINE_NET_VERSION and ENGINE_REPLAY_VERSION can be overriden directly in Version.h if necessary. * Added explicit macros for ENGINE_CURRENT_CL_VERSION And ENGINE_COMPATIBLE_CL_VERSION. The MODULE_API_VERSION macro is now defined to be the same as ENGINE_COMPATIBLE_CL_VERSION. * Build/Build.version now includes the compatible CL version, so it can be determined without having to parse header files inside MacToolChain.cs. * UpdateLocalVersion UAT command now exposes a -CompatibleCL=... argument to update ENGINE_COMPATIBLE_CL_VERSION and the CompatibleChange property in Build.version. * SetVersion BuildGraph task now has a CompatibleChange="" attribute to update ENGINE_COMPATIBLE_CL_VERSION and the CompatibleChange property in Build.version. * VersionFileUpdater now makes an effort to parse tokens matching the macros it's going to overwrite, rather than requiring exact matches in whitespace in Version.h. * Running UpdateLocalVersion now syncs files to revision 0 before writing if P4 is enabled, similarly to UGS, or fails if they are not writable. Change 3067242 on 2016/07/27 by Ben.Marsh Fix UGS showing a build failure/warning dialog immediately if you submit while the build is yellow/red. Now ignores builds before your last submitted CL. Change 3068116 on 2016/07/28 by Ben.Marsh Add a small Windows utility to terminate all running processes under a given root directory, and run it before and after every sync. Mean to fix instances where zombie editor processes do not terminate correctly and cause subsequent syncs to fail. Change 3068246 on 2016/07/28 by Ben.Marsh UGS: Improvements to 'Clean Workspace' dialog - empty folders are now set to be deleted by default, and added a context menu to select all/none/empty/default for a subtree, as well as to open Windows explorer at that location. Change 3068573 on 2016/07/28 by Ben.Marsh Attempt to fix AppDomain unload errors; suspect caused by delay to terminate child processes when managed exception is thrown on child threads. Now waits for the currently running threads to finish executing before quitting. Change 3068575 on 2016/07/28 by Ben.Marsh Respect the -noxge command-line argument when compiling UHT. Change 3068665 on 2016/07/28 by Ben.Marsh Delete any DLLs in output folders which share names with build products. The Windows loader reads DLLs from the first location it finds a file with a matching name, so we need to ensure that it doesn't load stale DLLs when output directories are changed (moving a module into a plugin, for example). Change 3073316 on 2016/08/02 by Ben.Marsh CoreUObject: Replace header guards with #pragma once directives. Change 3073325 on 2016/08/02 by Ben.Marsh CoreUObject: Manually untangle a few circular includes around ObjectBase.h and Class.h to allow making them into IWYU-style headers. Change 3074391 on 2016/08/02 by Ben.Marsh GitHub #2646: Always allow programs to build as part of non-game solution configurations. Change 3075123 on 2016/08/03 by Richard.Fawcett Ensure that zip files created outside of Ionic Zip can be unzipped with CommandUtils.UnzipFiles Zip files created with 7-zip, WinRAR, and Windows Explorer all contain entities for directories, not just the files in them. Code in CommandUtils.UnzipFiles made the assumption that all entities in the zip file were files, and an exception was being thrown on encountering a directory. Change 3075848 on 2016/08/03 by Ben.Marsh UBT: Fix a few cases where it's possible to construct a non-canonical DirectoryReference - root directories on Windows must always have a terminating slash, but any other directories must note. DirectoryInfo and Path.GetFullPath() do not enforce this requirement. Change 3075850 on 2016/08/03 by Ben.Marsh UBT: Fix UHT failures when running with -nopch. Change 3077058 by Ben.Marsh UHT: Remove the auto-generated ObjectBase.h include from generated header files. Was originally added to allow doxygen to parse headers more easily, but was switched to parsing the file only as part of parent files instead. [CL 3077308 by Ben Marsh in Main branch]
409 lines
13 KiB
C#
409 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
|
|
namespace AutomationTool
|
|
{
|
|
static class ParallelExecutor
|
|
{
|
|
[DebuggerDisplay("{Caption}")]
|
|
class BuildAction
|
|
{
|
|
public int SortIndex = -1;
|
|
|
|
public string Caption;
|
|
public string GroupPrefix;
|
|
public string OutputPrefix;
|
|
public string ToolPath;
|
|
public string ToolArguments;
|
|
public string WorkingDirectory;
|
|
public bool bSkipIfProjectFailed;
|
|
|
|
public List<BuildAction> Dependants = new List<BuildAction>();
|
|
public List<BuildAction> Dependencies = new List<BuildAction>();
|
|
|
|
public int TotalDependants;
|
|
public int MissingDependencyCount;
|
|
|
|
public IReadOnlyDictionary<string, string> Environment;
|
|
}
|
|
|
|
class BuildActionExecutor
|
|
{
|
|
public BuildAction Action;
|
|
public List<string> LogLines = new List<string>();
|
|
public int ExitCode = -1;
|
|
|
|
private ManualResetEvent CompletedEvent;
|
|
private List<BuildActionExecutor> CompletedActions;
|
|
|
|
public BuildActionExecutor(BuildAction InAction, ManualResetEvent InCompletedEvent, List<BuildActionExecutor> InCompletedActions)
|
|
{
|
|
Action = InAction;
|
|
CompletedEvent = InCompletedEvent;
|
|
CompletedActions = InCompletedActions;
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
if(!String.IsNullOrEmpty(Action.OutputPrefix))
|
|
{
|
|
LogLines.Add(Action.OutputPrefix);
|
|
}
|
|
|
|
using(ManagedProcess Process = new ManagedProcess(Action.ToolPath, Action.ToolArguments, Action.WorkingDirectory, Action.Environment, null, ManagedProcessPriority.BelowNormal))
|
|
{
|
|
LogLines.AddRange(Process.ReadAllLines());
|
|
ExitCode = Process.ExitCode;
|
|
}
|
|
|
|
lock(CompletedActions)
|
|
{
|
|
CompletedActions.Add(this);
|
|
}
|
|
|
|
CompletedEvent.Set();
|
|
}
|
|
}
|
|
|
|
public static int Execute(string ActionsFileName)
|
|
{
|
|
return Execute(ActionsFileName, Environment.ProcessorCount);
|
|
}
|
|
|
|
public static int Execute(string ActionsFileName, int MaxProcesses)
|
|
{
|
|
List<BuildAction> Actions = ReadActions(ActionsFileName);
|
|
|
|
CommandUtils.Log("Building with {0} {1}...", MaxProcesses, (MaxProcesses == 1)? "process" : "processes");
|
|
|
|
// Create the list of things to process
|
|
List<BuildAction> QueuedActions = new List<BuildAction>();
|
|
foreach(BuildAction Action in Actions)
|
|
{
|
|
if(Action.MissingDependencyCount == 0)
|
|
{
|
|
QueuedActions.Add(Action);
|
|
}
|
|
}
|
|
|
|
// Create a job object for all the child processes
|
|
int ExitCode = 0;
|
|
string CurrentPrefix = "";
|
|
Dictionary<BuildActionExecutor, Thread> ExecutingActions = new Dictionary<BuildActionExecutor,Thread>();
|
|
List<BuildActionExecutor> CompletedActions = new List<BuildActionExecutor>();
|
|
|
|
using(ManualResetEvent CompletedEvent = new ManualResetEvent(false))
|
|
{
|
|
while(QueuedActions.Count > 0 || ExecutingActions.Count > 0)
|
|
{
|
|
// Sort the actions by the number of things dependent on them
|
|
QueuedActions.Sort((A, B) => (A.TotalDependants == B.TotalDependants)? (B.SortIndex - A.SortIndex) : (B.TotalDependants - A.TotalDependants));
|
|
|
|
// Create threads up to the maximum number of actions
|
|
while(ExecutingActions.Count < MaxProcesses && QueuedActions.Count > 0)
|
|
{
|
|
BuildAction Action = QueuedActions[QueuedActions.Count - 1];
|
|
QueuedActions.RemoveAt(QueuedActions.Count - 1);
|
|
|
|
BuildActionExecutor ExecutingAction = new BuildActionExecutor(Action, CompletedEvent, CompletedActions);
|
|
|
|
Thread ExecutingThread = new Thread(() => { ExecutingAction.Run(); });
|
|
ExecutingThread.Name = String.Format("Build:{0}", Action.Caption);
|
|
ExecutingThread.Start();
|
|
|
|
ExecutingActions.Add(ExecutingAction, ExecutingThread);
|
|
}
|
|
|
|
// Wait for something to finish
|
|
CompletedEvent.WaitOne();
|
|
|
|
// Wait for something to finish and flush it to the log
|
|
lock(CompletedActions)
|
|
{
|
|
foreach(BuildActionExecutor CompletedAction in CompletedActions)
|
|
{
|
|
// Join the thread
|
|
Thread CompletedThread = ExecutingActions[CompletedAction];
|
|
CompletedThread.Join();
|
|
ExecutingActions.Remove(CompletedAction);
|
|
|
|
// Write it to the log
|
|
if(CompletedAction.LogLines.Count > 0)
|
|
{
|
|
if(CurrentPrefix != CompletedAction.Action.GroupPrefix)
|
|
{
|
|
CurrentPrefix = CompletedAction.Action.GroupPrefix;
|
|
CommandUtils.Log(CurrentPrefix);
|
|
}
|
|
foreach(string LogLine in CompletedAction.LogLines)
|
|
{
|
|
CommandUtils.Log(LogLine);
|
|
}
|
|
}
|
|
|
|
// Check the exit code
|
|
if(CompletedAction.ExitCode == 0)
|
|
{
|
|
// Mark all the dependents as done
|
|
foreach(BuildAction DependantAction in CompletedAction.Action.Dependants)
|
|
{
|
|
if(--DependantAction.MissingDependencyCount == 0)
|
|
{
|
|
QueuedActions.Add(DependantAction);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Update the exit code if it's not already set
|
|
if(ExitCode == 0)
|
|
{
|
|
ExitCode = CompletedAction.ExitCode;
|
|
}
|
|
}
|
|
}
|
|
CompletedActions.Clear();
|
|
}
|
|
|
|
// If we've already got a non-zero exit code, clear out the list of queued actions so nothing else will run
|
|
if(ExitCode != 0)
|
|
{
|
|
QueuedActions.Clear();
|
|
}
|
|
}
|
|
}
|
|
return ExitCode;
|
|
}
|
|
|
|
private static List<BuildAction> ReadActions(string InputPath)
|
|
{
|
|
XmlDocument File = new XmlDocument();
|
|
File.Load(InputPath);
|
|
|
|
XmlNode RootNode = File.FirstChild;
|
|
if(RootNode == null || RootNode.Name != "BuildSet")
|
|
{
|
|
throw new Exception("Incorrect node at root of graph; expected 'BuildSet'");
|
|
}
|
|
|
|
XmlNode EnvironmentsNode = RootNode.SelectSingleNode("Environments");
|
|
if(EnvironmentsNode == null)
|
|
{
|
|
throw new Exception("Missing Environments node under root in XML document");
|
|
}
|
|
|
|
// Get the current environment variables
|
|
Dictionary<string, string> CurrentEnvironment = new Dictionary<string,string>(StringComparer.InvariantCultureIgnoreCase);
|
|
foreach(System.Collections.DictionaryEntry Entry in Environment.GetEnvironmentVariables())
|
|
{
|
|
CurrentEnvironment[Entry.Key.ToString()] = Entry.Value.ToString();
|
|
}
|
|
|
|
// Read the tool environment
|
|
Dictionary<string, XmlNode> NameToTool = new Dictionary<string,XmlNode>();
|
|
Dictionary<string, IReadOnlyDictionary<string, string>> NameToEnvironment = new Dictionary<string,IReadOnlyDictionary<string, string>>();
|
|
for(XmlNode EnvironmentNode = EnvironmentsNode.FirstChild; EnvironmentNode != null; EnvironmentNode = EnvironmentNode.NextSibling)
|
|
{
|
|
if(EnvironmentNode.Name == "Environment")
|
|
{
|
|
// Read the tools nodes
|
|
XmlNode ToolsNode = EnvironmentNode.SelectSingleNode("Tools");
|
|
if(ToolsNode != null)
|
|
{
|
|
for(XmlNode ToolNode = ToolsNode.FirstChild; ToolNode != null; ToolNode = ToolNode.NextSibling)
|
|
{
|
|
if(ToolNode.Name == "Tool")
|
|
{
|
|
XmlNode Name = ToolNode.Attributes.GetNamedItem("Name");
|
|
if(Name != null)
|
|
{
|
|
NameToTool.Add(Name.Value, ToolNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read the environment variables for this environment. Each environment has its own set of variables
|
|
XmlNode VariablesNode = EnvironmentNode.SelectSingleNode("Variables");
|
|
if(VariablesNode != null)
|
|
{
|
|
XmlNode Name = EnvironmentNode.Attributes.GetNamedItem("Name");
|
|
if(Name != null)
|
|
{
|
|
Dictionary<string, string> NamedEnvironment = new Dictionary<string,string>(CurrentEnvironment, StringComparer.InvariantCultureIgnoreCase);
|
|
for (XmlNode VariableNode = VariablesNode.FirstChild; VariableNode != null; VariableNode = VariableNode.NextSibling)
|
|
{
|
|
if (VariableNode.Name == "Variable")
|
|
{
|
|
XmlNode VariableName = VariableNode.Attributes.GetNamedItem("Name");
|
|
if(VariableName != null)
|
|
{
|
|
NamedEnvironment[VariableName.Value] = VariableNode.Attributes.GetNamedItem("Value").Value;
|
|
}
|
|
}
|
|
}
|
|
NameToEnvironment[Name.Value] = NamedEnvironment;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read all the tasks for each project, and convert them into actions
|
|
List<BuildAction> Actions = new List<BuildAction>();
|
|
for(XmlNode ProjectNode = RootNode.FirstChild; ProjectNode != null; ProjectNode = ProjectNode.NextSibling)
|
|
{
|
|
if(ProjectNode.Name == "Project")
|
|
{
|
|
int SortIndex = 0;
|
|
Dictionary<string, BuildAction> NameToAction = new Dictionary<string,BuildAction>();
|
|
for(XmlNode TaskNode = ProjectNode.FirstChild; TaskNode != null; TaskNode = TaskNode.NextSibling)
|
|
{
|
|
if(TaskNode.Name == "Task")
|
|
{
|
|
XmlNode ToolNode;
|
|
if(NameToTool.TryGetValue(TaskNode.Attributes.GetNamedItem("Tool").Value, out ToolNode))
|
|
{
|
|
BuildAction Action = FindOrAddAction(NameToAction, TaskNode.Attributes.GetNamedItem("Name").Value, Actions);
|
|
Action.SortIndex = ++SortIndex;
|
|
TryGetAttribute(TaskNode, "Caption", out Action.Caption);
|
|
|
|
TryGetAttribute(ToolNode, "GroupPrefix", out Action.GroupPrefix);
|
|
TryGetAttribute(ToolNode, "OutputPrefix", out Action.OutputPrefix);
|
|
TryGetAttribute(ToolNode, "Path", out Action.ToolPath);
|
|
TryGetAttribute(ToolNode, "WorkingDir", out Action.WorkingDirectory);
|
|
|
|
string EnvironmentName;
|
|
if(!TryGetAttribute(ProjectNode, "Env", out EnvironmentName))
|
|
{
|
|
Action.Environment = CurrentEnvironment;
|
|
}
|
|
else if(!NameToEnvironment.TryGetValue(EnvironmentName, out Action.Environment))
|
|
{
|
|
throw new Exception(String.Format("Couldn't find environment '{0}'", EnvironmentName));
|
|
}
|
|
|
|
if(TryGetAttribute(ToolNode, "Params", out Action.ToolArguments))
|
|
{
|
|
Action.ToolArguments = ExpandEnvironmentVariables(Action.ToolArguments, Action.Environment);
|
|
}
|
|
|
|
string SkipIfProjectFailed;
|
|
if(TryGetAttribute(TaskNode, "SkipIfProjectFailed", out SkipIfProjectFailed) && SkipIfProjectFailed == "True")
|
|
{
|
|
Action.bSkipIfProjectFailed = true;
|
|
}
|
|
|
|
string DependsOnList;
|
|
if(TryGetAttribute(TaskNode, "DependsOn", out DependsOnList))
|
|
{
|
|
foreach (string DependsOn in DependsOnList.Split(';'))
|
|
{
|
|
BuildAction DependsOnAction = FindOrAddAction(NameToAction, DependsOn, Actions);
|
|
if(!Action.Dependencies.Contains(DependsOnAction))
|
|
{
|
|
Action.Dependencies.Add(DependsOnAction);
|
|
}
|
|
if(!DependsOnAction.Dependants.Contains(Action))
|
|
{
|
|
DependsOnAction.Dependants.Add(Action);
|
|
}
|
|
}
|
|
}
|
|
|
|
HashSet<BuildAction> VisitedActions = new HashSet<BuildAction>();
|
|
RecursiveIncDependents(Action, VisitedActions);
|
|
|
|
Action.MissingDependencyCount = Action.Dependencies.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure there have been no actions added which were referenced but never declared
|
|
foreach(KeyValuePair<string, BuildAction> Pair in NameToAction)
|
|
{
|
|
if(Pair.Value.SortIndex == -1)
|
|
{
|
|
throw new Exception(String.Format("Action {0} was referenced but never declared", Pair.Key));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Actions;
|
|
}
|
|
|
|
static bool TryGetAttribute(XmlNode Node, string Name, out string Value)
|
|
{
|
|
XmlNode Attribute = Node.Attributes.GetNamedItem(Name);
|
|
if(Attribute == null)
|
|
{
|
|
Value = null;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Value = Attribute.Value;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static string ExpandEnvironmentVariables(string Input, IReadOnlyDictionary<string, string> Environment)
|
|
{
|
|
string Output = Input;
|
|
for(int StartIndex = 0;;)
|
|
{
|
|
int Index = Output.IndexOf("$(", StartIndex);
|
|
if(Index == -1) break;
|
|
|
|
int EndIndex = Output.IndexOf(")", Index);
|
|
if(EndIndex == -1) break;
|
|
|
|
string VariableName = Output.Substring(Index + 2, EndIndex - Index - 2);
|
|
|
|
string VariableValue;
|
|
if(Environment.TryGetValue(VariableName, out VariableValue))
|
|
{
|
|
Output = Output.Substring(0, Index) + VariableValue + Output.Substring(EndIndex + 1);
|
|
StartIndex = Index + VariableValue.Length;
|
|
}
|
|
else
|
|
{
|
|
CommandUtils.LogWarning("Unknown environment variable '{0}' in script", VariableName);
|
|
StartIndex = EndIndex + 1;
|
|
}
|
|
}
|
|
return Output;
|
|
}
|
|
|
|
private static BuildAction FindOrAddAction(Dictionary<string, BuildAction> NameToAction, string Name, List<BuildAction> Actions)
|
|
{
|
|
BuildAction Action;
|
|
if(!NameToAction.TryGetValue(Name, out Action))
|
|
{
|
|
Action = new BuildAction();
|
|
Actions.Add(Action);
|
|
NameToAction.Add(Name, Action);
|
|
}
|
|
return Action;
|
|
}
|
|
|
|
private static void RecursiveIncDependents(BuildAction Action, HashSet<BuildAction> VisitedActions)
|
|
{
|
|
foreach(BuildAction Dependency in Action.Dependencies)
|
|
{
|
|
if(!VisitedActions.Contains(Action))
|
|
{
|
|
VisitedActions.Add(Action);
|
|
Dependency.TotalDependants++;
|
|
RecursiveIncDependents(Dependency, VisitedActions);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|