You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Skip flushing ProjectUtils temp folders on startup of a child process. Leave files intact that were generated by the parent process. This change fixes some issues where ProjectUtils is booted up in a parent AutomationTool, which causes some UATRules assemblies to be generated and loaded. Child processes try to clean up these files on startup, but they are locked by the parent process. Skip the cleanup in this case. #rnx #jira none #rb jonathan.adamczewski #preflight 614a12e1116f2a0001927afb #ROBOMERGE-AUTHOR: geoff.evans #ROBOMERGE-SOURCE: CL 17585916 in //UE5/Main/... #ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v871-17566257) [CL 17585949 by geoff evans in ue5-release-engine-test branch]
492 lines
14 KiB
C#
492 lines
14 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Diagnostics;
|
|
using UnrealBuildTool;
|
|
using EpicGames.Core;
|
|
using UnrealBuildBase;
|
|
|
|
namespace AutomationTool
|
|
{
|
|
|
|
public static class Automation
|
|
{
|
|
/// <summary>
|
|
/// Keep a persistent reference to the delegate for handling Ctrl-C events. Since it's passed to non-managed code, we have to prevent it from being garbage collected.
|
|
/// </summary>
|
|
static ProcessManager.CtrlHandlerDelegate CtrlHandlerDelegateInstance = CtrlHandler;
|
|
|
|
static bool CtrlHandler(CtrlTypes EventType)
|
|
{
|
|
Domain_ProcessExit(null, null);
|
|
if (EventType == CtrlTypes.CTRL_C_EVENT)
|
|
{
|
|
// Force exit
|
|
Environment.Exit(3);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void Domain_ProcessExit(object sender, EventArgs e)
|
|
{
|
|
// Kill all spawned processes (Console instead of Log because logging is closed at this time anyway)
|
|
if (ShouldKillProcesses && RuntimePlatform.IsWindows)
|
|
{
|
|
ProcessManager.KillAll();
|
|
}
|
|
Trace.Close();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main method.
|
|
/// </summary>
|
|
/// <param name="Arguments">Command line</param>
|
|
public static ExitCode Process(ParsedCommandLine AutomationToolCommandLine, StartupTraceListener StartupListener, HashSet<FileReference> ScriptModuleAssemblies)
|
|
{
|
|
GlobalCommandLine.Initialize(AutomationToolCommandLine);
|
|
|
|
// Hook up exit callbacks
|
|
AppDomain Domain = AppDomain.CurrentDomain;
|
|
Domain.ProcessExit += Domain_ProcessExit;
|
|
Domain.DomainUnload += Domain_ProcessExit;
|
|
HostPlatform.Current.SetConsoleCtrlHandler(CtrlHandlerDelegateInstance);
|
|
|
|
try
|
|
{
|
|
IsBuildMachine = GlobalCommandLine.BuildMachine;
|
|
if (!IsBuildMachine)
|
|
{
|
|
int Value;
|
|
if (int.TryParse(Environment.GetEnvironmentVariable("IsBuildMachine"), out Value) && Value != 0)
|
|
{
|
|
IsBuildMachine = true;
|
|
}
|
|
}
|
|
|
|
Log.TraceVerbose("IsBuildMachine={0}", IsBuildMachine);
|
|
Environment.SetEnvironmentVariable("IsBuildMachine", IsBuildMachine ? "1" : "0");
|
|
|
|
// Get the path to the telemetry file, if present
|
|
string TelemetryFile = GlobalCommandLine.TelemetryPath;
|
|
|
|
// should we kill processes on exit
|
|
ShouldKillProcesses = !GlobalCommandLine.NoKill;
|
|
Log.TraceVerbose("ShouldKillProcesses={0}", ShouldKillProcesses);
|
|
|
|
if (AutomationToolCommandLine.CommandsToExecute.Count == 0 && GlobalCommandLine.Help)
|
|
{
|
|
DisplayHelp(AutomationToolCommandLine.GlobalParameters);
|
|
return ExitCode.Success;
|
|
}
|
|
|
|
// Disable AutoSDKs if specified on the command line
|
|
if (GlobalCommandLine.NoAutoSDK)
|
|
{
|
|
PlatformExports.PreventAutoSDKSwitching();
|
|
}
|
|
|
|
// Setup environment
|
|
Log.TraceLog("Setting up command environment.");
|
|
CommandUtils.InitCommandEnvironment();
|
|
|
|
// Create the log file, and flush the startup listener to it
|
|
TraceListener LogTraceListener = LogUtils.AddLogFileListener(CommandUtils.CmdEnv.LogFolder,
|
|
CommandUtils.CmdEnv.FinalLogFolder);
|
|
StartupListener.CopyTo(LogTraceListener);
|
|
Trace.Listeners.Remove(StartupListener);
|
|
|
|
Log.TraceInformation($"Log location: {LogUtils.LogFileName}");
|
|
if (!String.Equals(LogUtils.FinalLogFileName, LogUtils.LogFileName))
|
|
{
|
|
Log.TraceInformation($"Final log location: {LogUtils.FinalLogFileName}");
|
|
}
|
|
|
|
// Initialize UBT
|
|
if (!UnrealBuildTool.PlatformExports.Initialize())
|
|
{
|
|
Log.TraceInformation("Failed to initialize UBT");
|
|
return ExitCode.Error_Unknown;
|
|
}
|
|
|
|
// Clean rules folders up
|
|
if (!CommandUtils.CmdEnv.IsChildInstance)
|
|
{
|
|
ProjectUtils.CleanupFolders();
|
|
}
|
|
|
|
// Compile scripts.
|
|
using (TelemetryStopwatch ScriptLoadStopwatch = new TelemetryStopwatch("ScriptLoad"))
|
|
{
|
|
ScriptManager.LoadScriptAssemblies(ScriptModuleAssemblies);
|
|
}
|
|
|
|
if (GlobalCommandLine.List)
|
|
{
|
|
ListAvailableCommands(ScriptManager.Commands);
|
|
return ExitCode.Success;
|
|
}
|
|
|
|
if (GlobalCommandLine.Help)
|
|
{
|
|
DisplayHelp(AutomationToolCommandLine.CommandsToExecute, ScriptManager.Commands);
|
|
return ExitCode.Success;
|
|
}
|
|
|
|
// Enable or disable P4 support
|
|
CommandUtils.InitP4Support(AutomationToolCommandLine.CommandsToExecute, ScriptManager.Commands);
|
|
if (CommandUtils.P4Enabled)
|
|
{
|
|
Log.TraceLog("Setting up Perforce environment.");
|
|
CommandUtils.InitP4Environment();
|
|
CommandUtils.InitDefaultP4Connection();
|
|
}
|
|
|
|
try
|
|
{
|
|
// Find and execute commands.
|
|
ExitCode Result = Execute(AutomationToolCommandLine.CommandsToExecute, ScriptManager.Commands);
|
|
if (TelemetryFile != null)
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(TelemetryFile));
|
|
CommandUtils.Telemetry.Write(TelemetryFile);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
finally
|
|
{
|
|
// Flush any timing data
|
|
TraceSpan.Flush();
|
|
}
|
|
}
|
|
catch (AutomationException Ex)
|
|
{
|
|
// Output the message in the desired format
|
|
if (Ex.OutputFormat == AutomationExceptionOutputFormat.Silent)
|
|
{
|
|
Log.TraceLog("{0}", ExceptionUtils.FormatExceptionDetails(Ex));
|
|
}
|
|
else if (Ex.OutputFormat == AutomationExceptionOutputFormat.Minimal)
|
|
{
|
|
Log.TraceInformation("{0}", Ex.ToString().Replace("\n", "\n "));
|
|
Log.TraceLog("{0}", ExceptionUtils.FormatExceptionDetails(Ex));
|
|
}
|
|
else
|
|
{
|
|
Log.WriteException(Ex, LogUtils.FinalLogFileName);
|
|
}
|
|
|
|
// Take the exit code from the exception
|
|
return Ex.ErrorCode;
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
// Use a default exit code
|
|
Log.WriteException(Ex, LogUtils.FinalLogFileName);
|
|
return ExitCode.Error_Unknown;
|
|
}
|
|
finally
|
|
{
|
|
// In all cases, do necessary shut down stuff, but don't let any additional exceptions leak out while trying to shut down.
|
|
|
|
// Make sure there's no directories on the stack.
|
|
NoThrow(() => CommandUtils.ClearDirStack(), "Clear Dir Stack");
|
|
|
|
// Try to kill process before app domain exits to leave the other KillAll call to extreme edge cases
|
|
NoThrow(() =>
|
|
{
|
|
if (ShouldKillProcesses && RuntimePlatform.IsWindows) ProcessManager.KillAll();
|
|
}, "Kill All Processes");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wraps an action in an exception block.
|
|
/// Ensures individual actions can be performed and exceptions won't prevent further actions from being executed.
|
|
/// Useful for shutdown code where shutdown may be in several stages and it's important that all stages get a chance to run.
|
|
/// </summary>
|
|
/// <param name="Action"></param>
|
|
private static void NoThrow(System.Action Action, string ActionDesc)
|
|
{
|
|
try
|
|
{
|
|
Action();
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
Log.TraceError("Exception performing nothrow action \"{0}\": {1}", ActionDesc, ExceptionUtils.FormatException(Ex));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute commands specified in the command line.
|
|
/// </summary>
|
|
/// <param name="CommandsToExecute"></param>
|
|
/// <param name="Commands"></param>
|
|
public static ExitCode Execute(List<CommandInfo> CommandsToExecute, Dictionary<string, Type> Commands)
|
|
{
|
|
for (int CommandIndex = 0; CommandIndex < CommandsToExecute.Count; ++CommandIndex)
|
|
{
|
|
var CommandInfo = CommandsToExecute[CommandIndex];
|
|
Log.TraceVerbose("Attempting to execute {0}", CommandInfo.ToString());
|
|
Type CommandType;
|
|
if (!Commands.TryGetValue(CommandInfo.CommandName, out CommandType))
|
|
{
|
|
throw new AutomationException("Failed to find command {0}", CommandInfo.CommandName);
|
|
}
|
|
|
|
BuildCommand Command = (BuildCommand)Activator.CreateInstance(CommandType);
|
|
Command.Params = CommandInfo.Arguments.ToArray();
|
|
try
|
|
{
|
|
ExitCode Result = Command.Execute();
|
|
if(Result != ExitCode.Success)
|
|
{
|
|
return Result;
|
|
}
|
|
CommandUtils.LogInformation("BUILD SUCCESSFUL");
|
|
}
|
|
finally
|
|
{
|
|
// dispose of the class if necessary
|
|
var CommandDisposable = Command as IDisposable;
|
|
if (CommandDisposable != null)
|
|
{
|
|
CommandDisposable.Dispose();
|
|
}
|
|
}
|
|
|
|
// Make sure there's no directories on the stack.
|
|
CommandUtils.ClearDirStack();
|
|
}
|
|
return ExitCode.Success;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display help for the specified commands (to execute)
|
|
/// </summary>
|
|
/// <param name="CommandsToExecute">List of commands specified in the command line.</param>
|
|
/// <param name="Commands">All discovered command objects.</param>
|
|
private static void DisplayHelp(List<CommandInfo> CommandsToExecute, Dictionary<string, Type> Commands)
|
|
{
|
|
for (int CommandIndex = 0; CommandIndex < CommandsToExecute.Count; ++CommandIndex)
|
|
{
|
|
var CommandInfo = CommandsToExecute[CommandIndex];
|
|
Type CommandType;
|
|
if (Commands.TryGetValue(CommandInfo.CommandName, out CommandType) == false)
|
|
{
|
|
Log.TraceError("Help: Failed to find command {0}", CommandInfo.CommandName);
|
|
}
|
|
else
|
|
{
|
|
CommandUtils.Help(CommandType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display AutomationTool.exe help.
|
|
/// </summary>
|
|
private static void DisplayHelp(Dictionary<string, string> ParamDict)
|
|
{
|
|
HelpUtils.PrintHelp("Automation Help:",
|
|
@"Executes scripted commands
|
|
|
|
AutomationTool.exe [-verbose] [-compileonly] [-p4] Command0 [-Arg0 -Arg1 -Arg2 ...] Command1 [-Arg0 -Arg1 ...] Command2 [-Arg0 ...] Commandn ... [EnvVar0=MyValue0 ... EnvVarn=MyValuen]",
|
|
ParamDict.ToList(), HelpUtils.WindowWidth, Log.Logger);
|
|
CommandUtils.LogHelp(typeof(Automation));
|
|
}
|
|
|
|
/// <summary>
|
|
/// List all available commands.
|
|
/// </summary>
|
|
/// <param name="Commands">All vailable commands.</param>
|
|
private static void ListAvailableCommands(Dictionary<string, Type> Commands)
|
|
{
|
|
string Message = Environment.NewLine;
|
|
Message += "Available commands:" + Environment.NewLine;
|
|
string AssemblyName = "";
|
|
foreach (var AvailableCommand in
|
|
Commands.OrderBy(Command => Command.Value.Assembly.GetName().Name).ThenBy(Command => Command.Key))
|
|
{
|
|
string NewAssemblyName = AvailableCommand.Value.Assembly.GetName().Name;
|
|
if (!String.Equals(AssemblyName, NewAssemblyName))
|
|
{
|
|
AssemblyName = NewAssemblyName;
|
|
Message += $" {AssemblyName}:\n";
|
|
}
|
|
Message += String.Format($" {AvailableCommand.Key}\n");
|
|
}
|
|
|
|
CommandUtils.LogInformation(Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if this process is running on a build machine, false if locally.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The reason one this property exists in Automation class and not BuildEnvironment is that
|
|
/// it's required long before BuildEnvironment is initialized.
|
|
/// </remarks>
|
|
public static bool IsBuildMachine
|
|
{
|
|
get
|
|
{
|
|
if (!bIsBuildMachine.HasValue)
|
|
{
|
|
throw new AutomationException("Trying to access IsBuildMachine property before it was initialized.");
|
|
}
|
|
return (bool)bIsBuildMachine;
|
|
}
|
|
private set
|
|
{
|
|
bIsBuildMachine = value;
|
|
}
|
|
}
|
|
private static bool? bIsBuildMachine;
|
|
|
|
public static bool ShouldKillProcesses
|
|
{
|
|
get
|
|
{
|
|
return bShouldKillProcesses;
|
|
}
|
|
private set
|
|
{
|
|
bShouldKillProcesses = value;
|
|
}
|
|
}
|
|
private static bool bShouldKillProcesses = true;
|
|
}
|
|
|
|
|
|
// This class turns stringly-typed commandline option values into named compile-time values
|
|
public class GlobalCommandLine
|
|
{
|
|
// Descriptions for these command line options may be found in AutomationTool/Program.cs
|
|
|
|
public static void Initialize(ParsedCommandLine AutomationToolCommandLine)
|
|
{
|
|
BuildMachine = AutomationToolCommandLine.IsSetGlobal("-BuildMachine");
|
|
NoKill = AutomationToolCommandLine.IsSetGlobal("-NoKill");
|
|
Help = AutomationToolCommandLine.IsSetGlobal("-Help");
|
|
NoAutoSDK = AutomationToolCommandLine.IsSetGlobal("-NoAutoSDK");
|
|
List = AutomationToolCommandLine.IsSetGlobal("-List");
|
|
TelemetryPath = (string)AutomationToolCommandLine.GetValueUnchecked("-Telemetry");
|
|
Verbose = AutomationToolCommandLine.IsSetGlobal("-Verbose");
|
|
AllowStdOutLogVerbosity = AutomationToolCommandLine.IsSetGlobal("-AllowStdOutLogVerbosity");
|
|
UTF8Output = AutomationToolCommandLine.IsSetGlobal("-UTF8Output");
|
|
UseLocalBuildStorage = AutomationToolCommandLine.IsSetGlobal("-UseLocalBuildStorage");
|
|
Submit = AutomationToolCommandLine.IsSetGlobal("-Submit");
|
|
NoSubmit = AutomationToolCommandLine.IsSetGlobal("-NoSubmit");
|
|
P4 = AutomationToolCommandLine.IsSetGlobal("-P4");
|
|
NoP4 = AutomationToolCommandLine.IsSetGlobal("-NoP4");
|
|
}
|
|
|
|
// Using Nullable bools here to ensure that Initialize() has been called before the members are accesed.
|
|
// Accessing the value before it has been set will cause an InvalidOperationException to be thrown.
|
|
// Private setters ensure values are set by Initialize()
|
|
|
|
private static bool? bBuildMachine;
|
|
public static bool BuildMachine
|
|
{
|
|
get => bBuildMachine.Value;
|
|
private set { bBuildMachine = value; }
|
|
}
|
|
|
|
private static bool? bNoKill;
|
|
public static bool NoKill
|
|
{
|
|
get => bNoKill.Value;
|
|
private set { bNoKill = value; }
|
|
}
|
|
|
|
private static bool? bHelp;
|
|
public static bool Help
|
|
{
|
|
get => bHelp.Value;
|
|
private set { bHelp = value; }
|
|
}
|
|
|
|
private static bool? bNoAutoSDK;
|
|
public static bool NoAutoSDK
|
|
{
|
|
get => bNoAutoSDK.Value;
|
|
private set { bNoAutoSDK = value; }
|
|
}
|
|
|
|
private static bool? bList;
|
|
public static bool List
|
|
{
|
|
get => bList.Value;
|
|
private set { bList = value; }
|
|
}
|
|
|
|
private static bool? bVerbose;
|
|
public static bool Verbose
|
|
{
|
|
get => bVerbose.Value;
|
|
private set { bVerbose = value; }
|
|
}
|
|
|
|
private static bool? bAllowStdOutLogVerbosity;
|
|
public static bool AllowStdOutLogVerbosity
|
|
{
|
|
get => bAllowStdOutLogVerbosity.Value;
|
|
private set { bAllowStdOutLogVerbosity = value; }
|
|
}
|
|
|
|
private static bool? bUTF8Output;
|
|
public static bool UTF8Output
|
|
{
|
|
get => bUTF8Output.Value;
|
|
private set { bUTF8Output = value; }
|
|
}
|
|
|
|
private static bool? bUseLocalBuildStorage;
|
|
public static bool UseLocalBuildStorage
|
|
{
|
|
get => bUseLocalBuildStorage.Value;
|
|
private set { bUseLocalBuildStorage = value; }
|
|
}
|
|
|
|
private static bool? bSubmit;
|
|
public static bool Submit
|
|
{
|
|
get => bSubmit.Value;
|
|
private set { bSubmit = value; }
|
|
}
|
|
|
|
private static bool? bNoSubmit;
|
|
public static bool NoSubmit
|
|
{
|
|
get => bNoSubmit.Value;
|
|
private set { bNoSubmit = value; }
|
|
}
|
|
|
|
private static bool? bP4;
|
|
public static bool P4
|
|
{
|
|
get => bP4.Value;
|
|
private set { bP4 = value; }
|
|
}
|
|
|
|
private static bool? bNoP4;
|
|
public static bool NoP4
|
|
{
|
|
get => bNoP4.Value;
|
|
private set { bNoP4 = value; }
|
|
}
|
|
|
|
public static string TelemetryPath
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
}
|
|
}
|