blob: c246a40a0569477775f8d51e86e48f8068892cb6 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.util;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import java.io.PrintStream;
import javax.annotation.Nullable;
/**
* Executes a {@link Process} and blocks until it is finished.
*/
public class ProcessExecutor {
private final PrintStream stdOutStream;
private final PrintStream stdErrStream;
private final Ansi ansi;
/**
* Creates a new {@link ProcessExecutor} with the specified parameters used for writing the output
* of the process.
*/
public ProcessExecutor(Console console) {
Preconditions.checkNotNull(console);
this.stdOutStream = console.getStdOut();
this.stdErrStream = console.getStdErr();
this.ansi = console.getAnsi();
}
/**
* Convenience method for {@link #execute(Process, boolean, boolean, boolean)} with boolean values
* set to {@code false}.
*/
public Result execute(Process process) {
return execute(process,
/* shouldPrintStdOut */ false,
/* shouldPrintStdErr */ false,
/* isSilent */ false);
}
/**
* Executes the specified process.
* @param process The {@code Process} to execute.
* @param shouldPrintStdOut If {@code true}, then the stdout of the process will be written
* directly to the stdout passed to the constructor of this executor. If {@code false}, then
* the stdout of the process will be made available via {@link Result#getStdout()}.
* @param shouldPrintStdErr If {@code true}, then the stderr of the process will be written
* directly to the stderr passed to the constructor of this executor. If {@code false}, then
* the stderr of the process will be made available via {@link Result#getStderr()}.
*/
public Result execute(
Process process,
boolean shouldPrintStdOut,
boolean shouldPrintStdErr,
boolean isSilent) {
// Read stdout/stderr asynchronously while running a Process.
// See http://stackoverflow.com/questions/882772/capturing-stdout-when-calling-runtime-exec
@SuppressWarnings("resource")
PrintStream stdOutToWriteTo = shouldPrintStdOut ?
stdOutStream : new CapturingPrintStream();
InputStreamConsumer stdOut = new InputStreamConsumer(
process.getInputStream(),
stdOutToWriteTo,
ansi);
@SuppressWarnings("resource")
PrintStream stdErrToWriteTo = shouldPrintStdErr ?
stdErrStream : new CapturingPrintStream();
InputStreamConsumer stdErr = new InputStreamConsumer(
process.getErrorStream(),
stdErrToWriteTo,
ansi);
// Consume the streams so they do not deadlock.
Thread stdOutConsumer = new Thread(stdOut);
stdOutConsumer.start();
Thread stdErrConsumer = new Thread(stdErr);
stdErrConsumer.start();
// Block until the Process completes.
try {
process.waitFor();
stdOutConsumer.join();
stdErrConsumer.join();
} catch (InterruptedException e) {
// Buck was killed while waiting for the consumers to finish. This means either the user
// killed the process or a step failed causing us to kill all other running steps. Neither of
// these is an exceptional situation.
return new Result(1, /* stdout */ null, /* stderr */ null);
}
String stdoutText = getDataIfNotPrinted(stdOutToWriteTo, shouldPrintStdOut);
String stderrText = getDataIfNotPrinted(stdErrToWriteTo, shouldPrintStdErr);
// Report the exit code of the Process.
int exitCode = process.exitValue();
// If the command has failed and we're not being explicitly quiet, ensure everything gets
// printed.
if (exitCode != 0 && !isSilent) {
if (!shouldPrintStdOut) {
stdOutStream.print(stdoutText);
}
if (!shouldPrintStdErr) {
stdErrStream.print(stderrText);
}
}
return new Result(exitCode, stdoutText, stderrText);
}
@Nullable
private static String getDataIfNotPrinted(PrintStream printStream, boolean shouldPrint) {
if (!shouldPrint) {
CapturingPrintStream capturingPrintStream = (CapturingPrintStream)printStream;
return capturingPrintStream.getContentsAsString(Charsets.US_ASCII);
} else {
return null;
}
}
/**
* Values from the result of {@link ProcessExecutor#execute(Process, boolean, boolean, boolean)}.
*/
public static class Result {
private final int exitCode;
@Nullable private final String stdout;
@Nullable private final String stderr;
public Result(int exitCode, @Nullable String stdOut, @Nullable String stderr) {
this.exitCode = exitCode;
this.stdout = stdOut;
this.stderr = stderr;
}
public int getExitCode() {
return exitCode;
}
@Nullable
public String getStdout() {
return stdout;
}
@Nullable
public String getStderr() {
return stderr;
}
}
}