/// <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; } }