2019-01-17 18:54:05 -05:00
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
// 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 ;
2015-07-22 14:00:30 -04:00
using Tools.DotNETCommon ;
2015-08-06 15:55:44 -04:00
using System.IO ;
2017-07-21 12:42:36 -04:00
using System.Collections.Generic ;
using System.Text ;
2014-03-14 14:13:41 -04:00
namespace AutomationTool
{
2014-08-29 12:25:29 -04:00
public class Program
2014-03-14 14:13:41 -04:00
{
2016-05-10 08:49:41 -04:00
/// <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 ;
2014-07-01 10:58:33 -04:00
[STAThread]
2019-01-17 18:54:05 -05:00
public static int Main ( string [ ] Arguments )
2014-03-14 14:13:41 -04:00
{
2018-01-20 11:19:29 -05:00
// 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 ) ;
}
2015-06-30 11:40:05 -04:00
2018-01-20 11:19:29 -05:00
// Parse the log level argument
if ( CommandUtils . ParseParam ( Arguments , "-Verbose" ) )
{
2019-01-17 18:54:05 -05:00
Log . OutputLevel = LogEventType . Verbose ;
2018-01-20 11:19:29 -05:00
}
if ( CommandUtils . ParseParam ( Arguments , "-VeryVerbose" ) )
{
2019-01-17 18:54:05 -05:00
Log . OutputLevel = LogEventType . VeryVerbose ;
2018-01-20 11:19:29 -05:00
}
// Initialize the log system, buffering the output until we can create the log file
StartupTraceListener StartupListener = new StartupTraceListener ( ) ;
2019-01-17 18:54:05 -05:00
Trace . Listeners . Add ( StartupListener ) ;
// Configure log timestamps
Log . IncludeTimestamps = CommandUtils . ParseParam ( Arguments , "-Timestamps" ) ;
2018-01-20 11:19:29 -05:00
// Enter the main program section
2015-08-20 08:38:09 -04:00
ExitCode ReturnCode = ExitCode . Success ;
try
{
2018-01-20 11:19:29 -05:00
// 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.
2018-11-28 15:05:11 -05:00
string PathToBinariesDotNET = Path . GetDirectoryName ( Assembly . GetEntryAssembly ( ) . GetOriginalLocation ( ) ) ;
AssemblyUtils . InstallAssemblyResolver ( PathToBinariesDotNET ) ;
AssemblyUtils . InstallRecursiveAssemblyResolver ( PathToBinariesDotNET ) ;
2018-01-20 11:19:29 -05:00
// Initialize the host platform layer
2015-08-20 08:38:09 -04:00
HostPlatform . Initialize ( ) ;
2015-06-30 11:40:05 -04:00
2018-01-20 11:19:29 -05:00
// Log the operating environment. Since we usually compile to AnyCPU, we may be executed using different system paths under WOW64.
2016-01-11 10:12:46 -05:00
Log . TraceVerbose ( "{2}: Running on {0} as a {1}-bit process." , HostPlatform . Current . GetType ( ) . Name , Environment . Is64BitProcess ? 64 : 32 , DateTime . UtcNow . ToString ( "o" ) ) ;
2014-03-14 14:13:41 -04:00
2015-08-20 08:38:09 -04:00
// Log if we're running from the launcher
2018-01-20 11:19:29 -05:00
string ExecutingAssemblyLocation = Assembly . GetExecutingAssembly ( ) . Location ;
2015-08-20 08:38:09 -04:00
if ( string . Compare ( ExecutingAssemblyLocation , Assembly . GetEntryAssembly ( ) . GetOriginalLocation ( ) , StringComparison . OrdinalIgnoreCase ) ! = 0 )
{
Log . TraceVerbose ( "Executed from AutomationToolLauncher ({0})" , ExecutingAssemblyLocation ) ;
}
Log . TraceVerbose ( "CWD={0}" , Environment . CurrentDirectory ) ;
2014-03-14 14:13:41 -04:00
2015-08-20 08:38:09 -04:00
// Hook up exit callbacks
2018-01-20 11:19:29 -05:00
AppDomain Domain = AppDomain . CurrentDomain ;
2015-08-20 08:38:09 -04:00
Domain . ProcessExit + = Domain_ProcessExit ;
Domain . DomainUnload + = Domain_ProcessExit ;
2016-05-10 08:49:41 -04:00
HostPlatform . Current . SetConsoleCtrlHandler ( CtrlHandlerDelegateInstance ) ;
2014-03-14 14:13:41 -04:00
2018-01-20 11:19:29 -05:00
// Log the application version
FileVersionInfo Version = AssemblyUtils . ExecutableVersion ;
2015-08-20 08:38:09 -04:00
Log . TraceVerbose ( "{0} ver. {1}" , Version . ProductName , Version . ProductVersion ) ;
2014-03-14 14:13:41 -04:00
2015-08-20 08:38:09 -04:00
// Don't allow simultaneous execution of AT (in the same branch)
2018-01-20 11:19:29 -05:00
ReturnCode = InternalUtils . RunSingleInstance ( ( ) = > MainProc ( Arguments , StartupListener ) ) ;
}
catch ( AutomationException Ex )
{
2019-03-22 18:26:00 -04:00
// Output the message in the desired format
if ( Ex . OutputFormat = = AutomationExceptionOutputFormat . Silent )
{
Log . TraceLog ( "{0}" , ExceptionUtils . FormatExceptionDetails ( Ex ) ) ;
}
else if ( Ex . OutputFormat = = AutomationExceptionOutputFormat . Minimal )
{
2019-03-22 18:51:26 -04:00
Log . TraceInformation ( "{0}" , Ex . ToString ( ) . Replace ( "\n" , "\n " ) ) ;
2019-03-22 18:26:00 -04:00
Log . TraceLog ( "{0}" , ExceptionUtils . FormatExceptionDetails ( Ex ) ) ;
}
else
{
Log . WriteException ( Ex , LogUtils . FinalLogFileName ) ;
}
2018-01-20 11:19:29 -05:00
// Take the exit code from the exception
ReturnCode = Ex . ErrorCode ;
2015-08-20 08:38:09 -04:00
}
catch ( Exception Ex )
{
2018-01-20 11:19:29 -05:00
// Use a default exit code
2018-08-15 11:23:10 -04:00
Log . WriteException ( Ex , LogUtils . FinalLogFileName ) ;
2018-01-20 11:19:29 -05:00
ReturnCode = ExitCode . Error_Unknown ;
2015-08-20 08:38:09 -04:00
}
2015-04-26 18:19:28 -04:00
finally
{
2015-06-30 11:40:05 -04:00
// 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" ) ;
2018-01-20 11:19:29 -05:00
// Write the exit code
2015-08-20 08:38:09 -04:00
Log . TraceInformation ( "AutomationTool exiting with ExitCode={0} ({1})" , ( int ) ReturnCode , ReturnCode ) ;
2015-06-30 11:40:05 -04:00
// Can't use NoThrow here because the code logs exceptions. We're shutting down logging!
2018-01-20 11:19:29 -05:00
Trace . Close ( ) ;
2015-04-26 18:19:28 -04:00
}
2015-06-30 11:40:05 -04:00
return ( int ) ReturnCode ;
}
2014-03-14 14:13:41 -04:00
2017-07-21 12:42:36 -04:00
/// <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 )
2015-06-30 11:40:05 -04:00
{
try
{
Action ( ) ;
}
catch ( Exception Ex )
{
2015-07-23 14:51:46 -04:00
Log . TraceError ( "Exception performing nothrow action \"{0}\": {1}" , ActionDesc , LogUtils . FormatException ( Ex ) ) ;
2015-06-30 11:40:05 -04:00
}
}
static bool CtrlHandler ( CtrlTypes EventType )
2014-03-14 14:13:41 -04:00
{
Domain_ProcessExit ( null , null ) ;
if ( EventType = = CtrlTypes . CTRL_C_EVENT )
{
// Force exit
Environment . Exit ( 3 ) ;
2014-05-14 14:52:20 -04:00
}
2014-03-14 14:13:41 -04:00
return true ;
}
static void Domain_ProcessExit ( object sender , EventArgs e )
{
2014-05-14 14:52:20 -04:00
// Kill all spawned processes (Console instead of Log because logging is closed at this time anyway)
2015-02-27 15:47:09 -05:00
if ( ShouldKillProcesses & & ! Utils . IsRunningOnMono )
2014-04-23 18:25:41 -04:00
{
2014-03-14 14:13:41 -04:00
ProcessManager . KillAll ( ) ;
}
Trace . Close ( ) ;
}
2018-01-20 11:19:29 -05:00
static ExitCode MainProc ( string [ ] Arguments , StartupTraceListener StartupListener )
2014-03-14 14:13:41 -04:00
{
2018-01-20 11:19:29 -05:00
ExitCode Result = Automation . Process ( Arguments , StartupListener ) ;
2014-03-14 14:13:41 -04:00
ShouldKillProcesses = Automation . ShouldKillProcesses ;
2015-08-20 09:46:59 -04:00
return Result ;
2014-03-14 14:13:41 -04:00
}
static bool ShouldKillProcesses = true ;
}
}