/** * This function should be used as the ActionHandler delegate method for Actions that * need to run over RPCUtility. It will block until the remote command completes */ static public void RPCActionHandler(Action Action, out int ExitCode, out string Output) { Hashtable Results = RPCUtilHelper.Command(Action.WorkingDirectory, Action.CommandPath, Action.CommandArguments, Action.ProducedItems.Count > 0 ? Action.ProducedItems[0].AbsolutePath : null); if (Results == null) { ExitCode = -1; Output = null; Log.TraceInformation("Command failed to execute! {0} {1}", Action.CommandPath, Action.CommandArguments); } else { // capture the exit code if (Results["ExitCode"] != null) { ExitCode = (int)(Int64)Results["ExitCode"]; } else { ExitCode = 0; } // pass back the string Output = Results["CommandOutput"] as string; } }
public override int GetMaxActionsToExecuteInParallel() { Int32 RemoteCPUCount = RPCUtilHelper.GetCommandSlots(); if (RemoteCPUCount == 0) { RemoteCPUCount = Environment.ProcessorCount; } return(RemoteCPUCount); }
public RemoteExecutor() { Int32 RemoteCPUCount = RPCUtilHelper.GetCommandSlots(); if (RemoteCPUCount == 0) { RemoteCPUCount = Environment.ProcessorCount; } AdjustedProcessorCountMultiplierValue = (Double)RemoteCPUCount / (Double)Environment.ProcessorCount; Log.TraceVerbose("Adjusting the remote Mac compile process multiplier to " + AdjustedProcessorCountMultiplierValue.ToString()); }
static public Double GetAdjustedProcessorCountMultiplier() { if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { Int32 RemoteCPUCount = RPCUtilHelper.GetCommandSlots(); if (RemoteCPUCount == 0) { RemoteCPUCount = Environment.ProcessorCount; } Double AdjustedMultiplier = (Double)RemoteCPUCount / (Double)Environment.ProcessorCount; Log.TraceVerbose("Adjusting the remote Mac compile process multiplier to " + AdjustedMultiplier.ToString()); return(AdjustedMultiplier); } else { return(1.0); } }
// Gather a users root path from the remote server. Should only be called once. public static void SetUserDevRootFromServer() { if (!bUseRPCUtil) { // 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 + UserDevRootMac; } } }
private static void SelectXcode(ref string DeveloperDir, bool bVerbose) { string Reason = "hardcoded"; if (DeveloperDir == "xcode-select") { Reason = "xcode-select"; if (Utils.IsRunningOnMono) { // on the Mac, run xcode-select directly DeveloperDir = Utils.RunLocalProcessAndReturnStdOut("xcode-select", "--print-path"); // make sure we get a full path if (Directory.Exists(DeveloperDir) == false) { throw new BuildException("Selected Xcode ('{0}') doesn't exist, cannot continue.", DeveloperDir); } } else { Hashtable Results = RPCUtilHelper.Command("/", "xcode-select", "--print-path", null); if (Results != null) { DeveloperDir = (string)Results["CommandOutput"]; DeveloperDir = DeveloperDir.TrimEnd(); } } if (DeveloperDir.EndsWith("/") == false) { // we expect this to end with a slash DeveloperDir += "/"; } } if (bVerbose && !DeveloperDir.StartsWith("/Applications/Xcode.app")) { Log.TraceInformationOnce("Compiling with non-standard Xcode ({0}): {1}", Reason, DeveloperDir); } }
/// <summary> /// Resolve any outstanding remote file info lookups /// </summary> private void LookupOutstandingFiles() { // for remote files, look up any outstanding files if (bIsRemoteFile) { FileItem[] Files = null; lock (DelayedRemoteLookupFiles) { if (DelayedRemoteLookupFiles.Count > 0) { // make an array so we can clear the original array, just in case BatchFileInfo does something that uses // DelayedRemoteLookupFiles, so we don't deadlock Files = DelayedRemoteLookupFiles.ToArray(); DelayedRemoteLookupFiles.Clear(); } } if (Files != null) { RPCUtilHelper.BatchFileInfo(Files); } } }
// 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 && !UEBuildConfiguration.bListBuildFolders) { // 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; } }
public override void PreBuildSync() { // no need to sync on the Mac! if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) { return; } if (bUseRPCUtil) { string ExtString = ""; // look only for useful extensions foreach (string Ext in RsyncExtensions) { // for later ls ExtString += Ext.StartsWith(".") ? ("*" + Ext) : Ext; ExtString += " "; } List <string> BatchUploadCommands = new List <string>(); // for each directory we visited, add all the files in that directory foreach (string Dir in RsyncDirs) { List <string> LocalFilenames = new List <string>(); if (!Directory.Exists(Dir)) { Directory.CreateDirectory(Dir); } // look only for useful extensions foreach (string Ext in RsyncExtensions) { string[] Files = Directory.GetFiles(Dir, "*" + Ext); foreach (string SyncFile in Files) { // remember all local files LocalFilenames.Add(Path.GetFileName(SyncFile)); string RemoteFilePath = ConvertPath(SyncFile); // an upload command is local name and remote name BatchUploadCommands.Add(SyncFile + ";" + RemoteFilePath); } } } // batch upload RPCUtilHelper.BatchUpload(BatchUploadCommands.ToArray()); } else { List <string> RelativeRsyncDirs = new List <string>(); foreach (string Dir in RsyncDirs) { RelativeRsyncDirs.Add(Utils.CleanDirectorySeparators(Dir.Replace(":", ""), '/') + "/"); } // write out directories to copy string RSyncPathsFile = Path.GetTempFileName(); string IncludeFromFile = Path.GetTempFileName(); File.WriteAllLines(RSyncPathsFile, RelativeRsyncDirs.ToArray()); File.WriteAllLines(IncludeFromFile, RsyncExtensions); // source and destination paths in the format rsync wants string CygRootPath = "/cygdrive"; // ConvertPathToCygwin(Path.GetFullPath("")); string RemotePath = string.Format("{0}{1}", UserDevRootMac, Environment.MachineName); // get the executable dir for SSH, so Rsync can call it easily string ExeDir = Path.GetDirectoryName(ResolvedSSHExe); Process RsyncProcess = new Process(); if (ExeDir != "") { RsyncProcess.StartInfo.WorkingDirectory = ExeDir; } // --exclude='*' ??? why??? RsyncProcess.StartInfo.FileName = ResolvedRSyncExe; RsyncProcess.StartInfo.Arguments = string.Format( "-vzae \"{0}\" --rsync-path=\"mkdir -p {2} && rsync\" --chmod=ug=rwX,o=rxX --delete --files-from=\"{4}\" --include-from=\"{5}\" --include='*/' --exclude='*.o' --exclude='Timestamp' '{1}' {6}@{3}:'{2}'", ResolvedRsyncAuthentication, CygRootPath, RemotePath, RemoteServerName, ConvertPathToCygwin(RSyncPathsFile), ConvertPathToCygwin(IncludeFromFile), RSyncUsername); Console.WriteLine("Command: " + RsyncProcess.StartInfo.Arguments); RsyncProcess.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync); RsyncProcess.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync); // run rsync Utils.RunLocalProcess(RsyncProcess); File.Delete(IncludeFromFile); File.Delete(RSyncPathsFile); } // we can now clear out the set of files RsyncDirs.Clear(); RsyncExtensions.Clear(); }
private RemoteToolChainErrorCode InitializeRemoteExecution() { 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 && !UEBuildConfiguration.bListBuildFolders) { // 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 <string> Locations = new List <string>(); 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(BuildConfiguration.RelativeEnginePath, "Build", "NotForLicensees")); Locations.Add(Path.Combine(BuildConfiguration.RelativeEnginePath, "Build", "NoRedist")); Locations.Add(Path.Combine(BuildConfiguration.RelativeEnginePath, "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); 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 = Path.GetFullPath(Path.Combine(BuildConfiguration.RelativeEnginePath, "Build", "BatchFiles")); 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)), Path.GetFullPath(BuildConfiguration.RelativeEnginePath)); KeyProcess.Start(); KeyProcess.WaitForExit(); // make sure it succeeded if we want to re-init if (KeyProcess.ExitCode == 0) { InitializationErrorCode = InitializeRemoteExecution(); } } } else { RemoteServerName = Environment.MachineName; // can't error in this case } bHasBeenInitialized = true; return(InitializationErrorCode); }
/// <summary> /// The actual function to run in a thread. This is potentially long and blocking /// </summary> private void ThreadFunc() { // thread start time Action.StartTime = DateTimeOffset.Now; if (Action.ActionHandler != null) { // call the function and get the ExitCode and an output string string Output; Action.ActionHandler(Action, out ExitCode, out Output); // Output status description (file name) when no output is returned if (string.IsNullOrEmpty(Output)) { if (Action.bShouldOutputStatusDescription) { Output = string.Format("[{0}/{1}] {2} {3}", JobNumber, TotalJobs, Action.CommandDescription, Action.StatusDescription); } else { Output = Action.StatusDescription; } } SendOutputToEventHandler(Action, Output); } else { // batch files (.bat, .cmd) need to be run via or cmd /c or shellexecute, // the latter which we can't use because we want to redirect input/output bool bLaunchViaCmdExe = !Utils.IsRunningOnMono && !Path.GetExtension(Action.CommandPath).ToLower().EndsWith("exe"); // Create the action's process. ProcessStartInfo ActionStartInfo = new ProcessStartInfo(); ActionStartInfo.WorkingDirectory = ExpandEnvironmentVariables(Action.WorkingDirectory); string ExpandedCommandPath = ExpandEnvironmentVariables(Action.CommandPath); if (bLaunchViaCmdExe) { ActionStartInfo.FileName = "cmd.exe"; ActionStartInfo.Arguments = string.Format ( "/c \"{0} {1}\"", ExpandEnvironmentVariables(ExpandedCommandPath), ExpandEnvironmentVariables(Action.CommandArguments) ); } else { ActionStartInfo.FileName = ExpandedCommandPath; ActionStartInfo.Arguments = ExpandEnvironmentVariables(Action.CommandArguments); } ActionStartInfo.UseShellExecute = false; ActionStartInfo.RedirectStandardInput = false; ActionStartInfo.RedirectStandardOutput = false; ActionStartInfo.RedirectStandardError = false; // Log command-line used to execute task if debug info printing is enabled. if (UnrealBuildTool.bPrintDebugInfo) { Log.TraceVerbose("Executing: {0} {1}", ExpandedCommandPath, ActionStartInfo.Arguments); } // Log summary if wanted. else if (Action.bShouldOutputStatusDescription) { string CommandDescription = Action.CommandDescription != null ? Action.CommandDescription : Path.GetFileName(ExpandedCommandPath); if (string.IsNullOrEmpty(CommandDescription)) { Log.TraceInformation(Action.StatusDescription); } else { Log.TraceInformation("[{0}/{1}] {2} {3}", JobNumber, TotalJobs, CommandDescription, Action.StatusDescription); } } // Try to launch the action's process, and produce a friendly error message if it fails. Process ActionProcess = null; try { try { ActionProcess = new Process(); ActionProcess.StartInfo = ActionStartInfo; bool bShouldRedirectOuput = Action.OutputEventHandler != null || Action.bPrintDebugInfo; if (bShouldRedirectOuput) { ActionStartInfo.RedirectStandardOutput = true; ActionStartInfo.RedirectStandardError = true; ActionProcess.EnableRaisingEvents = true; if (Action.OutputEventHandler != null) { ActionProcess.OutputDataReceived += Action.OutputEventHandler; ActionProcess.ErrorDataReceived += Action.OutputEventHandler; } if (Action.bPrintDebugInfo) { ActionProcess.OutputDataReceived += new DataReceivedEventHandler(ActionDebugOutput); ActionProcess.ErrorDataReceived += new DataReceivedEventHandler(ActionDebugOutput); } } ActionProcess.Start(); if (bShouldRedirectOuput) { ActionProcess.BeginOutputReadLine(); ActionProcess.BeginErrorReadLine(); } } catch (Exception ex) { throw new BuildException(ex, "Failed to start local process for action: {0} {1}\r\n{2}", Action.CommandPath, Action.CommandArguments, ex.ToString()); } // wait for process to start // NOTE: this may or may not be necessary; seems to depend on whether the system UBT is running on start the process in a timely manner. int checkIterations = 0; bool haveConfiguredProcess = false; do { if (ActionProcess.HasExited) { if (haveConfiguredProcess == false) { Debug.WriteLine("Process for action exited before able to configure!"); } break; } Thread.Sleep(100); if (!haveConfiguredProcess) { try { ActionProcess.PriorityClass = ProcessPriorityClass.BelowNormal; haveConfiguredProcess = true; } catch (Exception) { } break; } checkIterations++; } while (checkIterations < 10); if (checkIterations == 10) { throw new BuildException("Failed to configure local process for action: {0} {1}", Action.CommandPath, Action.CommandArguments); } // block until it's complete // @todo iosmerge: UBT had started looking at: if (Utils.IsValidProcess(Process)) // do we need to check that in the thread model? while (!ActionProcess.HasExited) { Thread.Sleep(10); } // capture exit code ExitCode = ActionProcess.ExitCode; } finally { // As the process has finished now, free its resources. On non-Windows platforms, processes depend // on POSIX/BSD threading and these are limited per application. Disposing the Process releases // these thread resources. if (ActionProcess != null) { ActionProcess.Close(); } } } // track how long it took Action.EndTime = DateTimeOffset.Now; if (!Utils.IsRunningOnMono) { // let RPCUtilHelper clean up anything thread related RPCUtilHelper.OnThreadComplete(); } // we are done!! bComplete = true; }
protected static void SelectSDK(string BaseSDKDir, string OSPrefix, ref string PlatformSDKVersion, bool bVerbose) { if (PlatformSDKVersion == "latest") { PlatformSDKVersion = ""; try { string[] SubDirs = null; if (Utils.IsRunningOnMono) { // on the Mac, we can just get the directory name SubDirs = System.IO.Directory.GetDirectories(BaseSDKDir); } else { Hashtable Results = RPCUtilHelper.Command("/", "ls", BaseSDKDir, null); if (Results != null) { string Result = (string)Results["CommandOutput"]; SubDirs = Result.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); } } // loop over the subdirs and parse out the version int MaxSDKVersionMajor = 0; int MaxSDKVersionMinor = 0; string MaxSDKVersionString = null; foreach (string SubDir in SubDirs) { string SubDirName = Path.GetFileNameWithoutExtension(SubDir); if (SubDirName.StartsWith(OSPrefix)) { // get the SDK version from the directory name string SDKString = SubDirName.Replace(OSPrefix, ""); int Major = 0; int Minor = 0; // parse it into whole and fractional parts (since 10.10 > 10.9 in versions, but not in math) try { string[] Tokens = SDKString.Split(".".ToCharArray()); if (Tokens.Length == 2) { Major = int.Parse(Tokens[0]); Minor = int.Parse(Tokens[1]); } } catch (Exception) { // weirdly formatted SDKs continue; } // update largest SDK version number if (Major > MaxSDKVersionMajor || (Major == MaxSDKVersionMajor && Minor > MaxSDKVersionMinor)) { MaxSDKVersionString = SDKString; MaxSDKVersionMajor = Major; MaxSDKVersionMinor = Minor; } } } // use the largest version if (MaxSDKVersionString != null) { PlatformSDKVersion = MaxSDKVersionString; } } catch (Exception Ex) { // on any exception, just use the backup version Log.TraceInformation("Triggered an exception while looking for SDK directory in Xcode.app"); Log.TraceInformation("{0}", Ex.ToString()); } } // make sure we have a valid SDK directory if (Utils.IsRunningOnMono && !Directory.Exists(Path.Combine(BaseSDKDir, OSPrefix + PlatformSDKVersion + ".sdk"))) { throw new BuildException("Invalid SDK {0}{1}.sdk, not found in {2}", OSPrefix, PlatformSDKVersion, BaseSDKDir); } if (bVerbose && !ProjectFileGenerator.bGenerateProjectFiles) { if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { Log.TraceInformation("Compiling with {0} SDK {1} on Mac {2}", OSPrefix, PlatformSDKVersion, RemoteServerName); } else { Log.TraceInformation("Compiling with {0} SDK {1}", OSPrefix, PlatformSDKVersion); } } }
/// <summary> /// /// </summary> /// <param name="ProjectFile"></param> /// <param name="Executable"></param> /// <param name="StageDirectory"></param> /// <param name="PlatformType"></param> public static void GenerateAssetCatalog(FileReference ProjectFile, string Executable, string StageDirectory, UnrealTargetPlatform PlatformType) { // Initialize the toolchain. IOSProjectSettings ProjectSettings = ((IOSPlatform)UEBuildPlatform.GetBuildPlatform(PlatformType)).ReadProjectSettings(null); IOSToolChain ToolChain = new IOSToolChain(ProjectFile, ProjectSettings); // Determine whether the user has modified icons that require a remote Mac to build. CppPlatform Platform = PlatformType == UnrealTargetPlatform.IOS ? CppPlatform.IOS : CppPlatform.TVOS; bool bUserImagesExist = false; ToolChain.GenerateAssetCatalog(Platform, ref bUserImagesExist); // Don't attempt to do anything remotely if the user is using the default UE4 images. if (!bUserImagesExist) { return; } // Also don't attempt to use a remote Mac if packaging for TVOS on PC. if (Platform == CppPlatform.TVOS && BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { return; } // Save off the current bUseRPCUtil setting to restore at the end of this function. // At this time, iPhonePackager needs to be called with bUseRPCUtil == true. bool bSaveUseRPCUtil = RemoteToolChain.bUseRPCUtil; // Initialize the remote calling environment, taking into account the user's SSH setting. ToolChain.SetUpGlobalEnvironment(false); // Build the asset catalog ActionGraph. ActionGraph ActionGraph = new ActionGraph(); List <FileItem> OutputFiles = new List <FileItem>(); ToolChain.CompileAssetCatalog(FileItem.GetItemByPath(Executable), Platform, ActionGraph, OutputFiles); ActionGraph.FinalizeActionGraph(); // I'm not sure how to derive the UE4Game and Development arguments programmatically. string[] Arguments = new string[] { "UE4Game", (PlatformType == UnrealTargetPlatform.IOS ? "IOS" : "TVOS"), "Development", "-UniqueBuildEnvironment" }; // Perform all of the setup necessary to actually execute the ActionGraph instance. ReadOnlyBuildVersion Version = new ReadOnlyBuildVersion(BuildVersion.ReadDefault()); List <string[]> TargetSettings = new List <string[]>(); TargetSettings.Add(Arguments); var Targets = new List <UEBuildTarget>(); Dictionary <UEBuildTarget, CPPHeaders> TargetToHeaders = new Dictionary <UEBuildTarget, CPPHeaders>(); List <TargetDescriptor> TargetDescs = new List <TargetDescriptor>(); foreach (string[] TargetSetting in TargetSettings) { TargetDescs.AddRange(TargetDescriptor.ParseCommandLine(TargetSetting, ref ProjectFile)); } foreach (TargetDescriptor TargetDesc in TargetDescs) { UEBuildTarget Target = UEBuildTarget.CreateTarget(TargetDesc, Arguments, false, Version); if (Target == null) { continue; } Targets.Add(Target); TargetToHeaders.Add(Target, null); } bool bIsRemoteCompile = BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac; // Create the build configuration object, and read the settings BuildConfiguration BuildConfiguration = new BuildConfiguration(); XmlConfig.ApplyTo(BuildConfiguration); CommandLine.ParseArguments(Arguments, BuildConfiguration); BuildConfiguration.bUseUBTMakefiles = false; Action[] PrerequisiteActions; { HashSet <Action> PrerequisiteActionsSet = new HashSet <Action>(); foreach (FileItem OutputFile in OutputFiles) { ActionGraph.GatherPrerequisiteActions(OutputFile, ref PrerequisiteActionsSet); } PrerequisiteActions = PrerequisiteActionsSet.ToArray(); } // Copy any asset catalog files to the remote Mac, if necessary. foreach (UEBuildTarget Target in Targets) { UEBuildPlatform.GetBuildPlatform(Target.Platform).PreBuildSync(); } // Begin execution of the ActionGraph. Dictionary <UEBuildTarget, List <FileItem> > TargetToOutdatedPrerequisitesMap; List <Action> ActionsToExecute = ActionGraph.GetActionsToExecute(BuildConfiguration, PrerequisiteActions, Targets, TargetToHeaders, true, true, out TargetToOutdatedPrerequisitesMap); string ExecutorName = "Unknown"; bool bSuccess = ActionGraph.ExecuteActions(BuildConfiguration, ActionsToExecute, bIsRemoteCompile, out ExecutorName, "", EHotReload.Disabled); if (bSuccess) { if (bIsRemoteCompile) { // Copy the remotely built AssetCatalog directory locally. foreach (FileItem OutputFile in OutputFiles) { string RemoteDirectory = System.IO.Path.GetDirectoryName(OutputFile.AbsolutePath).Replace("\\", "/"); FileItem LocalExecutable = ToolChain.RemoteToLocalFileItem(FileItem.GetItemByPath(Executable)); string LocalDirectory = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(LocalExecutable.AbsolutePath), "AssetCatalog"); LocalDirectory = StageDirectory; RPCUtilHelper.CopyDirectory(RemoteDirectory, LocalDirectory, RPCUtilHelper.ECopyOptions.DoNotReplace); } } else { // Copy the built AssetCatalog directory to the StageDirectory. foreach (FileItem OutputFile in OutputFiles) { string SourceDirectory = System.IO.Path.GetDirectoryName(OutputFile.AbsolutePath).Replace("\\", "/"); System.IO.DirectoryInfo SourceDirectoryInfo = new System.IO.DirectoryInfo(SourceDirectory); if (!System.IO.Directory.Exists(StageDirectory)) { System.IO.Directory.CreateDirectory(StageDirectory); } System.IO.FileInfo[] SourceFiles = SourceDirectoryInfo.GetFiles(); foreach (System.IO.FileInfo SourceFile in SourceFiles) { string DestinationPath = System.IO.Path.Combine(StageDirectory, SourceFile.Name); SourceFile.CopyTo(DestinationPath, true); } } } } // Restore the former bUseRPCUtil setting. RemoteToolChain.bUseRPCUtil = bSaveUseRPCUtil; }
private static int InitializeRemoteExecution() { if (bHasBeenInitialized) { return(InitializationErrorCode); } 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 to set up your remote compilation settings."); InitializationErrorCode = 99; } if (!bUseRPCUtil) { // we need the RemoteServerName and the Username to find the private key ResolvedRSyncUsername = ResolveString(RSyncUsername, false); // if the override path is set, just use it directly if (!string.IsNullOrEmpty(SSHPrivateKeyOverridePath)) { ResolvedSSHPrivateKey = ResolveString(SSHPrivateKeyOverridePath, true); // make sure it exists if (!File.Exists(ResolvedSSHPrivateKey)) { throw new BuildException("An SSHKey override was specified [" + SSHPrivateKeyOverridePath + "] but it doesn't exist. Can't continue..."); } } else { // all the places to look for a key string[] Locations = new string[] { Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Unreal Engine", "UnrealBuildTool"), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Unreal Engine", "UnrealBuildTool"), Path.Combine(BuildConfiguration.RelativeEnginePath, "Build", "NotForLicensees"), Path.Combine(BuildConfiguration.RelativeEnginePath, "Build", "NoRedist"), Path.Combine(BuildConfiguration.RelativeEnginePath, "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; break; } } } // resolve the rest of the strings ResolvedRSyncExe = ResolveString(RSyncExe, true); ResolvedSSHExe = ResolveString(SSHExe, true); ResolvedRsyncAuthentication = ResolveString(RsyncAuthentication, false); ResolvedSSHAuthentication = ResolveString(SSHAuthentication, false); } // start up remote communication and record if it succeeds InitializationErrorCode = RPCUtilHelper.Initialize(RemoteServerName); // allow user to set up if (InitializationErrorCode == 100) { Process KeyProcess = new Process(); KeyProcess.StartInfo.WorkingDirectory = Path.GetFullPath(Path.Combine(BuildConfiguration.RelativeEnginePath, "Build", "BatchFiles")); KeyProcess.StartInfo.FileName = "MakeAndInstallSSHKey.bat"; KeyProcess.StartInfo.Arguments = string.Format( "\"{0}\" \"{1}\" {2} {3} \"{4}\" \"{5}\" \"{6}\"", ResolvedSSHExe, ResolvedRSyncExe, ResolvedRSyncUsername, RemoteServerName, Environment.GetFolderPath(Environment.SpecialFolder.Personal), ConvertPathToCygwin(Environment.GetFolderPath(Environment.SpecialFolder.Personal)), Path.GetFullPath(BuildConfiguration.RelativeEnginePath)); KeyProcess.Start(); KeyProcess.WaitForExit(); // make sure it succeeded if we want to re-init if (KeyProcess.ExitCode == 0) { InitializeRemoteExecution(); } } } else { RemoteServerName = Environment.MachineName; // can't error in this case } bHasBeenInitialized = true; return(InitializationErrorCode); }