// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Linq;
using System.Security.AccessControl;
using System.Xml;
using System.Text;
using System.Text.RegularExpressions;
namespace UnrealBuildTool
{
abstract class RemoteToolChain : UEToolChain
{
///
/// Common error codes reported by Remote Tool Chain and its actions.
///
public enum RemoteToolChainErrorCode
{
NoError = 0,
ServerNameNotSpecified = 1,
ServerNotResponding = 2,
MissingDeltaCopyInstall = 3,
MissingRemoteUserName = 4,
MissingSSHKey = 5,
SSHCommandFailed = 6,
};
protected readonly FileReference ProjectFile;
public RemoteToolChain(CppPlatform InCppPlatform, UnrealTargetPlatform InRemoteToolChainPlatform, FileReference InProjectFile)
: base(InCppPlatform)
{
RemoteToolChainPlatform = InRemoteToolChainPlatform;
ProjectFile = InProjectFile;
}
///
/// These two variables will be loaded from XML config file in XmlConfigLoader.Init()
///
[XmlConfigFile]
public static string RemoteServerName = "";
[XmlConfigFile]
public static string[] PotentialServerNames = new string[] { };
///
/// Save the specified port so that RemoteServerName is the machine address only
///
private static int RemoteServerPort = 22; // Default ssh port
///
/// Keep a list of remote files that are potentially copied from local to remote
///
private static Dictionary CachedRemoteFileItems = new Dictionary();
///
/// The base path (on the Mac) to the your particular development directory, where files will be copied to from the PC
///
public static string UserDevRootMacBase = "/UE4/Builds/";
///
/// The final path (on the Mac) to your particular development directory, where files will be copied to from the PC
///
public static string UserDevRootMac = "/UE4/Builds";
///
/// Whether or not to connect to UnrealRemoteTool using RPCUtility
///
[XmlConfigFile]
public static bool bUseRPCUtil = true;
///
/// The user has specified a deltacopy install path
///
private static string OverrideDeltaCopyInstallPath = null;
///
/// Path to rsync executable and parameters for your rsync utility
///
[XmlConfigFile]
public static string RSyncExe = "${ENGINE_ROOT}\\Engine\\Extras\\ThirdPartyNotUE\\DeltaCopy\\Binaries\\rsync.exe";
public static string ResolvedRSyncExe = null;
///
/// Path to rsync executable and parameters for your rsync utility
///
[XmlConfigFile]
public static string SSHExe = "${ENGINE_ROOT}\\Engine\\Extras\\ThirdPartyNotUE\\DeltaCopy\\Binaries\\ssh.exe";
public static string ResolvedSSHExe = null;
///
/// Instead of looking for RemoteToolChainPrivate.key in the usual places (Documents/Unreal Engine/UnrealBuildTool/SSHKeys, Engine/Build/SSHKeys), this private key will be used if set
///
[XmlConfigFile]
public static string SSHPrivateKeyOverridePath = "";
public static string ResolvedSSHPrivateKey = null;
///
/// The authentication used for Rsync (for the -e rsync flag)
///
[XmlConfigFile]
public static string RsyncAuthentication = "ssh -i '${CYGWIN_SSH_PRIVATE_KEY}'";
public static string ResolvedRsyncAuthentication = null;
///
/// The authentication used for SSH (probably similar to RsyncAuthentication)
///
[XmlConfigFile]
public static string SSHAuthentication = "-i '${CYGWIN_SSH_PRIVATE_KEY}'";
public static string ResolvedSSHAuthentication = null;
///
/// Username on the remote machine to connect to with RSync
///
[XmlConfigFile]
public static string RSyncUsername = "${CURRENT_USER}";
public static string ResolvedRSyncUsername = null;
// has the toolchain initialized remote execution yet? no need to do it multiple times
private static bool bHasBeenInitialized = false;
///
/// The directory that this local branch is in, without drive information (strip off X:\ from X:\UE4\iOS)
///
public static string BranchDirectory = Path.GetFullPath(".\\");
///
/// Substrings that indicate a line contains an error
///
protected static List ErrorMessageTokens;
///
/// The platform this toolchain is compiling for
///
protected UnrealTargetPlatform RemoteToolChainPlatform;
///
/// The average amound of memory a compile takes, used so that we don't compile too many things at once
///
public static int MemoryPerCompileMB = 1000;
static RemoteToolChain()
{
ErrorMessageTokens = new List();
ErrorMessageTokens.Add("ERROR ");
ErrorMessageTokens.Add("** BUILD FAILED **");
ErrorMessageTokens.Add("[BEROR]");
ErrorMessageTokens.Add("IPP ERROR");
ErrorMessageTokens.Add("System.Net.Sockets.SocketException");
BranchDirectory = BranchDirectory.Replace("Engine\\Binaries\\DotNET", "");
BranchDirectory = BranchDirectory.Replace("Engine\\Source\\", "");
}
private string ResolveString(string Input, bool bIsPath)
{
string Result = Input;
// these assume entire string is a path, and will do file operations on the whole string
if (bIsPath)
{
if (Result.Contains("${PROGRAM_FILES}"))
{
// first look in real ProgramFiles
string Temp = Result.Replace("${PROGRAM_FILES}", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles, Environment.SpecialFolderOption.DoNotVerify));
if (File.Exists(Temp) || Directory.Exists(Temp))
{
Result = Temp;
}
else
{
// fallback to ProgramFilesX86
Temp = Result.Replace("${PROGRAM_FILES}", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86, Environment.SpecialFolderOption.DoNotVerify));
if (File.Exists(Temp) || Directory.Exists(Temp))
{
Result = Temp;
}
}
}
if (Result.Contains("${ENGINE_ROOT}"))
{
string Temp = Result.Replace("${ENGINE_ROOT}", UnrealBuildTool.RootDirectory.FullName);
// get the best version
Result = LookForSpecialFile(Temp);
}
if (Result.Contains("${PROJECT_ROOT}"))
{
if (ProjectFile == null)
{
throw new BuildException("Configuration setting was using ${PROJECT_ROOT}, but there was no project specified");
}
string Temp = Result.Replace("${PROJECT_ROOT}", ProjectFile.Directory.FullName);
// get the best version
Result = LookForSpecialFile(Temp);
}
}
// non path variables
Result = Result.Replace("${CURRENT_USER}", Environment.UserName);
// needs a resolved key (which isn't required if user is using alternate authentication)
if (Result.Contains("${SSH_PRIVATE_KEY}") || Result.Contains("${CYGWIN_SSH_PRIVATE_KEY}"))
{
// if it needs the key, then make sure we have it!
if (ResolvedSSHPrivateKey != null)
{
Result = Result.Replace("${SSH_PRIVATE_KEY}", ResolvedSSHPrivateKey);
Result = Result.Replace("${CYGWIN_SSH_PRIVATE_KEY}", ConvertPathToCygwin(ResolvedSSHPrivateKey));
}
else
{
Result = null;
}
}
return Result;
}
private static string LookForSpecialFile(string InPath)
{
// look in special NotForLicensees dir first
string Special = Path.Combine(Path.GetDirectoryName(InPath), "NoRedist", Path.GetFileName(InPath));
if (File.Exists(Special) || Directory.Exists(Special))
{
return Special;
}
Special = Path.Combine(Path.GetDirectoryName(InPath), "NotForLicensees", Path.GetFileName(InPath));
if (File.Exists(Special) || Directory.Exists(Special))
{
return Special;
}
return InPath;
}
// Look for any build options in the engine config file.
public override void ParseProjectSettings()
{
base.ParseProjectSettings();
DirectoryReference EngineIniPath = ProjectFile != null ? ProjectFile.Directory : null;
if (EngineIniPath == null && UnrealBuildTool.GetRemoteIniPath() != null)
{
EngineIniPath = new DirectoryReference(UnrealBuildTool.GetRemoteIniPath());
}
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, EngineIniPath, UnrealTargetPlatform.IOS);
string ServerName = RemoteServerName;
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "RemoteServerName", out ServerName) && !String.IsNullOrEmpty(ServerName))
{
RemoteServerName = ServerName;
}
bool bUseRSync = false;
if (Ini.GetBool("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bUseRSync", out bUseRSync))
{
bUseRPCUtil = !bUseRSync;
string UserName = RSyncUsername;
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "RSyncUsername", out UserName) && !String.IsNullOrEmpty(UserName))
{
RSyncUsername = UserName;
}
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "DeltaCopyInstallPath", out OverrideDeltaCopyInstallPath))
{
if (!string.IsNullOrEmpty(OverrideDeltaCopyInstallPath))
{
SSHExe = Path.Combine(OverrideDeltaCopyInstallPath, Path.GetFileName(SSHExe));
RSyncExe = Path.Combine(OverrideDeltaCopyInstallPath, Path.GetFileName(RSyncExe));
}
}
string ConfigKeyPath;
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "SSHPrivateKeyOverridePath", out ConfigKeyPath))
{
if (File.Exists(ConfigKeyPath))
{
SSHPrivateKeyOverridePath = ConfigKeyPath;
}
}
}
}
// Gather a users root path from the remote server. Should only be called once.
public static void SetUserDevRootFromServer()
{
if (!bUseRPCUtil && BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// Only set relative to the users root when using rsync, for now
Hashtable Results = RPCUtilHelper.Command("/", "echo $HOME", null);
if (Results == null)
{
Log.TraceInformation("UserDevRoot Command failed to execute!");
}
else if (Results["CommandOutput"] != null)
{
// pass back the string
string HomeLocation = Results["CommandOutput"] as string;
UserDevRootMac = HomeLocation + UserDevRootMacBase;
}
}
else
{
UserDevRootMac = UserDevRootMacBase;
}
}
// Do any one-time, global initialization for the tool chain
static RemoteToolChainErrorCode InitializationErrorCode = RemoteToolChainErrorCode.NoError;
private RemoteToolChainErrorCode InitializeRemoteExecution(bool bFlushBuildDir)
{
if (bHasBeenInitialized)
{
return InitializationErrorCode;
}
// don't need to set up the remote environment if we're simply listing build folders.
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// If we don't care which machine we're going to build on, query and
// pick the one with the most free command slots available
if (RemoteServerName == "best_available")
{
int AvailableSlots = 0;
int Attempts = 0;
if (!ProjectFileGenerator.bGenerateProjectFiles)
{
Log.TraceInformation("Picking a random Mac builder...");
}
while (AvailableSlots < 2 && Attempts < 20)
{
RemoteServerName = PotentialServerNames.OrderBy(x => Guid.NewGuid()).FirstOrDefault();
// make sure it's ready to take commands
AvailableSlots = GetAvailableCommandSlotCount(RemoteServerName);
Attempts++;
}
// make sure it succeeded
if (AvailableSlots <= 1)
{
throw new BuildException("Failed to find a Mac available to take commands!");
}
else if (!ProjectFileGenerator.bGenerateProjectFiles)
{
Log.TraceInformation("Chose {0} after {1} attempts to find a Mac, with {2} slots", RemoteServerName, Attempts, AvailableSlots);
}
/*
* this does not work right, because it pushes a lot of tasks to machines that have substantially more slots than others
Log.TraceInformation("Picking the best available Mac builder...");
Int32 MostAvailableCount = Int32.MinValue;
foreach (string NextMacName in PotentialServerNames)
{
Int32 NextAvailableCount = GetAvailableCommandSlotCount(NextMacName);
if (NextAvailableCount > MostAvailableCount)
{
MostAvailableName = NextMacName;
MostAvailableCount = NextAvailableCount;
}
Log.TraceVerbose("... " + NextMacName + " has " + NextAvailableCount + " slots available");
}
Log.TraceVerbose("Picking the compile server with the most available command slots: " + MostAvailableName);
// Finally, assign the name of the Mac we're going to use
RemoteServerName = MostAvailableName;
*/
}
else if (!ProjectFileGenerator.bGenerateProjectFiles)
{
Log.TraceInformation("Picking the default remote server " + RemoteServerName);
}
// we need a server name!
if (string.IsNullOrEmpty(RemoteServerName))
{
Log.TraceError("Remote compiling requires a server name. Use the editor (Project Settings, IOS) to set up your remote compilation settings.");
return RemoteToolChainErrorCode.ServerNameNotSpecified;
}
// Split port out from RemoteServerName
String[] RemoteServerNameSplit = RemoteServerName.Split(':');
if(RemoteServerNameSplit.Length > 1)
{
if(RemoteServerNameSplit.Length != 2)
{
Log.TraceError("Remote compiling server name contains too many colons.");
return RemoteToolChainErrorCode.ServerNameNotSpecified;
}
RemoteServerName = RemoteServerNameSplit[0];
RemoteServerPort = Convert.ToInt32(RemoteServerNameSplit[1]);
}
if (!bUseRPCUtil)
{
// Verify the Delta Copy install path
ResolvedRSyncExe = ResolveString(RSyncExe, true);
ResolvedSSHExe = ResolveString(SSHExe, true);
if (!File.Exists(ResolvedRSyncExe) || !File.Exists(ResolvedSSHExe))
{
Log.TraceError("Remote compiling requires Delta Copy to be installed. Use the editor (Project Settings, IOS) to set up your remote compilation settings.");
return RemoteToolChainErrorCode.MissingDeltaCopyInstall;
}
// we need the RemoteServerName and the Username to find the private key
ResolvedRSyncUsername = ResolveString(RSyncUsername, false);
if (string.IsNullOrEmpty(ResolvedRSyncUsername))
{
Log.TraceError("Remote compiling requires a user name. Use the editor (Project Settings, IOS) to set up your remote compilation settings.");
return RemoteToolChainErrorCode.MissingRemoteUserName;
}
bool bFoundOverrideSSHPrivateKey = false;
// if the override path is set, just use it directly
if (!string.IsNullOrEmpty(SSHPrivateKeyOverridePath))
{
ResolvedSSHPrivateKey = ResolveString(SSHPrivateKeyOverridePath, true);
bFoundOverrideSSHPrivateKey = File.Exists(ResolvedSSHPrivateKey);
// make sure it exists
if (!bFoundOverrideSSHPrivateKey)
{
Log.TraceWarning("An SSHKey override was specified [" + SSHPrivateKeyOverridePath + "] but it doesn't exist. Looking elsewhere...");
}
}
if (!bFoundOverrideSSHPrivateKey)
{
// all the places to look for a key
List Locations = new List();
Locations.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Unreal Engine", "UnrealBuildTool"));
Locations.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Unreal Engine", "UnrealBuildTool"));
if (ProjectFile != null)
{
Locations.Add(Path.Combine(ProjectFile.Directory.FullName, "Build", "NotForLicensees"));
Locations.Add(Path.Combine(ProjectFile.Directory.FullName, "Build", "NoRedist"));
Locations.Add(Path.Combine(ProjectFile.Directory.FullName, "Build"));
}
Locations.Add(Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Build", "NotForLicensees"));
Locations.Add(Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Build", "NoRedist"));
Locations.Add(Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Build"));
// look for a key file
foreach (string Location in Locations)
{
string KeyPath = Path.Combine(Location, "SSHKeys", RemoteServerName, ResolvedRSyncUsername, "RemoteToolChainPrivate.key");
if (File.Exists(KeyPath))
{
ResolvedSSHPrivateKey = KeyPath;
bFoundOverrideSSHPrivateKey = true;
break;
}
}
}
// resolve the rest of the strings
ResolvedRsyncAuthentication = ResolveString(RsyncAuthentication, false) + " -p " + RemoteServerPort;
ResolvedSSHAuthentication = ResolveString(SSHAuthentication, false) + " -p " + RemoteServerPort;
}
// start up remote communication and record if it succeeds
InitializationErrorCode = (RemoteToolChainErrorCode)RPCUtilHelper.Initialize(RemoteServerName, bFlushBuildDir);
if (InitializationErrorCode != RemoteToolChainErrorCode.NoError && InitializationErrorCode != RemoteToolChainErrorCode.MissingSSHKey)
{
Log.TraceError("Failed to initialize a connection to the Remote Server {0}", RemoteServerName);
return InitializationErrorCode;
}
else if (InitializationErrorCode == RemoteToolChainErrorCode.MissingSSHKey)
{
// Allow the user to set up a key from here.
Process KeyProcess = new Process();
KeyProcess.StartInfo.WorkingDirectory = DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Build", "BatchFiles").FullName;
KeyProcess.StartInfo.FileName = "MakeAndInstallSSHKey.bat";
KeyProcess.StartInfo.Arguments = string.Format(
"\"{0}\" {1} \"{2}\" {3} {4} \"{5}\" \"{6}\" \"{7}\"",
ResolvedSSHExe,
RemoteServerPort,
ResolvedRSyncExe,
ResolvedRSyncUsername,
RemoteServerName,
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
ConvertPathToCygwin(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)),
UnrealBuildTool.EngineDirectory.FullName);
KeyProcess.Start();
KeyProcess.WaitForExit();
// make sure it succeeded if we want to re-init
if (KeyProcess.ExitCode == 0)
{
InitializationErrorCode = InitializeRemoteExecution(bFlushBuildDir);
}
}
}
else
{
RemoteServerName = Environment.MachineName;
// can't error in this case
}
bHasBeenInitialized = true;
return InitializationErrorCode;
}
protected override void AddPrerequisiteSourceFile(CppCompileEnvironment CompileEnvironment, FileItem SourceFile, List PrerequisiteItems)
{
base.AddPrerequisiteSourceFile(CompileEnvironment, SourceFile, PrerequisiteItems);
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) // Don't use remote features when compiling from a Mac
{
QueueFileForBatchUpload(SourceFile);
// @todo ubtmake: What if one of the prerequisite files has become missing since it was updated in our cache? (usually, because a coder eliminated the source file)
// -> Two CASES:
// 1) NOT WORKING: Non-unity file went away (SourceFile in this context). That seems like an existing old use case. Compile params or Response file should have changed?
// 2) WORKING: Indirect file went away (unity'd original source file or include). This would return a file that no longer exists and adds to the prerequiteitems list
List IncludedFileList = CompileEnvironment.Headers.FindAndCacheAllIncludedFiles(SourceFile, CompileEnvironment.IncludePaths, bOnlyCachedDependencies: CompileEnvironment.Headers.bUseUBTMakefiles);
if (IncludedFileList != null)
{
foreach (FileItem IncludedFile in IncludedFileList)
{
QueueFileForBatchUpload(IncludedFile);
}
}
}
}
public override void SetUpGlobalEnvironment(ReadOnlyTargetRules Target)
{
base.SetUpGlobalEnvironment(Target);
// connect to server
if (InitializeRemoteExecution(Target.bFlushBuildDirOnRemoteMac) == RemoteToolChainErrorCode.NoError)
{
// Setup root directory to use.
SetUserDevRootFromServer();
}
}
///
/// Converts the passed in path from UBT host to compiler native format.
///
public static string ConvertPath(string OriginalPath)
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
if (OriginalPath[1] != ':')
{
if (OriginalPath[0] == '/')
{
return OriginalPath.Replace("\\", "/");
}
throw new BuildException("Can only convert full paths ({0})", OriginalPath);
}
string MacPath = string.Format("{0}{1}/{2}/{3}",
UserDevRootMac,
Environment.MachineName,
OriginalPath[0].ToString().ToUpper(),
OriginalPath.Substring(3));
// clean the path
MacPath = MacPath.Replace("\\", "/");
return MacPath;
}
else
{
return OriginalPath.Replace("\\", "/");
}
}
public static string UnconvertPath(string RemotePath)
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
string StrippedPath = RemotePath.Replace("\\", "/");
// skip over the UserDevRootMac and MachineName
string StuffToSkip = string.Format("{0}{1}", UserDevRootMac, Environment.MachineName);
if (!StrippedPath.StartsWith(StuffToSkip))
{
return RemotePath;
}
StrippedPath = StrippedPath.Substring(StuffToSkip.Length);
// now we make sure the path is /{DriverLetter}/
if (StrippedPath[0] != '/' || StrippedPath[2] != '/')
{
return RemotePath;
}
// convert /{DriveLetter}/ to {DriveLetter}:
char DriveLetter = StrippedPath[1];
// and skip over it
StrippedPath = StrippedPath.Substring(3);
// put back into PC parlance
return string.Format("{0}:/{1}", DriveLetter, StrippedPath).Replace("/", "\\");
}
else
{
return RemotePath;
}
}
protected string GetMacDevSrcRoot()
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// figure out the remote version of Engine/Source
return ConvertPath(Path.GetFullPath(Path.Combine(BranchDirectory, "Engine/Source/")));
}
else
{
return UnrealBuildTool.EngineSourceDirectory.FullName; ;
}
}
private static List RsyncDirs = new List();
private static List RsyncExtensions = new List();
public static void QueueFileForBatchUpload(FileItem LocalFileItem)
{
// Now, we actually just remember unique directories with any files, and upload all files in them to the remote machine
// (either via rsync, or RPCUtil acting like rsync)
string Entry = Path.GetDirectoryName(LocalFileItem.AbsolutePath);
if (!RsyncDirs.Contains(Entry))
{
RsyncDirs.Add(Entry);
}
string Ext = Path.GetExtension(LocalFileItem.AbsolutePath);
if (Ext == "")
{
Ext = Path.GetFileName(LocalFileItem.AbsolutePath);
}
if (!RsyncExtensions.Contains(Ext))
{
RsyncExtensions.Add(Ext);
}
}
public FileItem LocalToRemoteFileItem(FileItem LocalFileItem, bool bShouldUpload)
{
FileItem RemoteFileItem = null;
// Look to see if we've already made a remote FileItem for this local FileItem
if (!CachedRemoteFileItems.TryGetValue(LocalFileItem, out RemoteFileItem))
{
// If not, create it now
string RemoteFilePath = ConvertPath(LocalFileItem.AbsolutePath);
RemoteFileItem = FileItem.GetRemoteItemByPath(RemoteFilePath, RemoteToolChainPlatform);
// Is shadowing requested?
if (bShouldUpload)
{
QueueFileForBatchUpload(LocalFileItem);
}
CachedRemoteFileItems.Add(LocalFileItem, RemoteFileItem);
}
return RemoteFileItem;
}
public FileItem RemoteToLocalFileItem(FileItem RemoteFileItem)
{
// Look to see if we've already made a remote FileItem for this local FileItem
foreach (var Item in CachedRemoteFileItems)
{
if (Item.Value.AbsolutePath == RemoteFileItem.AbsolutePath)
{
return Item.Key;
}
}
return RemoteFileItem;
}
///
/// Helper function to sync source files to and from the local system and a remote Mac
///
//This chunk looks to be required to pipe output to VS giving information on the status of a remote build.
public static bool OutputReceivedDataEventHandlerEncounteredError = false;
public static string OutputReceivedDataEventHandlerEncounteredErrorMessage = "";
public static void OutputReceivedDataEventHandler(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null))
{
Log.TraceInformation(Line.Data);
foreach (string ErrorToken in ErrorMessageTokens)
{
if (Line.Data.Contains(ErrorToken))
{
OutputReceivedDataEventHandlerEncounteredError = true;
OutputReceivedDataEventHandlerEncounteredErrorMessage += Line.Data;
break;
}
}
}
}
public static void PostCodeGeneration(UHTManifest Manifest)
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// @todo UHT: Temporary workaround for UBT no longer being able to follow includes from generated headers unless
// the headers already existed before the build started. We're working on a proper fix.
// Make sure all generated headers are synced. If we had to generate code, we need to assume that not all of the
// header files existed on disk at the time that UBT scanned include statements looking for prerequisite files. Those
// files are created during code generation and must exist on disk by the time this function is called. We'll scan
// for generated code files and make sure they are enqueued for copying to the remote machine.
foreach (var UObjectModule in Manifest.Modules)
{
// @todo uht: Ideally would only copy exactly the files emitted by UnrealHeaderTool, rather than scanning directory (could copy stale files; not a big deal though)
try
{
var GeneratedCodeDirectory = Path.GetDirectoryName(UObjectModule.GeneratedCPPFilenameBase);
var GeneratedCodeFiles = Directory.GetFiles(GeneratedCodeDirectory, "*", SearchOption.AllDirectories);
foreach (var GeneratedCodeFile in GeneratedCodeFiles)
{
// Skip copying "Timestamp" files (UBT temporary files)
if (!Path.GetFileName(GeneratedCodeFile).Equals(@"Timestamp", StringComparison.InvariantCultureIgnoreCase))
{
var GeneratedCodeFileItem = FileItem.GetExistingItemByPath(GeneratedCodeFile);
QueueFileForBatchUpload(GeneratedCodeFileItem);
}
}
}
catch (System.IO.DirectoryNotFoundException)
{
// Ignore directory not found
}
// For source files in legacy "Classes" directories, we need to make sure they all get copied over too, since
// they may not have been directly included in any C++ source files (only generated headers), and the initial
// header scan wouldn't have picked them up if they hadn't been generated yet!
try
{
var SourceFiles = Directory.GetFiles(UObjectModule.BaseDirectory, "*", SearchOption.AllDirectories);
foreach (var SourceFile in SourceFiles)
{
var SourceFileItem = FileItem.GetExistingItemByPath(SourceFile);
QueueFileForBatchUpload(SourceFileItem);
}
}
catch (System.IO.DirectoryNotFoundException)
{
// Ignore directory not found
}
}
}
}
static public void OutputReceivedForRsync(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null) && (Line.Data != ""))
{
Log.TraceInformation(Line.Data);
}
}
private static Dictionary