/// <summary> /// Initializes a new instance of the ProcessInvoker class. /// </summary> /// <param name="workingDirectory">The working directory the process is started in.</param> /// <param name="executable">The executable to run.</param> /// <param name="args">The command line arguments to provide to the executable.</param> /// <param name="failureBase">Not sure what this is -- some debugging/diagnostic thing.</param> /// <param name="captureStdout">Where to (optionally) capture standard out.</param> /// <param name="dbgText">Debugging text for something or another.</param> /// <param name="allowAbsoluteExe">Whether to allow an absolute (rather than relative) file path to the executable.</param> /// <param name="allowAbsoluteArgs">Whether to allow absolute (rather than relative) file paths as arguments.</param> /// <param name="workingDirOverride">The working directory to use.</param> public ProcessInvoker( WorkingDirectory workingDirectory, string executable, string[] args, BuildObject failureBase, BuildObject captureStdout = null, string dbgText = null, bool allowAbsoluteExe = false, bool allowAbsoluteArgs = false, string workingDirOverride = null) { // Catch bad verb authors before they hurt themselves. Util.Assert(allowAbsoluteExe || !executable.Contains(":")); // Hey, this looks like an absolute path! Use .getRelativePath(); it makes your output more stable. foreach (string arg in args) { // Pardon my distasteful heuristic to avoid flagging /flag:value args. Util.Assert(allowAbsoluteArgs || arg.Length < 2 || arg[1] != ':'); // Hey, this looks like an absolute path! Use .getRelativePath() to tolerate crossing machine boundaries. } this.workingDirectory = workingDirectory; this.stdout = new StringBuilder(); this.stderr = new StringBuilder(); using (Job job = new Job()) { using (Process proc = new Process()) { if (allowAbsoluteExe) { proc.StartInfo.FileName = executable; } else { // TODO: *All* async verbs need to list their executable (and all the libs it depends upon) as dependencies. proc.StartInfo.FileName = workingDirectory.PathTo(executable); } // TODO Is there a better way to escape the args to avoid problems with spaces? proc.StartInfo.Arguments = string.Join(" ", args); proc.StartInfo.WorkingDirectory = workingDirOverride == null ? workingDirectory.Root : workingDirOverride; proc.StartInfo.RedirectStandardOutput = true; // REVIEW: Maybe we should always capture stdout in a StringBuilder and just write it out to a file afterwards if requested? if (captureStdout != null) { this.tmpStdout = new BuildObject(captureStdout.getRelativePath() + ".tmp"); this.stdoutFile = new StreamWriter(workingDirectory.PathTo(this.tmpStdout)); proc.OutputDataReceived += new DataReceivedEventHandler(this.StdoutRedirectHandler); } else { // Collect stdout here for diagnostics. proc.OutputDataReceived += new DataReceivedEventHandler(this.StdoutHandler); } proc.StartInfo.RedirectStandardError = true; proc.ErrorDataReceived += new DataReceivedEventHandler(this.StderrHandler); proc.StartInfo.UseShellExecute = false; string commandLine = proc.StartInfo.FileName + " " + proc.StartInfo.Arguments; if (failureBase != null && AlwaysEmitDiagnostics) { // In diagnostic mode, we emit the command line twice, once ahead in case Boogie decides // to run away and never come back. BuildObject failureBatObj = failureBase.makeOutputObject(".bat"); workingDirectory.CreateDirectoryFor(failureBatObj); File.WriteAllText(workingDirectory.PathTo(failureBatObj), commandLine); } proc.Start(); job.AddProcess(proc); proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); proc.WaitForExit(); this.cpuTime = job.GetCpuTime().TotalSeconds; this.exitCode = proc.ExitCode; if (this.stdoutFile != null) { this.stdoutFile.Close(); } if (failureBase != null && AlwaysEmitDiagnostics) { workingDirectory.CreateDirectoryFor(failureBase); File.WriteAllText(workingDirectory.PathTo(failureBase.makeOutputObject(".bat")), commandLine); File.WriteAllText(workingDirectory.PathTo(failureBase.makeOutputObject(".txt")), dbgText); File.WriteAllText(workingDirectory.PathTo(failureBase.makeOutputObject(".stdout")), this.GetStdoutString()); File.WriteAllText(workingDirectory.PathTo(failureBase.makeOutputObject(".stderr")), this.GetStderr()); } } } // REVIEW: Add Delete, Exists and Move methods to WorkingDirectory class? if (this.tmpStdout != null && File.Exists(workingDirectory.PathTo(this.tmpStdout))) { // REVIEW: Nothing should be here. Bother with the delete? File.Delete(workingDirectory.PathTo(captureStdout)); File.Move(workingDirectory.PathTo(this.tmpStdout), workingDirectory.PathTo(captureStdout)); this.tmpStdout = null; } }
public ProcessInvoker( string executable, string[] args, RcHandling rcHandling, BuildObject failureBase, string finalStdoutPath = null, string dbgText = null, bool allowAbsoluteExe = false, bool allowAbsoluteArgs = false, string workingDir = null) { Util.Assert(allowAbsoluteExe || !executable.Contains(":")); //- Hey, this looks like an absolute path! Use .getRelativePath() to tolerate crossing machine boundaries. foreach (string arg in args) { //- Pardon my distasteful heuristic to avoid flagging /flag:value args. Util.Assert(allowAbsoluteArgs || arg.Length < 2 || arg[1] != ':'); //- Hey, this looks like an absolute path! Use .getRelativePath() to tolerate crossing machine boundaries. } this.rcHandling = rcHandling; stdout = new StringBuilder(); stderr = new StringBuilder(); using (Job job = new Job()) { using (Process proc = new Process()) { proc.StartInfo.FileName = Path.Combine(BuildEngine.theEngine.getIronRoot(), executable); //- TODO Is there a better way to escape the args to avoid problems with spaces? proc.StartInfo.Arguments = String.Join(" ", args); proc.StartInfo.WorkingDirectory = workingDir == null?BuildEngine.theEngine.getIronRoot() : workingDir; proc.StartInfo.RedirectStandardOutput = true; if (finalStdoutPath != null) { _tmpStdoutPath = finalStdoutPath + ".tmp"; stdoutFile = new StreamWriter(_tmpStdoutPath); proc.OutputDataReceived += new DataReceivedEventHandler(stdoutRedirectHandler); } else { //- collect stdout here for diagnostics. proc.OutputDataReceived += new DataReceivedEventHandler(stdoutHandler); } proc.StartInfo.RedirectStandardError = true; proc.ErrorDataReceived += new DataReceivedEventHandler(stderrHandler); proc.StartInfo.UseShellExecute = false; string commandLine = proc.StartInfo.FileName + " " + proc.StartInfo.Arguments; if (alwaysEmitDiagnostics) { //- In diagnostic mode, we emit the command line twice, once ahead in case Boogie decides //- to run away and never come back. BuildObject failureBatObj = failureBase.makeOutputObject(".bat"); failureBatObj.prepareObjDirectory(); File.WriteAllText(failureBatObj.getFilesystemPath(), commandLine); } proc.Start(); job.AddProcess(proc); proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); proc.WaitForExit(); cpuTime = job.GetCpuTime().TotalSeconds; exitCode = proc.ExitCode; if (stdoutFile != null) { stdoutFile.Close(); } if (passed()) { disposition = new Fresh(); } else { //- sheesh. Some tools emit error messages to stdout. Failed f = new Failed(getStdoutString() + stderr.ToString()); f.AddError("Command line: " + commandLine + "\n"); disposition = f; } #pragma warning disable 429 //- alwaysEmitDiagnostics is a compile-time constant that can hide expression !passed() if (failureBase != null && (alwaysEmitDiagnostics || !passed())) #pragma warning restore 429 { failureBase.prepareObjDirectory(); File.WriteAllText(failureBase.makeOutputObject(".bat").getFilesystemPath(), commandLine); File.WriteAllText(failureBase.makeOutputObject(".txt").getFilesystemPath(), dbgText); File.WriteAllText(failureBase.makeOutputObject(".stdout").getFilesystemPath(), getStdoutString()); File.WriteAllText(failureBase.makeOutputObject(".stderr").getFilesystemPath(), getStderr()); } } } if (passed() && _tmpStdoutPath != null) { File.Delete(finalStdoutPath); File.Move(_tmpStdoutPath, finalStdoutPath); _tmpStdoutPath = null; } }