/** * Creates and deserializes the dependency cache at the passed in location * * @param CachePath Name of the cache file to deserialize */ public static DependencyCache Create(string CachePath) { // See whether the cache file exists. FileItem Cache = FileItem.GetItemByPath(CachePath); if (Cache.bExists) { if (BuildConfiguration.bPrintPerformanceInfo) { Log.TraceInformation("Loading existing IncludeFileCache: " + Cache.AbsolutePath); } var TimerStartTime = DateTime.UtcNow; // Deserialize cache from disk if there is one. DependencyCache Result = Load(Cache); if (Result != null) { // Successfully serialize, create the transient variables and return cache. Result.CachePath = CachePath; Result.CacheUpdateDate = DateTimeOffset.Now; var TimerDuration = DateTime.UtcNow - TimerStartTime; if (BuildConfiguration.bPrintPerformanceInfo) { Log.TraceInformation("Loading IncludeFileCache took " + TimerDuration.TotalSeconds + "s"); } Telemetry.SendEvent("LoadIncludeDependencyCacheStats.2", "TotalDuration", TimerDuration.TotalSeconds.ToString("0.00")); return(Result); } } // Fall back to a clean cache on error or non-existance. return(new DependencyCache(Cache)); }
public static ExecutionResult ExecuteActions(List <Action> Actions) { ExecutionResult XGEResult = ExecutionResult.TasksSucceeded; if (Actions.Count > 0) { // Write the actions to execute to a XGE task file. string XGETaskFilePath = Path.Combine(BuildConfiguration.BaseIntermediatePath, "XGETasks.xml"); WriteTaskFile(Actions, XGETaskFilePath, ProgressWriter.bWriteMarkup); if (BuildConfiguration.bXGEExport) { SaveXGEFile(XGETaskFilePath); } else { // Try to execute the XGE tasks, and if XGE is available, skip the local execution fallback. if (Telemetry.IsAvailable()) { try { const string BuilderKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Xoreax\\IncrediBuild\\Builder"; string CPUUtilization = Registry.GetValue(BuilderKey, "ForceCPUCount", "").ToString(); string AvoidTaskExecutionOnLocalMachine = Registry.GetValue(BuilderKey, "AvoidLocalExec", "").ToString(); string RestartRemoteProcessesOnLocalMachine = Registry.GetValue(BuilderKey, "AllowDoubleTargets", "").ToString(); string LimitMaxNumberOfCores = Registry.GetValue(BuilderKey, "MaxHelpers", "").ToString(); string WriteOutputToDiskInBackground = Registry.GetValue(BuilderKey, "LazyOutputWriter_Beta", "").ToString(); string MaxConcurrentPDBs = Registry.GetValue(BuilderKey, "MaxConcurrentPDBs", "").ToString(); string EnabledAsHelper = Registry.GetValue(BuilderKey, "LastEnabled", "").ToString(); Telemetry.SendEvent("XGESettings.2", "CPUUtilization", CPUUtilization, "AvoidTaskExecutionOnLocalMachine", AvoidTaskExecutionOnLocalMachine, "RestartRemoteProcessesOnLocalMachine", RestartRemoteProcessesOnLocalMachine, "LimitMaxNumberOfCores", LimitMaxNumberOfCores, "WriteOutputToDiskInBackground", WriteOutputToDiskInBackground, "MaxConcurrentPDBs", MaxConcurrentPDBs, "EnabledAsHelper", EnabledAsHelper); } catch { } } XGEResult = XGE.ExecuteTaskFileWithProgressMarkup(XGETaskFilePath, Actions.Count, (Sender, Args) => { if (Actions[0].OutputEventHandler != null) { Actions[0].OutputEventHandler(Sender, Args); } else { Console.WriteLine(Args.Data); } }); } } return(XGEResult); }
bool ExecuteActionBatch(List <Action> Actions) { bool XGEResult = true; if (Actions.Count > 0) { // Write the actions to execute to a XGE task file. string XGETaskFilePath = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "XGETasks.xml").FullName; WriteTaskFile(Actions, XGETaskFilePath, ProgressWriter.bWriteMarkup, false); // Try to execute the XGE tasks, and if XGE is available, skip the local execution fallback. if (Telemetry.IsAvailable()) { try { // @todo: find a way to report that for other host platforms if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64) { const string BuilderKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Xoreax\\IncrediBuild\\Builder"; string CPUUtilization = Registry.GetValue(BuilderKey, "ForceCPUCount", "").ToString(); string AvoidTaskExecutionOnLocalMachine = Registry.GetValue(BuilderKey, "AvoidLocalExec", "").ToString(); string RestartRemoteProcessesOnLocalMachine = Registry.GetValue(BuilderKey, "AllowDoubleTargets", "").ToString(); string LimitMaxNumberOfCores = Registry.GetValue(BuilderKey, "MaxHelpers", "").ToString(); string WriteOutputToDiskInBackground = Registry.GetValue(BuilderKey, "LazyOutputWriter_Beta", "").ToString(); string MaxConcurrentPDBs = Registry.GetValue(BuilderKey, "MaxConcurrentPDBs", "").ToString(); string EnabledAsHelper = Registry.GetValue(BuilderKey, "LastEnabled", "").ToString(); Telemetry.SendEvent("XGESettings.2", "CPUUtilization", CPUUtilization, "AvoidTaskExecutionOnLocalMachine", AvoidTaskExecutionOnLocalMachine, "RestartRemoteProcessesOnLocalMachine", RestartRemoteProcessesOnLocalMachine, "LimitMaxNumberOfCores", LimitMaxNumberOfCores, "WriteOutputToDiskInBackground", WriteOutputToDiskInBackground, "MaxConcurrentPDBs", MaxConcurrentPDBs, "EnabledAsHelper", EnabledAsHelper); } } catch { } } DataReceivedEventHandler OutputHandler = ((Sender, Args) => { if (Args.Data != null) { DefaultOutputHandler(Args.Data); } }); XGEResult = ExecuteTaskFileWithProgressMarkup(XGETaskFilePath, Actions.Count, OutputHandler); } return(XGEResult); }
/// <summary> /// Check that an environment variable is the same as one derived from keys in the registry, and send a telemetry event if it's not /// </summary> private void CompareEnvironmentVariable(string VariableName, string RegistryValue, bool bSupportWindowsXP) { string EnvironmentValue = Environment.GetEnvironmentVariable(VariableName); Telemetry.SendEvent("CompareEnvironmentVariable", "Name", VariableName, "Match", (EnvironmentValue == RegistryValue).ToString(), "EnvironmentValue", EnvironmentValue, "RegistryValue", RegistryValue, "Platform", Platform.ToString(), "UWPBuildForStore", UWPPlatform.bBuildForStore.ToString(), "WinXP", bSupportWindowsXP.ToString(), "Compiler", WindowsPlatform.Compiler.ToString(), "UseWindowsSDK10", WindowsPlatform.bUseWindowsSDK10.ToString()); }
public static ExecutionResult ExecuteActions(List <Action> Actions) { ExecutionResult XGEResult = ExecutionResult.TasksSucceeded; if (Actions.Count > 0) { // Write the actions to execute to a XGE task file. string XGETaskFilePath = Path.Combine(BuildConfiguration.BaseIntermediatePath, "XGETasks.xml"); WriteTaskFile(Actions, XGETaskFilePath, ProgressWriter.bWriteMarkup); if (BuildConfiguration.bXGEExport) { SaveXGEFile(XGETaskFilePath); } else { // Try to execute the XGE tasks, and if XGE is available, skip the local execution fallback. if (Telemetry.IsAvailable()) { try { const string BuilderKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Xoreax\\IncrediBuild\\Builder"; string CPUUtilization = Registry.GetValue(BuilderKey, "ForceCPUCount", "").ToString(); string AvoidTaskExecutionOnLocalMachine = Registry.GetValue(BuilderKey, "AvoidLocalExec", "").ToString(); string RestartRemoteProcessesOnLocalMachine = Registry.GetValue(BuilderKey, "AllowDoubleTargets", "").ToString(); string LimitMaxNumberOfCores = Registry.GetValue(BuilderKey, "MaxHelpers", "").ToString(); string WriteOutputToDiskInBackground = Registry.GetValue(BuilderKey, "LazyOutputWriter_Beta", "").ToString(); string MaxConcurrentPDBs = Registry.GetValue(BuilderKey, "MaxConcurrentPDBs", "").ToString(); string EnabledAsHelper = Registry.GetValue(BuilderKey, "LastEnabled", "").ToString(); Telemetry.SendEvent("XGESettings.2", "CPUUtilization", CPUUtilization, "AvoidTaskExecutionOnLocalMachine", AvoidTaskExecutionOnLocalMachine, "RestartRemoteProcessesOnLocalMachine", RestartRemoteProcessesOnLocalMachine, "LimitMaxNumberOfCores", LimitMaxNumberOfCores, "WriteOutputToDiskInBackground", WriteOutputToDiskInBackground, "MaxConcurrentPDBs", MaxConcurrentPDBs, "EnabledAsHelper", EnabledAsHelper); } catch { } // Add a custom output handler to determine the build duration for each task and map it back to the action that generated it. XGEResult = XGE.ExecuteTaskFileWithProgressMarkup(XGETaskFilePath, Actions.Count, (sender, args) => { // sometimes the args comes in as null var match = XGEDurationRegex.Match(args.Data ?? ""); /* * // This is considered fragile and risky parsing of undocumented XGE output, so protect this code from taking down UBT * try * { * // Use LINQ to evaluate the terms so we don't end up with a * // huge nested conditional expression just to verify the input. * foreach (var PCHEvent in * // first, convert the input line into an enumerable * from RegexMatch in new[] { match } * where RegexMatch.Success * let Filename = RegexMatch.Groups["Filename"].Value * // find the mapped action. (On PS4 at least, XGE appears to use the full filenane, so we need to strip it back down. * let Action = Actions.Find(a => a.StatusDescription == Path.GetFileName(Filename) && a.ActionType == ActionType.Compile) * // We should ALWAYS find the action, so maybe this should throw if we can't * where Action != null * // see if the mapped action produces a PCH file * // (there is currently no compile environment left around by which to tell absolutely, so we infer by the extension. Ugh). * let ActionProducedPCH = Action.ProducedItems.Find(fileItem => new[] { ".PCH", ".GCH" }.Contains(Path.GetExtension(fileItem.AbsolutePath).ToUpperInvariant())) * where ActionProducedPCH != null * // we found a valid PCH action and output file, so parse the duration and send an event. * let durationMatchStr = RegexMatch.Groups["Duration"].Value * // if there's no hour designator, add one so .NET can parse the time. * let durationStr = durationMatchStr.Count(c => c == ':') == 1 ? "0:" + durationMatchStr : durationMatchStr * let duration = TimeSpan.Parse(durationStr) * select new * { * // actually use the filename here because it is coerced to be PCH.MODULE.HEADER.cpp. * // This allows us an easy way to determine shared status (or source module) and the real header source. * FileName = Filename,//Path.GetFileName(ActionProducedPCH.AbsolutePath), * // Get the length from the OS as the FileItem.Length is really for when the file is used as an input, * // so the stored length is absent for a new for or out of date at best. * GeneratedFileLength = new FileInfo(ActionProducedPCH.AbsolutePath).Length, * GeneratedFileDuration = duration, * }) * { * // If we had a valid match for a PCH item, send an event. * Telemetry.SendEvent("PCHTime.2", * "ExecutorType", "XGE", * "Filename", PCHEvent.FileName, * "FileSize", PCHEvent.GeneratedFileLength.ToString(), * "Duration", PCHEvent.GeneratedFileDuration.TotalSeconds.ToString("0.00")); * } * } * catch (Exception ex) * { * // report that something went wrong so we can diagnose. * Telemetry.SendEvent("PCHTimeError.2", * "OutputLine", args.Data, * "Exception", ex.ToString()); * } */ // XGE outputs the duration info in a format that makes VC think it's an error file/line notation if the full filename is used. // So munge it a bit so we don't confuse VC. var updatedData = match.Success ? args.Data.Replace('(', '[').Replace(')', ']') : args.Data; // forward the output on to the normal handler or the console like normal if (Actions[0].OutputEventHandler != null) { DataReceivedEventArgs EventArgs = ConstructDataReceivedEventArgs(updatedData); Actions[0].OutputEventHandler(sender, EventArgs); } else { Console.WriteLine(updatedData); } }); } else { XGEResult = XGE.ExecuteTaskFileWithProgressMarkup(XGETaskFilePath, Actions.Count, (Sender, Args) => { if (Actions[0].OutputEventHandler != null) { Actions[0].OutputEventHandler(Sender, Args); } else { Console.WriteLine(Args.Data); } }); } } } return(XGEResult); }
public static ExecutionResult ExecuteActions(List <Action> Actions) { ExecutionResult XGEResult = ExecutionResult.TasksSucceeded; if (Actions.Count > 0) { // Write the actions to execute to a XGE task file. string XGETaskFilePath = Path.Combine(BuildConfiguration.BaseIntermediatePath, "XGETasks.xml"); WriteTaskFile(Actions, XGETaskFilePath, ProgressWriter.bWriteMarkup); if (BuildConfiguration.bXGEExport) { SaveXGEFile(XGETaskFilePath); } else { // Try to execute the XGE tasks, and if XGE is available, skip the local execution fallback. if (Telemetry.IsAvailable()) { // Add a custom output handler to determine the build duration for each task and map it back to the action that generated it. XGEResult = XGE.ExecuteTaskFileWithProgressMarkup(XGETaskFilePath, Actions.Count, (sender, args) => { // sometimes the args comes in as null var match = XGEDurationRegex.Match(args.Data ?? ""); // This is considered fragile and risky parsing of undocumented XGE output, so protect this code from taking down UBT try { // Use LINQ to evaluate the terms so we don't end up with a // huge nested conditional expression just to verify the input. foreach (var PCHEvent in // first, convert the input line into an enumerable from RegexMatch in new[] { match } where RegexMatch.Success let Filename = RegexMatch.Groups["Filename"].Value // find the mapped action. (On PS4 at least, XGE appears to use the full filenane, so we need to strip it back down. let Action = Actions.Find(a => a.StatusDescription == Path.GetFileName(Filename) && a.ActionType == ActionType.Compile) // We should ALWAYS find the action, so maybe this should throw if we can't where Action != null // see if the mapped action produces a PCH file // (there is currently no compile environment left around by which to tell absolutely, so we infer by the extension. Ugh). let ActionProducedPCH = Action.ProducedItems.Find(fileItem => new[] { ".PCH", ".GCH" }.Contains(Path.GetExtension(fileItem.AbsolutePath).ToUpperInvariant())) where ActionProducedPCH != null // we found a valid PCH action and output file, so parse the duration and send an event. let durationMatchStr = RegexMatch.Groups["Duration"].Value // if there's no hour designator, add one so .NET can parse the time. let durationStr = durationMatchStr.Count(c => c == ':') == 1 ? "0:" + durationMatchStr : durationMatchStr let duration = TimeSpan.Parse(durationStr) select new { // actually use the filename here because it is coerced to be PCH.MODULE.HEADER.cpp. // This allows us an easy way to determine shared status (or source module) and the real header source. FileName = Filename, //Path.GetFileName(ActionProducedPCH.AbsolutePath), // Get the length from the OS as the FileItem.Length is really for when the file is used as an input, // so the stored length is absent for a new for or out of date at best. GeneratedFileLength = new FileInfo(ActionProducedPCH.AbsolutePath).Length, GeneratedFileDuration = duration, }) { // If we had a valid match for a PCH item, send an event. Telemetry.SendEvent("PCHTime.2", "ExecutorType", "XGE", "Filename", PCHEvent.FileName, "FileSize", PCHEvent.GeneratedFileLength.ToString(), "Duration", PCHEvent.GeneratedFileDuration.TotalSeconds.ToString("0.00")); } } catch (Exception ex) { // report that something went wrong so we can diagnose. Telemetry.SendEvent("PCHTimeError.2", "OutputLine", args.Data, "Exception", ex.ToString()); } // XGE outputs the duration info in a format that makes VC think it's an error file/line notation if the full filename is used. // So munge it a bit so we don't confuse VC. var updatedData = match.Success ? args.Data.Replace('(', '[').Replace(')', ']') : args.Data; // forward the output on to the normal handler or the console like normal if (Actions[0].OutputEventHandler != null) { DataReceivedEventArgs EventArgs = ConstructDataReceivedEventArgs(updatedData); Actions[0].OutputEventHandler(sender, EventArgs); } else { Console.WriteLine(updatedData); } }); } else { XGEResult = XGE.ExecuteTaskFileWithProgressMarkup(XGETaskFilePath, Actions.Count, (Sender, Args) => { if (Actions[0].OutputEventHandler != null) { Actions[0].OutputEventHandler(Sender, Args); } else { Console.WriteLine(Args.Data); } }); } } } return(XGEResult); }
/** * The actual function to run in a thread. This is potentially long and blocking */ 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 (BuildConfiguration.bPrintDebugInfo) { Log.TraceVerbose("Executing: {0} {1}", ExpandedCommandPath, ActionStartInfo.Arguments); } if (Action.bPrintDebugInfo) { Log.TraceInformation("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; // send telemetry { // See if the action produced a PCH file (infer from the extension as we no longer have the compile environments used to generate the actions. var ActionProducedPCH = Action.ProducedItems.Find(fileItem => new[] { ".PCH", ".GCH" }.Contains(Path.GetExtension(fileItem.AbsolutePath).ToUpperInvariant())); if (ActionProducedPCH != null && File.Exists(ActionProducedPCH.AbsolutePath)) // File may not exist if we're building remotely { // If we had a valid match for a PCH item, send an event. Telemetry.SendEvent("PCHTime.2", "ExecutorType", "Local", // Use status description because on VC tool chains it tells us more details about shared PCHs and their source modules. "Filename", Action.StatusDescription, // Get the length from the OS instead of the FileItem.Length is really for when the file is used as an input, // so the stored length is absent for a new for or out of date at best. "FileSize", new FileInfo(ActionProducedPCH.AbsolutePath).Length.ToString(), "Duration", Action.Duration.TotalSeconds.ToString("0.00")); } } if (!Utils.IsRunningOnMono) { // let RPCUtilHelper clean up anything thread related RPCUtilHelper.OnThreadComplete(); } // we are done!! bComplete = true; }