예제 #1
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;
        }
예제 #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())
                    {
                        // 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);
        }