// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using EpicGames.Core; using UnrealBuildBase; using Microsoft.Extensions.Logging; using System.Diagnostics.CodeAnalysis; #nullable enable namespace AutomationTool { /// /// Base class for buildcommands. /// public abstract class BuildCommand : CommandUtils { /// /// Command line parameters for this command (empty by non-null by default) /// public string[] Params { get { return _commandLineParams; } set { _commandLineParams = value; } } private string[] _commandLineParams = new string[0]; /// /// Parses the command's Params list for a parameter and returns whether it is defined or not. /// /// Param to check for. /// True if param was found, false otherwise. public bool ParseParam(string Param) { return ParseParam(Params, Param); } /// /// Parses the command's Params list for a parameter and reads its value. /// Ex. ParseParamValue(Args, "map=") /// /// Param to read its value. /// /// /// Returns the value or Default if the parameter was not found. [return: NotNullIfNotNull("Default")] public string? ParseParamValue(string Param, string? Default = null, string? ObsoleteParam = null) { string ParamValue = ParseParamValue(Params, Param, null); if (ObsoleteParam != null) { string ObsoleteParamValue = ParseParamValue(Params, ObsoleteParam, null); if (ObsoleteParamValue != null) { if (ParamValue == null) { Logger.LogWarning("Param name \"{ObsoleteParam}\" is deprecated, use \"{Param}\" instead.", ObsoleteParam, Param); } else { Logger.LogWarning("Deprecated param name \"{ObsoleteParam}\" was ignored because \"{Param}\" was set.", ObsoleteParam, Param); } } } return ParamValue ?? Default; } /// /// Parses an argument. /// /// /// public string? ParseOptionalStringParam(string Param) { return ParseParamValue(Param, null); } /// /// Parses an argument. Throws an exception if the parameter is not specified. /// /// Name of the argument /// Value of the argument public string ParseRequiredStringParam(string Param) { string? value = ParseOptionalStringParam(Param); if(value == null) { throw new AutomationException("Missing -{0}=... parameter", Param); } return value; } /// /// Parses an file reference argument. /// /// Name of the argument /// Value of the argument public FileReference? ParseOptionalFileReferenceParam(string Param) { string? stringValue = ParseParamValue(Param); if(stringValue == null) { return null; } else { return new FileReference(stringValue); } } /// /// Parses an file reference argument. Throws an exception if the parameter is not specified. /// /// Name of the argument /// Value of the argument public FileReference ParseRequiredFileReferenceParam(string Param) { FileReference? value = ParseOptionalFileReferenceParam(Param); if(value == null) { throw new AutomationException("Missing -{0}=... parameter", Param); } return value; } /// /// Parses a directory reference argument. /// /// Name of the argument /// Value of the argument public DirectoryReference? ParseOptionalDirectoryReferenceParam(string Param) { string? stringValue = ParseOptionalStringParam(Param); if(stringValue == null) { return null; } else { return new DirectoryReference(stringValue); } } /// /// Parses a directory reference argument. Throws an exception if the parameter is not specified. /// /// Name of the argument /// Value of the argument public DirectoryReference ParseRequiredDirectoryReferenceParam(string Param) { DirectoryReference? value = ParseOptionalDirectoryReferenceParam(Param); if(value == null) { throw new AutomationException("Missing -{0}=... parameter", Param); } return value; } /// /// Parses an argument as an enum. /// /// Name of the parameter to read. /// Returns the value that was parsed. public Nullable ParseOptionalEnumParam(string Param) where T : struct { string? valueString = ParseParamValue(Param); if(valueString == null) { return null; } else { T Value; if(!Enum.TryParse(valueString, out Value)) { throw new AutomationException("'{0}' is not a valid value for {1}", valueString, typeof(T).Name); } return Value; } } /// /// Parses an argument as an enum. Throws an exception if the parameter is not specified. /// /// Name of the parameter to read. /// Returns the value that was parsed. public T ParseRequiredEnumParamEnum(string Param) where T : struct { Nullable value = ParseOptionalEnumParam(Param); if(!value.HasValue) { throw new AutomationException("Missing -{0}=... parameter", Param); } return value.Value; } /// /// Parses the argument list for any number of parameters. /// /// Param to read its value. /// Returns an array of values for this parameter (or an empty array if one was not found. public string[] ParseParamValues(string Param) { return ParseParamValues(Params, Param); } /// /// Parses the command's Params list for a parameter and reads its value. /// Ex. ParseParamValue(Args, "map=") /// /// Param to read its value. /// /// Returns the value or Default if the parameter was not found. public bool ParseParamBool(string Param, bool Default = false) { string boolValue = ParseParamValue(Params, Param, Default.ToString()); return bool.Parse(boolValue); } /// /// Parses the command's Params list for a parameter and reads its value. /// Ex. ParseParamValue(Args, "map=") /// /// Param to read its value. /// /// Returns the value or Default if the parameter was not found. public int ParseParamInt(string Param, int Default = 0) { string num = ParseParamValue(Params, Param, Default.ToString()); return int.Parse(num); } /// /// Parses the command's Params list for a parameter and reads its value. /// Ex. ParseParamValue(Args, "map=") /// /// Param to read its value. /// Returns the value or Default if the parameter was not found. public int? ParseParamNullableInt(string Param) { string value = ParseParamValue(Params, Param, null); if(value == null) { return null; } else { return int.Parse(value); } } /// /// Parses project name string into a FileReference. Can be "Game" or "Game.uproject" or "Path/To/Game.uproject" /// /// In project string to parse /// FileReference to uproject public FileReference? ParseProjectString(string originalProjectName) { FileReference? projectFullPath = null; if (string.IsNullOrEmpty(originalProjectName)) { return null; } string projectName = originalProjectName; projectName = projectName.Trim(new char[] { '\"' }); if (projectName.IndexOfAny(new char[] { '\\', '/' }) < 0) { projectName = CombinePaths(CmdEnv.LocalRoot, projectName, projectName + ".uproject"); } else if (!FileExists_NoExceptions(projectName)) { projectName = CombinePaths(CmdEnv.LocalRoot, projectName); } if (FileExists_NoExceptions(projectName)) { projectFullPath = new FileReference(projectName); } else { var Branch = new BranchInfo(); var GameProj = Branch.FindGame(originalProjectName); if (GameProj != null) { projectFullPath = GameProj.FilePath; } } return projectFullPath; } public FileReference? ParseProjectParam() { FileReference? projectFullPath = null; bool bForeign = ParseParam("foreign"); bool bForeignCode = ParseParam("foreigncode"); if (bForeign) { string destSample = ParseParamValue("DestSample", "CopiedHoverShip"); string dest = ParseParamValue("ForeignDest", CombinePaths(@"C:\testue\foreign\", destSample + "_ _Dir")); projectFullPath = new FileReference(CombinePaths(dest, destSample + ".uproject")); } else if (bForeignCode) { string destSample = ParseParamValue("DestSample", "PlatformerGame"); string dest = ParseParamValue("ForeignDest", CombinePaths(@"C:\testue\foreign\", destSample + "_ _Dir")); projectFullPath = new FileReference(CombinePaths(dest, destSample + ".uproject")); } else { string originalProjectName = ParseParamValue("project", ""); if (string.IsNullOrEmpty(originalProjectName)) { return null; } projectFullPath = ParseProjectString(originalProjectName); if (projectFullPath == null || !FileExists_NoExceptions(projectFullPath.FullName)) { throw new AutomationException("Could not find a project file {0}.", originalProjectName); } } return projectFullPath; } /// /// Checks that all of the required params are present, throws an exception if not /// /// public void CheckParamsArePresent(params string[] args) { List missingParams = new List(); foreach (string arg in args) { if (ParseParamValue(arg, null) == null) { missingParams.Add(arg); } } if (missingParams.Count > 0) { throw new AutomationException("Params {0} are missing but required. Required params are {1}", string.Join(",", missingParams), string.Join(",", args)); } } /// /// Build command entry point. Throws AutomationExceptions on failure. /// public virtual void ExecuteBuild() { throw new AutomationException("Either Execute() or ExecuteBuild() should be implemented for {0}", GetType().Name); } /// /// Command entry point. /// public virtual ExitCode Execute() { ExecuteBuild(); return ExitCode.Success; } /// /// Async command entry point. /// public virtual Task ExecuteAsync() { return Task.FromResult(Execute()); } /// /// Executes a new command as a child of another command. /// /// /// public static ExitCode Execute(BuildCommand ParentCommand) where T : BuildCommand, new() { T Command = new T(); if (ParentCommand != null) { Command.Params = ParentCommand.Params; } return Command.Execute(); } } }