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

317 lines
12 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using Microsoft.Win32;
using System.Diagnostics;
using EpicGames.Core;
using System.Text.RegularExpressions;
using UnrealBuildBase;
using Microsoft.Extensions.Logging;
using UnrealBuildTool;
namespace AutomationTool
{
/// <summary>
/// Defines the environment variable names that will be used to setup the environment.
/// </summary>
public static class EnvVarNames
{
// Command Environment
public const string LocalRoot = "uebp_LOCAL_ROOT";
public const string LogFolder = "uebp_LogFolder";
public const string FinalLogFolder = "uebp_FinalLogFolder";
public const string CSVFile = "uebp_CSVFile";
public const string EngineSavedFolder = "uebp_EngineSavedFolder";
public const string MacMallocNanoZone = "MallocNanoZone";
public const string DisableStartupMutex = "uebp_UATMutexNoWait";
public const string IsChildInstance = "uebp_UATChildInstance";
// Perforce Environment
public const string P4Port = "uebp_PORT";
public const string ClientRoot = "uebp_CLIENT_ROOT";
public const string Changelist = "uebp_CL";
public const string CodeChangelist = "uebp_CodeCL";
public const string User = "uebp_USER";
public const string Client = "uebp_CLIENT";
public const string BuildRootP4 = "uebp_BuildRoot_P4";
public const string BuildRootEscaped = "uebp_BuildRoot_Escaped";
public const string P4Password = "uebp_PASS";
}
/// <summary>
/// Environment to allow access to commonly used environment variables.
/// </summary>
public class CommandEnvironment
{
static ILogger Logger => Log.Logger;
/// <summary>
/// Path to a file we know to always exist under the Unreal root directory.
/// </summary>
public static readonly string KnownFileRelativeToRoot = @"Engine/Config/BaseEngine.ini";
public string LocalRoot { get; protected set; }
public string EngineSavedFolder { get; protected set; }
public string LogFolder { get; protected set; }
public string FinalLogFolder { get; protected set; }
public string CSVFile { get; protected set; }
public string RobocopyExe { get; protected set; }
public string MountExe { get; protected set; }
public string CmdExe { get; protected set; }
public string AutomationToolDll { get; protected set; }
public string TimestampAsString { get; protected set; }
public bool HasCapabilityToCompile { get; protected set; }
public string FrameworkMsbuildPath { get; protected set; }
public string DotnetMsbuildPath { get; protected set; }
public string MallocNanoZone { get; protected set; }
public bool IsChildInstance { get; protected set; }
/// <summary>
/// Initializes the environment.
/// </summary>
internal CommandEnvironment()
{
// Get the path to AutomationTool.dll
AutomationToolDll = Assembly.GetEntryAssembly().GetOriginalLocation();
if (!CommandUtils.FileExists(AutomationToolDll))
{
throw new AutomationException("Could not find AutomationTool.dll. Reflection indicated it was here: {0}", AutomationToolDll);
}
// Find the root directory (containing the Engine folder)
LocalRoot = CommandUtils.GetEnvVar(EnvVarNames.LocalRoot);
if(String.IsNullOrEmpty(LocalRoot))
{
LocalRoot = CommandUtils.ConvertSeparators(PathSeparator.Slash, Unreal.RootDirectory.FullName);
CommandUtils.ConditionallySetEnvVar(EnvVarNames.LocalRoot, LocalRoot);
}
string SavedPath = CommandUtils.GetEnvVar(EnvVarNames.EngineSavedFolder);
if (String.IsNullOrEmpty(SavedPath))
{
SavedPath = CommandUtils.CombinePaths(PathSeparator.Slash, LocalRoot, "Engine", "Programs", "AutomationTool", "Saved");
CommandUtils.SetEnvVar(EnvVarNames.EngineSavedFolder, SavedPath);
}
EngineSavedFolder = CommandUtils.GetEnvVar(EnvVarNames.EngineSavedFolder);
CSVFile = CommandUtils.GetEnvVar(EnvVarNames.CSVFile);
LogFolder = CommandUtils.GetEnvVar(EnvVarNames.LogFolder);
if (String.IsNullOrEmpty(LogFolder))
{
if (Unreal.IsEngineInstalled())
{
LogFolder = GetInstalledLogFolder();
}
else
{
LogFolder = CommandUtils.CombinePaths(PathSeparator.Slash, EngineSavedFolder, "Logs");
}
CommandUtils.SetEnvVar(EnvVarNames.LogFolder, LogFolder);
}
// clear the logfolder if we're the only running instance
if (ProcessSingleton.IsSoleInstance)
{
ClearLogFolder(LogFolder);
}
FinalLogFolder = CommandUtils.GetEnvVar(EnvVarNames.FinalLogFolder);
if(String.IsNullOrEmpty(FinalLogFolder))
{
FinalLogFolder = LogFolder;
CommandUtils.SetEnvVar(EnvVarNames.FinalLogFolder, FinalLogFolder);
}
RobocopyExe = GetSystemExePath("robocopy.exe");
MountExe = GetSystemExePath("mount.exe");
CmdExe = OperatingSystem.IsWindows() ? GetSystemExePath("cmd.exe") : "/bin/sh";
MallocNanoZone = "0";
CommandUtils.SetEnvVar(EnvVarNames.MacMallocNanoZone, MallocNanoZone);
int IsChildInstanceInt;
int.TryParse(CommandUtils.GetEnvVar(EnvVarNames.IsChildInstance, "0"), out IsChildInstanceInt);
IsChildInstance = (IsChildInstanceInt != 0);
if (IsChildInstance)
{
Logger.LogInformation("AutomationTool is running as a child instance ({Name}={Value})", EnvVarNames.IsChildInstance, IsChildInstanceInt.ToString());
}
// Setup the timestamp string
DateTime LocalTime = DateTime.Now;
string TimeStamp = LocalTime.Year + "-"
+ LocalTime.Month.ToString("00") + "-"
+ LocalTime.Day.ToString("00") + "_"
+ LocalTime.Hour.ToString("00") + "."
+ LocalTime.Minute.ToString("00") + "."
+ LocalTime.Second.ToString("00");
TimestampAsString = TimeStamp;
SetupBuildEnvironment();
LogSettings();
}
/// <summary>
/// Returns the path to an executable in the System Directory.
/// To help support running 32-bit assemblies on a 64-bit operating system, if the executable
/// can't be found in System32, we also search Sysnative.
/// </summary>
/// <param name="ExeName">The name of the executable to find</param>
/// <returns>The path to the executable within the system folder</returns>
string GetSystemExePath(string ExeName)
{
var Result = CommandUtils.CombinePaths(Environment.SystemDirectory, ExeName);
if (!CommandUtils.FileExists(Result))
{
// Use Regex.Replace so we can do a case-insensitive replacement of System32
var SysNativeDirectory = Regex.Replace(Environment.SystemDirectory, "System32", "Sysnative", RegexOptions.IgnoreCase);
var SysNativeExe = CommandUtils.CombinePaths(SysNativeDirectory, ExeName);
if (CommandUtils.FileExists(SysNativeExe))
{
Result = SysNativeExe;
}
}
return Result;
}
void LogSettings()
{
Logger.LogDebug("Command Environment settings:");
Logger.LogDebug("CmdExe={CmdExe}", CmdExe);
Logger.LogDebug("EngineSavedFolder={EngineSavedFolder}", EngineSavedFolder);
Logger.LogDebug("HasCapabilityToCompile={HasCapabilityToCompile}", HasCapabilityToCompile);
Logger.LogDebug("LocalRoot={LocalRoot}", LocalRoot);
Logger.LogDebug("LogFolder={LogFolder}", LogFolder);
Logger.LogDebug("MountExe={MountExe}", MountExe);
Logger.LogDebug("FrameworkMsbuildExe={FrameworkMsbuildPath}", FrameworkMsbuildPath);
Logger.LogDebug("DotnetMsbuildExe={DotnetMsbuildPath}", DotnetMsbuildPath);
Logger.LogDebug("RobocopyExe={RobocopyExe}", RobocopyExe);
Logger.LogDebug("TimestampAsString={TimestampAsString}", TimestampAsString);
Logger.LogDebug("AutomationToolDll={AutomationToolDll}", AutomationToolDll);
}
/// <summary>
/// Initializes build environemnt: finds the path to msbuild.exe
/// </summary>
void SetupBuildEnvironment()
{
// Assume we have the capability co compile.
HasCapabilityToCompile = true;
if (BuildHostPlatform.Current.IsRunningOnWine())
{
// Just set an empty path as we currently compile .NET Framework/Core dependencies outside Wine
FrameworkMsbuildPath = "";
return;
}
try
{
FrameworkMsbuildPath = HostPlatform.Current.GetFrameworkMsbuildExe();
}
catch (Exception Ex)
{
Log.WriteLine(LogEventType.Log, Ex.Message);
Log.WriteLine(LogEventType.Log, "Assuming no compilation capability for NET Framework projects.");
HasCapabilityToCompile = false;
FrameworkMsbuildPath = "";
}
Logger.LogDebug("CompilationEvironment.HasCapabilityToCompile={HasCapabilityToCompile}", HasCapabilityToCompile);
Logger.LogDebug("CompilationEvironment.FrameworkMsbuildExe={FrameworkMsbuildPath}", FrameworkMsbuildPath);
DotnetMsbuildPath = Unreal.DotnetPath.FullName;
Logger.LogDebug("CompilationEvironment.DotnetMsbuildExe={DotnetMsbuildPath}", DotnetMsbuildPath);
}
/// <summary>
/// Finds the root path of the branch by looking in progressively higher folder locations until a file known to exist in the branch is found.
/// </summary>
/// <param name="UATLocation">Location of the currently executing assembly.</param>
/// <returns></returns>
private static string RootFromUATLocation(string UATLocation)
{
// pick a known file in the depot and try to build a relative path to it. This will tell us where the root path to the branch is.
var KnownFilePathFromRoot = CommandEnvironment.KnownFileRelativeToRoot;
var CurrentPath = CommandUtils.ConvertSeparators(PathSeparator.Slash, Path.GetDirectoryName(UATLocation));
var UATPathRoot = CommandUtils.CombinePaths(Path.GetPathRoot(CurrentPath), KnownFilePathFromRoot);
// walk parent dirs until we find the file or hit the path root, which means we aren't in a known location.
while (true)
{
var PathToCheck = Path.GetFullPath(CommandUtils.CombinePaths(CurrentPath, KnownFilePathFromRoot));
Logger.LogDebug("Checking for {PathToCheck}", PathToCheck);
if (!File.Exists(PathToCheck))
{
var LastSeparatorIndex = CurrentPath.LastIndexOf('/');
if (String.Compare(PathToCheck, UATPathRoot, true) == 0 || LastSeparatorIndex < 0)
{
throw new AutomationException("{0} does not appear to exist at a valid location with the branch, so the local root cannot be determined.", UATLocation);
}
// Step back one directory
CurrentPath = CurrentPath.Substring(0, LastSeparatorIndex);
}
else
{
return CurrentPath;
}
}
}
/// <summary>
/// Gets the log folder used when running from installed build.
/// </summary>
/// <returns></returns>
private string GetInstalledLogFolder()
{
var LocalRootPath = CommandUtils.GetEnvVar(EnvVarNames.LocalRoot);
var LocalRootEscaped = CommandUtils.ConvertSeparators(PathSeparator.Slash, LocalRootPath.Replace(":", ""));
if (LocalRootEscaped[LocalRootEscaped.Length - 1] == '/')
{
LocalRootEscaped = LocalRootEscaped.Substring(0, LocalRootEscaped.Length - 1);
}
LocalRootEscaped = CommandUtils.EscapePath(LocalRootEscaped);
return CommandUtils.CombinePaths(PathSeparator.Slash, HostPlatform.Current.LocalBuildsLogFolder, LocalRootEscaped);
}
/// <summary>
/// Clears out the contents of the given log folder
/// </summary>
/// <param name="LogFolder">The log folder to clear</param>
private static void ClearLogFolder(string LogFolder)
{
try
{
if (Directory.Exists(LogFolder))
{
CommandUtils.DeleteDirectoryContents(LogFolder);
// Since the directory existed and might have been empty, we need to make sure
// we can actually write to it, so create and delete a temporary file.
var RandomFilename = Path.GetRandomFileName();
var RandomFilePath = CommandUtils.CombinePaths(LogFolder, RandomFilename);
File.WriteAllText(RandomFilePath, RandomFilename);
File.Delete(RandomFilePath);
}
else
{
Directory.CreateDirectory(LogFolder);
}
}
catch(Exception Ex)
{
throw new AutomationException(Ex, "Unable to clear log folder ({0})", LogFolder);
}
}
}
}