Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/Program.cs
Ben Marsh adef91feea UAT: Add support for setting a status message through the log class. Allows writing transient messages (eg. progress messages) which will be cleared out before writing other messages. Best used through the LogStatusScope class, which can set a status message for the duration of a using() block.
As part of this change, the console no longer has to be added as a dedicated trace listener. Since we already special-case this listener when formatting log output, it's easier to just keep the implementation separate to the other trace listeners.

#rb none

[CL 4111304 by Ben Marsh in Dev-Core branch]
2018-06-04 17:23:16 -04:00

181 lines
6.7 KiB
C#

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
// This software is provided "as-is," without any express or implied warranty.
// In no event shall the author, nor Epic Games, Inc. be held liable for any damages arising from the use of this software.
// This software will not be supported.
// Use at your own risk.
using System;
using System.Threading;
using System.Diagnostics;
using UnrealBuildTool;
using System.Reflection;
using Tools.DotNETCommon;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace AutomationTool
{
public class Program
{
/// <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;
[STAThread]
public static int Main()
{
// Parse the command line
string[] Arguments = SharedUtils.ParseCommandLineAndRemoveExe(Environment.CommandLine);
// Ensure UTF8Output flag is respected, since we are initializing logging early in the program.
if (CommandUtils.ParseParam(Arguments, "-Utf8output"))
{
Console.OutputEncoding = new System.Text.UTF8Encoding(false, false);
}
// Parse the log level argument
LogEventType LogLevel = LogEventType.Log;
if(CommandUtils.ParseParam(Arguments, "-Verbose"))
{
LogLevel = LogEventType.Verbose;
}
if(CommandUtils.ParseParam(Arguments, "-VeryVerbose"))
{
LogLevel = LogEventType.VeryVerbose;
}
// Initialize the log system, buffering the output until we can create the log file
StartupTraceListener StartupListener = new StartupTraceListener();
Log.InitLogging(
bLogTimestamps: CommandUtils.ParseParam(Arguments, "-Timestamps"),
InLogLevel: LogLevel,
bLogSeverity: true,
bLogProgramNameWithSeverity: false,
bLogSources: true,
bLogSourcesToConsole: false,
bColorConsoleOutput: true,
TraceListeners: new TraceListener[]
{
StartupListener
}
);
// Enter the main program section
ExitCode ReturnCode = ExitCode.Success;
try
{
// Set the working directory to the UE4 root
Environment.CurrentDirectory = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetOriginalLocation()), "..", "..", ".."));
// Ensure we can resolve any external assemblies as necessary.
AssemblyUtils.InstallAssemblyResolver(Path.GetDirectoryName(Assembly.GetEntryAssembly().GetOriginalLocation()));
// Initialize the host platform layer
HostPlatform.Initialize();
// Log the operating environment. Since we usually compile to AnyCPU, we may be executed using different system paths under WOW64.
Log.TraceVerbose("{2}: Running on {0} as a {1}-bit process.", HostPlatform.Current.GetType().Name, Environment.Is64BitProcess ? 64 : 32, DateTime.UtcNow.ToString("o"));
// Log if we're running from the launcher
string ExecutingAssemblyLocation = Assembly.GetExecutingAssembly().Location;
if (string.Compare(ExecutingAssemblyLocation, Assembly.GetEntryAssembly().GetOriginalLocation(), StringComparison.OrdinalIgnoreCase) != 0)
{
Log.TraceVerbose("Executed from AutomationToolLauncher ({0})", ExecutingAssemblyLocation);
}
Log.TraceVerbose("CWD={0}", Environment.CurrentDirectory);
// Hook up exit callbacks
AppDomain Domain = AppDomain.CurrentDomain;
Domain.ProcessExit += Domain_ProcessExit;
Domain.DomainUnload += Domain_ProcessExit;
HostPlatform.Current.SetConsoleCtrlHandler(CtrlHandlerDelegateInstance);
// Log the application version
FileVersionInfo Version = AssemblyUtils.ExecutableVersion;
Log.TraceVerbose("{0} ver. {1}", Version.ProductName, Version.ProductVersion);
// Don't allow simultaneous execution of AT (in the same branch)
ReturnCode = InternalUtils.RunSingleInstance(() => MainProc(Arguments, StartupListener));
}
catch (AutomationException Ex)
{
// Take the exit code from the exception
Log.WriteException(Ex, LogUtils.FinalLogFileName);
ReturnCode = Ex.ErrorCode;
}
catch (Exception Ex)
{
// Use a default exit code
Log.WriteException(Ex, LogUtils.FinalLogFileName);
ReturnCode = 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 && !Utils.IsRunningOnMono) ProcessManager.KillAll(); }, "Kill All Processes");
// Write the exit code
Log.TraceInformation("AutomationTool exiting with ExitCode={0} ({1})", (int)ReturnCode, ReturnCode);
// Can't use NoThrow here because the code logs exceptions. We're shutting down logging!
Trace.Close();
}
return (int)ReturnCode;
}
/// <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, LogUtils.FormatException(Ex));
}
}
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 && !Utils.IsRunningOnMono)
{
ProcessManager.KillAll();
}
Trace.Close();
}
static ExitCode MainProc(string[] Arguments, StartupTraceListener StartupListener)
{
ExitCode Result = Automation.Process(Arguments, StartupListener);
ShouldKillProcesses = Automation.ShouldKillProcesses;
return Result;
}
static bool ShouldKillProcesses = true;
}
}