2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.IO ;
using System.Reflection ;
using System.Diagnostics ;
using UnrealBuildTool ;
2015-06-01 10:14:54 -04:00
using Tools.DotNETCommon.CaselessDictionary ;
2014-03-14 14:13:41 -04:00
namespace AutomationTool
{
#region Command info
/// <summary>
/// Command to execute info.
/// </summary>
class CommandInfo
{
public string CommandName ;
public List < string > Arguments = new List < string > ( ) ;
public override string ToString ( )
{
string Result = CommandName ;
Result + = "(" ;
for ( int Index = 0 ; Index < Arguments . Count ; + + Index )
{
if ( Index > 0 )
{
Result + = ", " ;
}
Result + = Arguments [ Index ] ;
}
Result + = ")" ;
return Result ;
}
}
#endregion
#region Command Line helpers
/// <summary>
/// Helper class for automatically registering command line params.
/// </summary>
public class CommandLineArg
{
public delegate void OnSetDelegate ( ) ;
public string Name ;
public OnSetDelegate SetDelegate ;
/// <summary>
/// True of this arg was set in the command line.
/// </summary>
public bool IsSet { get ; private set ; }
public CommandLineArg ( string InName , OnSetDelegate InSet = null )
{
Name = InName ;
SetDelegate = InSet ;
GlobalCommandLine . RegisteredArgs . Add ( Name , this ) ;
}
public void Set ( )
{
IsSet = true ;
if ( SetDelegate ! = null )
{
SetDelegate ( ) ;
}
}
public override string ToString ( )
{
return String . Format ( "{0}, {1}" , Name , IsSet ) ;
}
/// <summary>
/// Returns true of this arg was set in the command line.
/// </summary>
public static implicit operator bool ( CommandLineArg Arg )
{
return Arg . IsSet ;
}
}
/// <summary>
/// Global command line parameters.
/// </summary>
public static class GlobalCommandLine
{
/// <summary>
/// List of all registered command line parameters (global, not command-specific)
/// </summary>
public static CaselessDictionary < CommandLineArg > RegisteredArgs = new CaselessDictionary < CommandLineArg > ( ) ;
public static CommandLineArg CompileOnly = new CommandLineArg ( "-CompileOnly" ) ;
public static CommandLineArg Verbose = new CommandLineArg ( "-Verbose" ) ;
public static CommandLineArg Submit = new CommandLineArg ( "-Submit" ) ;
public static CommandLineArg NoSubmit = new CommandLineArg ( "-NoSubmit" ) ;
public static CommandLineArg ForceLocal = new CommandLineArg ( "-ForceLocal" ) ;
public static CommandLineArg NoP4 = new CommandLineArg ( "-NoP4" ) ;
public static CommandLineArg P4 = new CommandLineArg ( "-P4" ) ;
2015-07-09 10:15:37 -04:00
public static CommandLineArg Preprocess = new CommandLineArg ( "-Preprocess" ) ;
public static CommandLineArg Compile = new CommandLineArg ( "-Compile" ) ;
/// <summary>
/// This command is LEGACY because we used to run UAT.exe to compile scripts by default.
/// Now we only compile by default when run via RunUAT.bat, which still understands -nocompile.
/// However, the batch file simply passes on all arguments, so UAT will choke when encountering -nocompile.
/// Keep this CommandLineArg around so that doesn't happen.
/// </summary>
public static CommandLineArg NoCompileLegacyDontUse = new CommandLineArg ( "-NoCompile" ) ;
public static CommandLineArg NoCompileEditor = new CommandLineArg ( "-NoCompileEditor" ) ;
2014-03-14 14:13:41 -04:00
public static CommandLineArg Help = new CommandLineArg ( "-Help" ) ;
public static CommandLineArg List = new CommandLineArg ( "-List" ) ;
public static CommandLineArg Rocket = new CommandLineArg ( "-Rocket" ) ;
public static CommandLineArg NoKill = new CommandLineArg ( "-NoKill" ) ;
public static CommandLineArg Installed = new CommandLineArg ( "-Installed" ) ;
2014-04-23 18:15:18 -04:00
public static CommandLineArg UTF8Output = new CommandLineArg ( "-UTF8Output" ) ;
2014-11-25 17:54:11 -05:00
public static CommandLineArg NoAutoSDK = new CommandLineArg ( "-NoAutoSDK" ) ;
2015-03-05 07:53:52 -05:00
public static CommandLineArg IgnoreJunk = new CommandLineArg ( "-ignorejunk" ) ;
2015-08-05 10:22:11 -04:00
/// <summary>
/// Allows you to use local storage for your root build storage dir (default of P:\Builds (on PC) is changed to Engine\Saved\LocalBuilds). Used for local testing.
/// </summary>
public static CommandLineArg UseLocalBuildStorage = new CommandLineArg ( "-UseLocalBuildStorage" ) ;
2014-03-14 14:13:41 -04:00
/// <summary>
/// Force initialize static members by calling this.
/// </summary>
public static void Init ( )
{
Log . TraceVerbose ( "Registered {0} command line parameters." , RegisteredArgs . Count ) ;
}
}
#endregion
[ Help (
@ "Executes scripted commands
AutomationTool . exe [ - verbose ] [ - compileonly ] [ - p4 ] Command0 [ - Arg0 - Arg1 - Arg2 … ] Command1 [ - Arg0 - Arg1 … ] Command2 [ - Arg0 … ] Commandn … [ EnvVar0 = MyValue0 … EnvVarn = MyValuen ] "
) ]
[Help("verbose", "Enables verbose logging")]
[Help("nop4", "Disables Perforce functionality (default if not run on a build machine)")]
[Help("p4", "Enables Perforce functionality (default if run on a build machine)")]
[Help("compileonly", "Does not run any commands, only compiles them")]
2015-07-09 10:15:37 -04:00
[Help("compile", "Dynamically compiles all commands (otherwise assumes they are already built)")]
[Help("forcelocal", "Forces local execution")]
2014-03-14 14:13:41 -04:00
[Help("help", "Displays help")]
[Help("list", "Lists all available commands")]
[Help("submit", "Allows UAT command to submit changes")]
[Help("nosubmit", "Prevents any submit attempts")]
[Help("nokill", "Does not kill any spawned processes on exit")]
2015-03-05 07:53:52 -05:00
[Help("ignorejunk", "Prevents UBT from cleaning junk files")]
2015-08-05 10:22:11 -04:00
[Help("UseLocalBuildStorage", @"Allows you to use local storage for your root build storage dir (default of P:\Builds (on PC) is changed to Engine\Saved\LocalBuilds). Used for local testing.")]
public static class Automation
2014-03-14 14:13:41 -04:00
{
#region Command line parsing
/// <summary>
/// Parses command line parameter.
/// </summary>
/// <param name="ParamIndex">Parameter index</param>
/// <param name="CommandLine">Command line</param>
/// <param name="CurrentCommand">Recently parsed command</param>
2015-07-30 12:15:19 -04:00
/// <param name="OutScriptsForProjectFileName">The only project to build scripts for</param>
2014-03-14 14:13:41 -04:00
/// <param name="OutAdditionalScriptsFolders">Additional script locations</param>
/// <returns>True if the parameter has been successfully parsed.</returns>
2015-07-30 12:15:19 -04:00
private static void ParseParam ( string CurrentParam , CommandInfo CurrentCommand , ref string OutScriptsForProjectFileName , List < string > OutAdditionalScriptsFolders )
2014-03-14 14:13:41 -04:00
{
bool bGlobalParam = false ;
foreach ( var RegisteredParam in GlobalCommandLine . RegisteredArgs )
{
2015-08-05 10:22:11 -04:00
if ( String . Compare ( CurrentParam , RegisteredParam . Key , StringComparison . InvariantCultureIgnoreCase ) = = 0 )
2014-03-14 14:13:41 -04:00
{
// Register and exit, we're done here.
RegisteredParam . Value . Set ( ) ;
bGlobalParam = true ;
break ;
}
}
// The parameter was not found in the list of global parameters, continue looking...
2015-07-30 12:15:19 -04:00
if ( CurrentParam . StartsWith ( "-ScriptsForProject=" , StringComparison . InvariantCultureIgnoreCase ) )
{
if ( OutScriptsForProjectFileName ! = null )
{
throw new AutomationException ( "The -ProjectScripts argument may only be specified once" ) ;
}
var ProjectFileName = CurrentParam . Substring ( CurrentParam . IndexOf ( '=' ) + 1 ) ;
if ( ! File . Exists ( ProjectFileName ) )
{
throw new AutomationException ( "Project '{0}' does not exist" , ProjectFileName ) ;
}
OutScriptsForProjectFileName = Path . GetFullPath ( ProjectFileName ) ;
}
else if ( CurrentParam . StartsWith ( "-ScriptDir=" , StringComparison . InvariantCultureIgnoreCase ) )
2014-03-14 14:13:41 -04:00
{
var ScriptDir = CurrentParam . Substring ( CurrentParam . IndexOf ( '=' ) + 1 ) ;
if ( Directory . Exists ( ScriptDir ) )
{
OutAdditionalScriptsFolders . Add ( ScriptDir ) ;
Log . TraceVerbose ( "Found additional script dir: {0}" , ScriptDir ) ;
}
else
{
throw new AutomationException ( "Specified ScriptDir doesn't exist: {0}" , ScriptDir ) ;
}
}
else if ( CurrentParam . StartsWith ( "-" ) )
{
if ( CurrentCommand ! = null )
{
CurrentCommand . Arguments . Add ( CurrentParam . Substring ( 1 ) ) ;
}
else if ( ! bGlobalParam )
{
throw new AutomationException ( "Unknown parameter {0} in the command line that does not belong to any command." , CurrentParam ) ;
}
}
else if ( CurrentParam . Contains ( "=" ) )
{
// Environment variable
int ValueStartIndex = CurrentParam . IndexOf ( '=' ) + 1 ;
string EnvVarName = CurrentParam . Substring ( 0 , ValueStartIndex - 1 ) ;
if ( String . IsNullOrEmpty ( EnvVarName ) )
{
throw new AutomationException ( "Unable to parse environment variable that has no name. Error when parsing command line param {0}" , CurrentParam ) ;
}
string EnvVarValue = CurrentParam . Substring ( ValueStartIndex ) ;
CommandUtils . SetEnvVar ( EnvVarName , EnvVarValue ) ;
}
}
/// <summary>
/// Parse the command line and create a list of commands to execute.
/// </summary>
/// <param name="CommandLine">Command line</param>
/// <param name="OutCommandsToExecute">List containing the names of commands to execute</param>
/// <param name="OutAdditionalScriptsFolders">Optional list of additional paths to look for scripts in</param>
2015-07-30 12:15:19 -04:00
private static void ParseCommandLine ( string [ ] CommandLine , List < CommandInfo > OutCommandsToExecute , out string OutScriptsForProjectFileName , List < string > OutAdditionalScriptsFolders )
2014-03-14 14:13:41 -04:00
{
// Initialize global command line parameters
GlobalCommandLine . Init ( ) ;
Log . TraceInformation ( "Parsing command line: {0}" , String . Join ( " " , CommandLine ) ) ;
2015-07-30 12:15:19 -04:00
OutScriptsForProjectFileName = null ;
2014-03-14 14:13:41 -04:00
CommandInfo CurrentCommand = null ;
for ( int Index = 0 ; Index < CommandLine . Length ; + + Index )
{
var Param = CommandLine [ Index ] ;
if ( Param . StartsWith ( "-" ) | | Param . Contains ( "=" ) )
{
2015-07-30 12:15:19 -04:00
ParseParam ( CommandLine [ Index ] , CurrentCommand , ref OutScriptsForProjectFileName , OutAdditionalScriptsFolders ) ;
2014-03-14 14:13:41 -04:00
}
else
{
CurrentCommand = new CommandInfo ( ) ;
CurrentCommand . CommandName = CommandLine [ Index ] ;
OutCommandsToExecute . Add ( CurrentCommand ) ;
}
}
// Validate
var Result = OutCommandsToExecute . Count > 0 | | GlobalCommandLine . Help | | GlobalCommandLine . CompileOnly | | GlobalCommandLine . List ;
if ( OutCommandsToExecute . Count > 0 )
{
Log . TraceVerbose ( "Found {0} scripts to execute:" , OutCommandsToExecute . Count ) ;
foreach ( var Command in OutCommandsToExecute )
{
Log . TraceVerbose ( " " + Command . ToString ( ) ) ;
}
}
else if ( ! Result )
{
throw new AutomationException ( "Failed to find scripts to execute in the command line params." ) ;
}
if ( GlobalCommandLine . NoP4 & & GlobalCommandLine . P4 )
{
throw new AutomationException ( "{0} and {1} can't be set simultaneously." , GlobalCommandLine . NoP4 . Name , GlobalCommandLine . P4 . Name ) ;
}
if ( GlobalCommandLine . NoSubmit & & GlobalCommandLine . Submit )
{
throw new AutomationException ( "{0} and {1} can't be set simultaneously." , GlobalCommandLine . NoSubmit . Name , GlobalCommandLine . Submit . Name ) ;
}
2014-07-08 12:54:23 -04:00
if ( GlobalCommandLine . Rocket )
{
UnrealBuildTool . UnrealBuildTool . bRunningRocket = true ;
}
2014-03-14 14:13:41 -04:00
}
#endregion
#region Main Program
/// <summary>
/// Main method.
/// </summary>
/// <param name="CommandLine">Command line</param>
public static void Process ( string [ ] CommandLine )
{
2014-09-05 12:41:18 -04:00
// Initial check for local or build machine runs BEFORE we parse the command line (We need this value set
// in case something throws the exception while parsing the command line)
IsBuildMachine = ! String . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "uebp_LOCAL_ROOT" ) ) ;
2014-03-14 14:13:41 -04:00
// Scan the command line for commands to execute.
var CommandsToExecute = new List < CommandInfo > ( ) ;
2015-07-30 12:15:19 -04:00
string OutScriptsForProjectFileName ;
2014-03-14 14:13:41 -04:00
var AdditionalScriptsFolders = new List < string > ( ) ;
2015-07-30 12:15:19 -04:00
ParseCommandLine ( CommandLine , CommandsToExecute , out OutScriptsForProjectFileName , AdditionalScriptsFolders ) ;
2014-03-14 14:13:41 -04:00
2014-09-05 12:41:18 -04:00
// Check for build machine override (force local)
IsBuildMachine = GlobalCommandLine . ForceLocal ? false : IsBuildMachine ;
2015-07-23 14:51:46 -04:00
Log . TraceVerbose ( "IsBuildMachine={0}" , IsBuildMachine ) ;
2014-11-20 15:05:06 -05:00
Environment . SetEnvironmentVariable ( "IsBuildMachine" , IsBuildMachine ? "1" : "0" ) ;
2014-03-14 14:13:41 -04:00
// should we kill processes on exit
ShouldKillProcesses = ! GlobalCommandLine . NoKill ;
2015-07-23 14:51:46 -04:00
Log . TraceVerbose ( "ShouldKillProcesses={0}" , ShouldKillProcesses ) ;
2014-03-14 14:13:41 -04:00
2014-05-09 14:32:42 -04:00
if ( CommandsToExecute . Count = = 0 & & GlobalCommandLine . Help )
{
DisplayHelp ( ) ;
return ;
}
2014-11-25 17:54:11 -05:00
// Disable AutoSDKs if specified on the command line
if ( GlobalCommandLine . NoAutoSDK )
{
UEBuildPlatform . bAllowAutoSDKSwitching = false ;
}
2014-03-14 14:13:41 -04:00
// Setup environment
Log . TraceInformation ( "Setting up command environment." ) ;
CommandUtils . InitCommandEnvironment ( ) ;
// Change CWD to UE4 root.
Environment . CurrentDirectory = CommandUtils . CmdEnv . LocalRoot ;
// Fill in the project info
UnrealBuildTool . UProjectInfo . FillProjectInfo ( ) ;
2014-04-23 18:28:28 -04:00
// Clean rules folders up
ProjectUtils . CleanupFolders ( ) ;
2014-03-14 14:13:41 -04:00
// Compile scripts.
Log . TraceInformation ( "Compiling scripts." ) ;
ScriptCompiler Compiler = new ScriptCompiler ( ) ;
2015-08-05 22:16:45 -04:00
using ( TelemetryStopwatch ScriptCompileStopwatch = new TelemetryStopwatch ( "ScriptCompile" ) )
2015-07-02 16:16:19 -04:00
{
2015-07-30 12:15:19 -04:00
Compiler . FindAndCompileAllScripts ( OutScriptsForProjectFileName , AdditionalScriptsFolders ) ;
2015-07-02 16:16:19 -04:00
}
2014-03-14 14:13:41 -04:00
if ( GlobalCommandLine . CompileOnly )
{
Log . TraceInformation ( "Compilation successful, exiting (CompileOnly)" ) ;
return ;
}
if ( GlobalCommandLine . List )
{
ListAvailableCommands ( Compiler . Commands ) ;
return ;
}
if ( GlobalCommandLine . Help )
{
DisplayHelp ( CommandsToExecute , Compiler . Commands ) ;
return ;
}
// Enable or disable P4 support
CommandUtils . InitP4Support ( CommandsToExecute , Compiler . Commands ) ;
if ( CommandUtils . P4Enabled )
{
Log . TraceInformation ( "Setting up Perforce environment." ) ;
CommandUtils . InitP4Environment ( ) ;
2014-04-02 18:09:23 -04:00
CommandUtils . InitDefaultP4Connection ( ) ;
2014-03-14 14:13:41 -04:00
}
// Find and execute commands.
Execute ( CommandsToExecute , Compiler . Commands ) ;
return ;
}
/// <summary>
/// Execute commands specified in the command line.
/// </summary>
/// <param name="CommandsToExecute"></param>
/// <param name="Commands"></param>
private static void Execute ( List < CommandInfo > CommandsToExecute , CaselessDictionary < 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 ) ;
}
else
{
BuildCommand Command = ( BuildCommand ) Activator . CreateInstance ( CommandType ) ;
Command . Params = CommandInfo . Arguments . ToArray ( ) ;
Command . Execute ( ) ;
// 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 ( ) ;
}
}
Log . TraceInformation ( "Script execution successful, exiting." ) ;
}
#endregion
#region Help
/// <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 , CaselessDictionary < 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 ( )
{
CommandUtils . LogHelp ( typeof ( Automation ) ) ;
}
/// <summary>
/// List all available commands.
/// </summary>
/// <param name="Commands">All vailable commands.</param>
private static void ListAvailableCommands ( CaselessDictionary < Type > Commands )
{
string Message = Environment . NewLine ;
Message + = "Available commands:" + Environment . NewLine ;
foreach ( var AvailableCommand in Commands )
{
Message + = String . Format ( " {0}{1}" , AvailableCommand . Key , Environment . NewLine ) ;
}
2015-07-23 14:51:46 -04:00
CommandUtils . LogConsole ( Message ) ;
2014-03-14 14:13:41 -04:00
}
#endregion
#region Properties
/// <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
{
if ( ! bShouldKillProcesses . HasValue )
{
throw new AutomationException ( "Trying to access ShouldKillProcesses property before it was initialized." ) ;
}
return ( bool ) bShouldKillProcesses ;
}
private set
{
bShouldKillProcesses = value ;
}
}
private static bool? bShouldKillProcesses ;
#endregion
}
}