Files
UnrealEngine/Engine/Source/Programs/AutomationTool/AutomationUtils/CommandletUtils.cs
2025-05-18 13:04:45 +08:00

592 lines
25 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.Threading;
using UnrealBuildTool;
using EpicGames.Core;
using UnrealBuildBase;
using Microsoft.Extensions.Logging;
using static AutomationTool.CommandUtils;
namespace AutomationTool
{
/// <summary>
/// Exception thrown when the execution of a commandlet fails
/// </summary>
public class CommandletException : AutomationException
{
/// <summary>
/// The log file output by this commandlet
/// </summary>
public string LogFileName;
/// <summary>
/// The exit code
/// </summary>
public int ErrorNumber;
/// <summary>
/// Constructor
/// </summary>
/// <param name="LogFilename">File containing the error log</param>
/// <param name="ErrorNumber">The exit code from the commandlet</param>
/// <param name="Format">Formatting string for the message</param>
/// <param name="Args">Arguments for the formatting string</param>
public CommandletException(string LogFilename, int ErrorNumber, string Format, params object[] Args)
: base(Format,Args)
{
this.LogFileName = LogFilename;
this.ErrorNumber = ErrorNumber;
}
}
public partial class CommandUtils
{
/// <summary>
/// Runs Cook commandlet.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Maps">List of maps to cook, can be null in which case -MapIniSection=AllMaps is used.</param>
/// <param name="Dirs">List of directories to cook, can be null</param>
/// <param name="InternationalizationPreset">The name of a prebuilt set of internationalization data to be included.</param>
/// <param name="CulturesToCook">List of culture names whose localized assets should be cooked, can be null (implying defaults should be used).</param>
/// <param name="TargetPlatform">Target platform.</param>
/// <param name="Parameters">List of additional parameters.</param>
public static void CookCommandlet(FileReference ProjectFile, string UnrealExe = null, string[] Maps = null, string[] Dirs = null, string InternationalizationPreset = "", string[] CulturesToCook = null, string TargetPlatform = "Windows", string Parameters = "-Unversioned")
{
string CommandletArguments = "";
if (IsNullOrEmpty(Maps))
{
// MapsToCook = "-MapIniSection=AllMaps";
}
else
{
string MapsToCookArg = "-Map=" + CombineCommandletParams(Maps).Trim();
CommandletArguments += (CommandletArguments.Length > 0 ? " " : "") + MapsToCookArg;
}
if (!IsNullOrEmpty(Dirs))
{
foreach(string Dir in Dirs)
{
CommandletArguments += (CommandletArguments.Length > 0 ? " " : "") + String.Format("-CookDir={0}", CommandUtils.MakePathSafeToUseWithCommandLine(Dir));
}
}
if (!String.IsNullOrEmpty(InternationalizationPreset))
{
CommandletArguments += (CommandletArguments.Length > 0 ? " " : "") + InternationalizationPreset;
}
if (!IsNullOrEmpty(CulturesToCook))
{
string CulturesToCookArg = "-CookCultures=" + CombineCommandletParams(CulturesToCook).Trim();
CommandletArguments += (CommandletArguments.Length > 0 ? " " : "") + CulturesToCookArg;
}
RunCommandlet(ProjectFile, UnrealExe, "Cook", String.Format("{0} -TargetPlatform={1} {2}", CommandletArguments, TargetPlatform, Parameters));
}
/// <summary>
/// Runs DDC commandlet.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Maps">List of maps to cook, can be null in which case -MapIniSection=AllMaps is used.</param>
/// <param name="TargetPlatform">Target platform.</param>
/// <param name="Parameters">List of additional parameters.</param>
public static void DDCCommandlet(FileReference ProjectFile, string UnrealExe = null, string[] Maps = null, string TargetPlatform = "Windows", string Parameters = "")
{
string MapsToCook = "";
if (!IsNullOrEmpty(Maps))
{
MapsToCook = "-Map=" + CombineCommandletParams(Maps).Trim();
}
RunCommandlet(ProjectFile, UnrealExe, "DerivedDataCache", String.Format("{0} -TargetPlatform={1} {2}", MapsToCook, TargetPlatform, Parameters));
}
/// <summary>
/// Runs RebuildLightMaps commandlet.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Maps">List of maps to rebuild light maps for. Can be null in which case -MapIniSection=AllMaps is used.</param>
/// <param name="Parameters">List of additional parameters.</param>
public static void RebuildLightMapsCommandlet(FileReference ProjectFile, string UnrealExe = null, string[] Maps = null, string Parameters = "")
{
string MapsToRebuildLighting = "";
if (!IsNullOrEmpty(Maps))
{
MapsToRebuildLighting = "-Map=" + CombineCommandletParams(Maps).Trim();
}
RunCommandlet(ProjectFile, UnrealExe, "ResavePackages", String.Format("-buildtexturestreaming -buildlighting -MapsOnly -ProjectOnly -AllowCommandletRendering -SkipSkinVerify {0} {1}", MapsToRebuildLighting, Parameters));
}
public static void RebuildHLODCommandlet(FileReference ProjectName, string UnrealExe = null, string[] Maps = null, string Parameters = "")
{
RebuildHLODCommandlet(ProjectName, out string LogFile, UnrealExe, Maps, Parameters);
}
public static void RebuildHLODCommandlet(FileReference ProjectFile, out string DestLogFile, string UnrealExe = null, string[] Maps = null, string Parameters = "")
{
string MapsToRebuildHLODs = "";
if (!IsNullOrEmpty(Maps))
{
MapsToRebuildHLODs = "-Map=" + CombineCommandletParams(Maps).Trim();
}
RunCommandlet(ProjectFile, UnrealExe, "ResavePackages", String.Format("-BuildHLOD -ProjectOnly -AllowCommandletRendering -SkipSkinVerify {0} {1}", MapsToRebuildHLODs, Parameters), out DestLogFile);
}
/// <summary>
/// Runs RebuildLightMaps commandlet.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Maps">List of maps to rebuild light maps for. Can be null in which case -MapIniSection=AllMaps is used.</param>
/// <param name="Parameters">List of additional parameters.</param>
public static void ResavePackagesCommandlet(FileReference ProjectFile, string UnrealExe = null, string[] Maps = null, string Parameters = "")
{
string MapsToRebuildLighting = "";
if (!IsNullOrEmpty(Maps))
{
MapsToRebuildLighting = "-Map=" + CombineCommandletParams(Maps).Trim();
}
RunCommandlet(ProjectFile, UnrealExe, "ResavePackages", String.Format((!String.IsNullOrEmpty(MapsToRebuildLighting) ? "-MapsOnly" : "") + "-ProjectOnly {0} {1}", MapsToRebuildLighting, Parameters));
}
/// <summary>
/// Runs GenerateDistillFileSets commandlet.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="ManifestFile"></param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Maps">List of maps to cook, can be null in which case -MapIniSection=AllMaps is used.</param>
/// <param name="Parameters">List of additional parameters.</param>
public static List<FileReference> GenerateDistillFileSetsCommandlet(FileReference ProjectFile, string ManifestFile, string UnrealExe = null, string[] Maps = null, string Parameters = "")
{
string MapsToCook = "";
if (!IsNullOrEmpty(Maps))
{
MapsToCook = CombineCommandletParams(Maps, " ").Trim();
}
var Dir = Path.GetDirectoryName(ManifestFile);
var Filename = Path.GetFileName(ManifestFile);
if (String.IsNullOrEmpty(Dir) || String.IsNullOrEmpty(Filename))
{
throw new AutomationException("GenerateDistillFileSets should have a full path and file for {0}.", ManifestFile);
}
CreateDirectory(Dir);
if (FileExists_NoExceptions(ManifestFile))
{
DeleteFile(ManifestFile);
}
RunCommandlet(ProjectFile, UnrealExe, "GenerateDistillFileSets", String.Format("{0} -OutputFolder={1} -Output={2} {3}", MapsToCook, CommandUtils.MakePathSafeToUseWithCommandLine(Dir), Filename, Parameters));
if (!FileExists_NoExceptions(ManifestFile))
{
throw new AutomationException("GenerateDistillFileSets did not produce a manifest for {0}.", ProjectFile);
}
var Lines = new List<string>(ReadAllLines(ManifestFile));
if (Lines.Count < 1)
{
throw new AutomationException("GenerateDistillFileSets for {0} did not produce any files.", ProjectFile);
}
var Result = new List<FileReference>();
foreach (var ThisFile in Lines)
{
var TestFile = CombinePaths(ThisFile);
if (!FileExists_NoExceptions(TestFile))
{
throw new AutomationException("GenerateDistillFileSets produced {0}, but {1} doesn't exist.", ThisFile, TestFile);
}
// we correct the case here
var TestFileInfo = new FileInfo(TestFile);
var FinalFile = new FileReference(TestFileInfo);
if (!FileReference.Exists(FinalFile))
{
throw new AutomationException("GenerateDistillFileSets produced {0}, but {1} doesn't exist.", ThisFile, FinalFile);
}
Result.Add(FinalFile);
}
return Result;
}
/// <summary>
/// Runs UpdateGameProject commandlet.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Parameters">List of additional parameters.</param>
public static void UpdateGameProjectCommandlet(FileReference ProjectFile, string UnrealExe = null, string Parameters = "")
{
RunCommandlet(ProjectFile, UnrealExe, "UpdateGameProject", Parameters);
}
/// <summary>
/// Runs a commandlet using Engine/Binaries/Win64/UnrealEditor-Cmd.exe.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Commandlet">Commandlet name.</param>
/// <param name="Parameters">Command line parameters (without -run=)</param>
/// <param name="ErrorLevel">The minimum exit code, which is treated as an error.</param>
public static void RunCommandlet(FileReference ProjectFile, string UnrealExe, string Commandlet, string Parameters = null, uint ErrorLevel = 1)
{
string LogFile;
RunCommandlet(ProjectFile, UnrealExe, Commandlet, Parameters, out LogFile, ErrorLevel);
}
/// <summary>
/// Runs a commandlet using Engine/Binaries/Win64/UnrealEditor-Cmd.exe.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Commandlet">Commandlet name.</param>
/// <param name="Parameters">Command line parameters (without -run=)</param>
/// <param name="ErrorLevel">The minimum exit code, which is treated as an error.</param>
public static void RunCommandlet(FileReference ProjectFile, string UnrealExe, string Commandlet, string Parameters, int ErrorLevel)
{
string LogFile;
RunCommandlet(ProjectFile, UnrealExe, Commandlet, Parameters, out LogFile, (uint)ErrorLevel);
}
/// <summary>
/// Runs a commandlet using Engine/Binaries/Win64/UnrealEditor-Cmd.exe.
/// </summary>
/// <param name="ProjectFile">Project file.</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Commandlet">Commandlet name.</param>
/// <param name="Parameters">Command line parameters (without -run=)</param>
/// <param name="DestLogFile">Log file after completion</param>
/// <param name="ErrorLevel">The minimum exit code, which is treated as an error.</param>
public static void RunCommandlet(FileReference ProjectFile, string UnrealExe, string Commandlet, string Parameters, out string DestLogFile, uint ErrorLevel = 1)
{
string LocalLogFile;
IProcessResult RunResult;
DateTime StartTime = DateTime.UtcNow;
StartRunCommandlet(ProjectFile, UnrealExe, Commandlet, Parameters, ERunOptions.Default, out LocalLogFile, out RunResult);
FinishRunCommandlet(ProjectFile, Commandlet, StartTime, RunResult, LocalLogFile, out DestLogFile, ErrorLevel);
}
/// <summary>
/// Runs a commandlet using Engine/Binaries/Win64/UnrealEditor-Cmd.exe.
/// </summary>
/// <param name="ProjectFile">Project file</param>
/// <param name="UnrealExe">The name of the Unreal Editor executable to use.</param>
/// <param name="Commandlet">Commandlet name.</param>
/// <param name="Parameters">Command line parameters (without -run=)</param>
/// <param name="DestLogFile">Log file after completion</param>
/// <param name="ErrorLevel">The minimum exit code, which is treated as an error.</param>
public static void RunCommandlet(FileReference ProjectFile, string UnrealExe, string Commandlet, string Parameters, out string DestLogFile, int ErrorLevel)
{
RunCommandlet(ProjectFile, UnrealExe, Commandlet, Parameters, out DestLogFile, (uint)ErrorLevel);
}
public static void StartRunCommandlet(FileReference ProjectFile, string UnrealExe, string Commandlet, string Parameters, ERunOptions RunOptions, out string LocalLogFile, out IProcessResult RunResult, ProcessResult.SpewFilterCallbackType SpewFilterCallback=null)
{
Logger.LogInformation("Running UnrealEditor {Commandlet} for project {ProjectName}", Commandlet, ProjectFile);
var CWD = Path.GetDirectoryName(UnrealExe);
string EditorExe = UnrealExe;
if (EditorExe == null)
{
EditorExe = ProjectUtils.GetEditorForProject(ProjectFile).FullName;
}
if (String.IsNullOrEmpty(CWD))
{
EditorExe = HostPlatform.Current.GetUnrealExePath(EditorExe);
CWD = CombinePaths(CmdEnv.LocalRoot, HostPlatform.Current.RelativeBinariesFolder);
}
PushDir(CWD);
LocalLogFile = LogUtils.GetUniqueLogName(CombinePaths(CmdEnv.EngineSavedFolder, Commandlet));
Logger.LogInformation("Commandlet log file is {LocalLogFile}", LocalLogFile);
string Args = String.Format(
"{0} -run={1} {2} -abslog={3} -stdout -CrashForUAT -unattended -NoLogTimes {5}{4}",
(ProjectFile == null) ? "" : CommandUtils.MakePathSafeToUseWithCommandLine(ProjectFile.FullName),
Commandlet,
String.IsNullOrEmpty(Parameters) ? "" : Parameters,
CommandUtils.MakePathSafeToUseWithCommandLine(LocalLogFile),
IsBuildMachine ? "-buildmachine" : "",
(GlobalCommandLine.Verbose || GlobalCommandLine.AllowStdOutLogVerbosity) ? "-AllowStdOutLogVerbosity " : ""
);
if (GlobalCommandLine.UTF8Output)
{
RunOptions |= ERunOptions.UTF8Output;
}
RunResult = Run(EditorExe, Args, Options: RunOptions, Identifier: Commandlet, SpewFilterCallback: SpewFilterCallback);
PopDir();
}
public static void FinishRunCommandlet(FileReference ProjectFile, string Commandlet, DateTime StartTime, IProcessResult RunResult, string LocalLogFile, out string DestLogFile, uint ErrorLevel = 1)
{
// If we're running on a Windows build machine, copy any crash dumps into the log folder
if(HostPlatform.Current.HostEditorPlatform == UnrealTargetPlatform.Win64 && IsBuildMachine)
{
DirectoryInfo CrashesDir = new DirectoryInfo(GetCrashesDirectory(ProjectFile).FullName);
if(CrashesDir.Exists)
{
foreach(DirectoryInfo CrashDir in CrashesDir.EnumerateDirectories())
{
if(CrashDir.LastWriteTimeUtc > StartTime)
{
DirectoryInfo OutputCrashesDir = new DirectoryInfo(Path.Combine(CmdEnv.LogFolder, "Crashes", CrashDir.Name));
try
{
Logger.LogInformation("Copying crash data to {Arg0}...", OutputCrashesDir.FullName);
OutputCrashesDir.Create();
foreach(FileInfo CrashFile in CrashDir.EnumerateFiles())
{
CrashFile.CopyTo(Path.Combine(OutputCrashesDir.FullName, CrashFile.Name));
}
}
catch(Exception Ex)
{
Logger.LogWarning("Unable to copy crash data; skipping. See log for exception details.");
Logger.LogDebug("{Text}", EpicGames.Core.ExceptionUtils.FormatExceptionDetails(Ex));
}
}
}
}
}
// If we're running on a Mac, dump all the *.crash files that were generated while the editor was running.
if(HostPlatform.Current.HostEditorPlatform == UnrealTargetPlatform.Mac)
{
// If the exit code indicates the main process crashed, introduce a small delay because the crash report is written asynchronously.
// If we exited normally, still check without waiting in case SCW or some other child process crashed.
if(RunResult.ExitCode > 128)
{
Logger.LogInformation("Pausing before checking for crash logs...");
Thread.Sleep(10 * 1000);
}
// Create a list of directories containing crash logs, and add the system log folder
List<string> CrashDirs = new List<string>();
CrashDirs.Add("/Library/Logs/DiagnosticReports");
// Add the user's log directory too
string HomeDir = Environment.GetEnvironmentVariable("HOME");
if(!String.IsNullOrEmpty(HomeDir))
{
CrashDirs.Add(Path.Combine(HomeDir, "Library/Logs/DiagnosticReports"));
}
// Check each directory for crash logs
List<FileInfo> CrashFileInfos = new List<FileInfo>();
foreach(string CrashDir in CrashDirs)
{
try
{
DirectoryInfo CrashDirInfo = new DirectoryInfo(CrashDir);
if (CrashDirInfo.Exists)
{
CrashFileInfos.AddRange(CrashDirInfo.EnumerateFiles("*.crash", SearchOption.TopDirectoryOnly).Where(x => x.LastWriteTimeUtc >= StartTime));
}
}
catch (UnauthorizedAccessException)
{
// Not all account types can access /Library/Logs/DiagnosticReports
}
}
// Dump them all to the log
foreach(FileInfo CrashFileInfo in CrashFileInfos)
{
// snmpd seems to often crash (suspect due to it being starved of CPU cycles during cooks)
// also ignore spotlight crash with the excel plugin
if(!CrashFileInfo.Name.StartsWith("snmpd_") && !CrashFileInfo.Name.StartsWith("mdworker32_") && !CrashFileInfo.Name.StartsWith("Dock_"))
{
Logger.LogInformation("Found crash log - {Arg0}", CrashFileInfo.FullName);
try
{
string[] Lines = File.ReadAllLines(CrashFileInfo.FullName);
foreach(string Line in Lines)
{
Logger.LogInformation("Crash: {Line}", Line);
}
}
catch(Exception Ex)
{
Logger.LogWarning("Failed to read file ({Arg0})", Ex.Message);
}
}
}
}
// Copy the local commandlet log to the destination folder.
DestLogFile = LogUtils.GetUniqueLogName(CombinePaths(CmdEnv.LogFolder, Commandlet));
if (!CommandUtils.CopyFile_NoExceptions(LocalLogFile, DestLogFile))
{
Logger.LogWarning("Commandlet {Commandlet} failed to copy the local log file from {LocalLogFile} to {DestLogFile}. The log file will be lost.", Commandlet, LocalLogFile, DestLogFile);
}
string ProjectStatsDirectory = CombinePaths((ProjectFile == null)? CombinePaths(CmdEnv.LocalRoot, "Engine") : Path.GetDirectoryName(ProjectFile.FullName), "Saved", "Stats");
if (Directory.Exists(ProjectStatsDirectory))
{
string DestCookerStats = CmdEnv.LogFolder;
foreach (var StatsFile in Directory.EnumerateFiles(ProjectStatsDirectory, "*.csv"))
{
if (!CommandUtils.CopyFile_NoExceptions(StatsFile, CombinePaths(DestCookerStats, Path.GetFileName(StatsFile))))
{
Logger.LogWarning("Commandlet {Commandlet} failed to copy the local log file from {StatsFile} to {Arg2}. The log file will be lost.", Commandlet, StatsFile, CombinePaths(DestCookerStats, Path.GetFileName(StatsFile)));
}
}
}
// else
// {
// CommandUtils.LogWarning("Failed to find directory {0} will not save stats", ProjectStatsDirectory);
// }
// Whether it was copied correctly or not, delete the local log as it was only a temporary file.
CommandUtils.DeleteFile_NoExceptions(LocalLogFile);
// Throw an exception if the execution failed. Draw attention to signal exit codes on Posix systems, rather than just printing the exit code
if (RunResult.ExitCode != 0 && (uint)RunResult.ExitCode >= ErrorLevel)
{
string ExitCodeDesc = "";
if(RunResult.ExitCode > 128 && RunResult.ExitCode < 128 + 32)
{
if(RunResult.ExitCode == 139)
{
ExitCodeDesc = " (segmentation fault)";
}
else
{
ExitCodeDesc = String.Format(" (signal {0})", RunResult.ExitCode - 128);
}
}
throw new CommandletException(DestLogFile, RunResult.ExitCode, "Editor terminated with exit code {0}{1} while running {2}{3}; see log {4}", RunResult.ExitCode, ExitCodeDesc, Commandlet, (ProjectFile == null)? "" : String.Format(" for {0}", ProjectFile), DestLogFile) { OutputFormat = AutomationExceptionOutputFormat.Minimal };
}
}
public static void FinishRunCommandlet(FileReference ProjectFile, string Commandlet, DateTime StartTime, IProcessResult RunResult, string LocalLogFile, out string DestLogFile, int ErrorLevel)
{
FinishRunCommandlet(ProjectFile, Commandlet, StartTime, RunResult, LocalLogFile, out DestLogFile, (uint)ErrorLevel);
}
/// <summary>
/// Returns the default path of the editor executable to use for running commandlets.
/// </summary>
/// <param name="BuildRoot">Root directory for the build</param>
/// <param name="HostPlatform">Platform to get the executable for</param>
/// <returns>Path to the editor executable</returns>
public static string GetEditorCommandletExe(string BuildRoot, UnrealBuildTool.UnrealTargetPlatform HostPlatform)
{
if (HostPlatform == UnrealBuildTool.UnrealTargetPlatform.Mac)
{
return CommandUtils.CombinePaths(BuildRoot, "Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor");
}
if (HostPlatform == UnrealBuildTool.UnrealTargetPlatform.Win64)
{
return CommandUtils.CombinePaths(BuildRoot, "Engine/Binaries/Win64/UnrealEditor-Cmd.exe");
}
if (HostPlatform == UnrealBuildTool.UnrealTargetPlatform.Linux)
{
return CommandUtils.CombinePaths(BuildRoot, "Engine/Binaries/Linux/UnrealEditor");
}
throw new AutomationException("EditorCommandlet is not supported for platform {0}", HostPlatform);
}
/// <summary>
/// Combines a list of parameters into a single commandline param separated with '+':
/// For example: Map1+Map2+Map3
/// </summary>
/// <param name="ParamValues">List of parameters (must not be empty)</param>
/// <param name="Separator"></param>
/// <returns>Combined param</returns>
public static string CombineCommandletParams(IEnumerable<string> ParamValues, string Separator = "+")
{
string CombinedParams = String.Empty;
if (ParamValues != null)
{
var Iter = ParamValues.GetEnumerator();
while (Iter.MoveNext())
{
if (!String.IsNullOrEmpty(Iter.Current))
{
CombinedParams += Iter.Current;
break;
}
}
while (Iter.MoveNext())
{
if (!String.IsNullOrEmpty(Iter.Current))
{
CombinedParams += Separator + Iter.Current;
}
}
}
return CombinedParams;
}
/// <summary>
/// Converts project name to FileReference used by other commandlet functions
/// </summary>
/// <param name="ProjectName">Project name.</param>
/// <returns>FileReference to project location</returns>
public static FileReference GetCommandletProjectFile(string ProjectName)
{
FileReference ProjectFullPath = null;
var OriginalProjectName = ProjectName;
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;
}
if (!FileExists_NoExceptions(ProjectFullPath.FullName))
{
throw new AutomationException("Could not find a project file {0}.", ProjectName);
}
}
return ProjectFullPath;
}
/// <summary>
/// Get the crashes directory for the given project file
/// </summary>
/// <param name="ProjectFullPath">Path to a project file</param>
/// <returns>DirectoryReference for the directory where crashes are stored for this project</returns>
public static DirectoryReference GetCrashesDirectory(FileReference ProjectFullPath)
{
DirectoryReference CrashesDir = DirectoryReference.Combine(DirectoryReference.FromFile(ProjectFullPath) ?? Unreal.EngineDirectory, "Saved", "Crashes");
return CrashesDir;
}
}
}