Implement an abstraction layer over java.lang.Process so that the Windows implementation can eventually be plugged in.
--
MOS_MIGRATED_REVID=126404913
diff --git a/src/main/java/com/google/devtools/build/lib/shell/Command.java b/src/main/java/com/google/devtools/build/lib/shell/Command.java
index d4a69dc..e5a958c 100644
--- a/src/main/java/com/google/devtools/build/lib/shell/Command.java
+++ b/src/main/java/com/google/devtools/build/lib/shell/Command.java
@@ -15,12 +15,13 @@
package com.google.devtools.build.lib.shell;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.shell.SubprocessBuilder.StreamAction;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.lang.ProcessBuilder.Redirect;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
@@ -125,8 +126,6 @@
*/
public static final byte[] NO_INPUT = new byte[0];
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
/**
* Pass this to {@link #execute(byte[], KillableObserver, boolean)} to
* indicate that you do not wish to observe / kill the underlying
@@ -143,25 +142,11 @@
}
};
- private final ProcessBuilder processBuilder;
+ private final SubprocessBuilder subprocessBuilder;
// Start of public API -----------------------------------------------------
/**
- * Creates a new {@link Command} that will execute a command line that
- * is described by a {@link ProcessBuilder}. Command line elements,
- * environment, and working directory are taken from this object. The
- * command line is executed exactly as given, without a shell.
- *
- * @param processBuilder {@link ProcessBuilder} describing command line
- * to execute
- */
- public Command(final ProcessBuilder processBuilder) {
- this(processBuilder.command().toArray(EMPTY_STRING_ARRAY),
- processBuilder.environment(),
- processBuilder.directory());
- }
-
/**
* Creates a new {@link Command} for the given command line elements. The
* command line is executed exactly as given, without a shell.
@@ -214,22 +199,17 @@
commandLineElements[0] = new File(workingDirectory, commandLineElements[0]).getAbsolutePath();
}
- this.processBuilder =
- new ProcessBuilder(commandLineElements);
- if (environmentVariables != null) {
- // TODO(bazel-team) remove next line eventually; it is here to mimic old
- // Runtime.exec() behavior
- this.processBuilder.environment().clear();
- this.processBuilder.environment().putAll(environmentVariables);
- }
- this.processBuilder.directory(workingDirectory);
+ this.subprocessBuilder = new SubprocessBuilder();
+ subprocessBuilder.setArgv(ImmutableList.copyOf(commandLineElements));
+ subprocessBuilder.setEnv(environmentVariables);
+ subprocessBuilder.setWorkingDirectory(workingDirectory);
}
/**
* @return raw command line elements to be executed
*/
public String[] getCommandLineElements() {
- final List<String> elements = processBuilder.command();
+ final List<String> elements = subprocessBuilder.getArgv();
return elements.toArray(new String[elements.size()]);
}
@@ -237,7 +217,7 @@
* @return (unmodifiable) {@link Map} view of command's environment variables
*/
public Map<String, String> getEnvironmentVariables() {
- return Collections.unmodifiableMap(processBuilder.environment());
+ return subprocessBuilder.getEnv();
}
/**
@@ -245,7 +225,7 @@
* working directory is used
*/
public File getWorkingDirectory() {
- return processBuilder.directory();
+ return subprocessBuilder.getWorkingDirectory();
}
/**
@@ -446,31 +426,23 @@
throws CommandException {
nullCheck(stdinInput, "stdinInput");
nullCheck(observer, "observer");
- processBuilder.redirectOutput(redirectToFileOrDevNull(stdOut));
- processBuilder.redirectError(redirectToFileOrDevNull(stdErr));
+ if (stdOut == null) {
+ subprocessBuilder.setStdout(StreamAction.DISCARD);
+ } else {
+ subprocessBuilder.setStdout(stdOut);
+ }
+
+ if (stdErr == null) {
+ subprocessBuilder.setStderr(StreamAction.DISCARD);
+ } else {
+ subprocessBuilder.setStderr(stdErr);
+ }
return doExecute(
new ByteArrayInputSource(stdinInput), observer, null, killSubprocessOnInterrupt, false)
.get();
}
/**
- * Returns a {@link ProcessBuilder.Redirect} that writes process output to {@code file} or to
- * /dev/null in case {@code file} is null. If {@code file} exists, it is deleted before
- * redirecting to it.
- */
- private Redirect redirectToFileOrDevNull(File file) {
- if (file == null) {
- return Redirect.to(new File("/dev/null"));
- }
- // We need to use Redirect.appendTo() here, because on older Linux kernels writes are otherwise
- // not atomic and might result in lost log messages: https://lkml.org/lkml/2014/3/3/308
- if (file.exists()) {
- file.delete();
- }
- return Redirect.appendTo(file);
- }
-
- /**
* Execute this command with given input to stdin; this stream is closed when the process
* terminates, and exceptions raised when closing this stream are ignored. This call blocks until
* the process completes or an error occurs. The caller provides {@link OutputStream} instances
@@ -706,7 +678,7 @@
logCommand();
- final Process process = startProcess();
+ final Subprocess process = startProcess();
if (outErrConsumers != null) {
outErrConsumers.logConsumptionStrategy();
@@ -745,10 +717,10 @@
};
}
- private Process startProcess()
+ private Subprocess startProcess()
throws ExecFailedException {
try {
- return processBuilder.start();
+ return subprocessBuilder.start();
} catch (IOException ioe) {
throw new ExecFailedException(this, ioe);
}
@@ -809,8 +781,7 @@
}
}
- private static void processInput(final InputSource stdinInput,
- final Process process) {
+ private static void processInput(InputSource stdinInput, Subprocess process) {
if (log.isLoggable(Level.FINER)) {
log.finer(stdinInput.toLogString("stdin"));
}
@@ -836,15 +807,15 @@
}
}
- private static Killable observeProcess(final Process process,
- final KillableObserver observer) {
+ private static Killable observeProcess(Subprocess process,
+ final KillableObserver observer) {
final Killable processKillable = new ProcessKillable(process);
observer.startObserving(processKillable);
return processKillable;
}
private CommandResult waitForProcessToComplete(
- final Process process,
+ final Subprocess process,
final KillableObserver observer,
final Killable processKillable,
final Consumers.OutErrConsumers outErr,
@@ -903,7 +874,7 @@
}
}
- private static TerminationStatus waitForProcess(Process process,
+ private static TerminationStatus waitForProcess(Subprocess process,
boolean killSubprocessOnInterrupt) {
boolean wasInterrupted = false;
try {
@@ -941,15 +912,15 @@
public String toDebugString() {
StringBuilder message = new StringBuilder(128);
message.append("Executing (without brackets):");
- for (final String arg : processBuilder.command()) {
+ for (String arg : subprocessBuilder.getArgv()) {
message.append(" [");
message.append(arg);
message.append(']');
}
message.append("; environment: ");
- message.append(processBuilder.environment());
- final File workingDirectory = processBuilder.directory();
+ message.append(subprocessBuilder.getEnv());
message.append("; working dir: ");
+ File workingDirectory = subprocessBuilder.getWorkingDirectory();
message.append(workingDirectory == null ?
"(current)" :
workingDirectory.toString());