예제 #1
0
        /**
         * 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));
        }
예제 #2
0
        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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        /// <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());
        }
예제 #5
0
        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);
        }
예제 #6
0
파일: XGE.cs 프로젝트: kidaa/UnrealEngineVR
        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);
        }
예제 #7
0
        /**
         * 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;
        }