aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/util
diff options
context:
space:
mode:
authorGravatar cparsons <cparsons@google.com>2018-04-16 11:49:24 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-04-16 11:51:10 -0700
commit835814895fffef00fe88658f91e4550dc61546cf (patch)
tree1d3c200bd86087f547421e1a761bec5c8a6034e5 /src/main/java/com/google/devtools/build/lib/util
parent8f495c97762ec7d23c9f338da971fee30b2800e1 (diff)
It breaks downstream rules_nodejs. See https://github.com/bazelbuild/bazel/issues/5028 for details. RELNOTES: None. PiperOrigin-RevId: 193074798
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/util')
-rw-r--r--src/main/java/com/google/devtools/build/lib/util/CommandBuilder.java121
1 files changed, 108 insertions, 13 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/util/CommandBuilder.java b/src/main/java/com/google/devtools/build/lib/util/CommandBuilder.java
index 9bff531521..d60fd94dc4 100644
--- a/src/main/java/com/google/devtools/build/lib/util/CommandBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/util/CommandBuilder.java
@@ -14,44 +14,65 @@
package com.google.devtools.build.lib.util;
+import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.devtools.build.lib.shell.Command;
import com.google.devtools.build.lib.vfs.Path;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
- * Implements {@link Command} builder.
+ * Implements OS aware {@link Command} builder. At this point only Linux, Mac
+ * and Windows XP are supported.
+ *
+ * <p>Builder will also apply heuristic to identify trivial cases where
+ * unix-like command lines could be automatically converted into the
+ * Windows-compatible form.
*
- * <p>TODO(bazel-team): (2010) Some of the code here is very similar to the {@link
- * com.google.devtools.build.lib.shell.Shell} class. This should be looked at.
+ * <p>TODO(bazel-team): (2010) Some of the code here is very similar to the
+ * {@link com.google.devtools.build.lib.shell.Shell} class. This should be looked at.
*/
public final class CommandBuilder {
- private final ArrayList<String> argv = new ArrayList<>();
- private final HashMap<String, String> env = new HashMap<>();
- private final File workingDir;
+ private static final ImmutableList<String> SHELLS = ImmutableList.of("/bin/sh", "/bin/bash");
+
+ private static final Splitter ARGV_SPLITTER = Splitter.on(CharMatcher.anyOf(" \t"));
- public CommandBuilder(Path workingDir) {
- this(workingDir.getPathFile());
+ private final OS system;
+ private final List<String> argv = new ArrayList<>();
+ private final Map<String, String> env = new HashMap<>();
+ private File workingDir = null;
+ private boolean useShell = false;
+
+ public CommandBuilder() {
+ this(OS.getCurrent());
}
- public CommandBuilder(File workingDir) {
- this.workingDir = Preconditions.checkNotNull(workingDir);
+ @VisibleForTesting
+ CommandBuilder(OS system) {
+ this.system = system;
}
public CommandBuilder addArg(String arg) {
- Preconditions.checkNotNull(arg);
+ Preconditions.checkNotNull(arg, "Argument must not be null");
argv.add(arg);
return this;
}
public CommandBuilder addArgs(Iterable<String> args) {
- Preconditions.checkArgument(!Iterables.contains(args, null));
+ Preconditions.checkArgument(!Iterables.contains(args, null), "Arguments must not be null");
Iterables.addAll(argv, args);
return this;
}
@@ -77,8 +98,82 @@ public final class CommandBuilder {
return this;
}
+ public CommandBuilder setWorkingDir(Path path) {
+ Preconditions.checkNotNull(path);
+ workingDir = path.getPathFile();
+ return this;
+ }
+
+ public CommandBuilder useTempDir() {
+ workingDir = new File(JAVA_IO_TMPDIR.value());
+ return this;
+ }
+
+ public CommandBuilder useShell(boolean useShell) {
+ this.useShell = useShell;
+ return this;
+ }
+
+ private boolean argvStartsWithSh() {
+ return argv.size() >= 2 && SHELLS.contains(argv.get(0)) && "-c".equals(argv.get(1));
+ }
+
+ private String[] transformArgvForLinux() {
+ // If command line already starts with "/bin/sh -c", ignore useShell attribute.
+ if (useShell && !argvStartsWithSh()) {
+ // c.g.io.base.shell.Shell.shellify() actually concatenates argv into the space-separated
+ // string here. Not sure why, but we will do the same.
+ return new String[] { "/bin/sh", "-c", Joiner.on(' ').join(argv) };
+ }
+ return argv.toArray(new String[argv.size()]);
+ }
+
+ private String[] transformArgvForWindows() {
+ List<String> modifiedArgv;
+ // Heuristic: replace "/bin/sh -c" with something more appropriate for Windows.
+ if (argvStartsWithSh()) {
+ useShell = true;
+ modifiedArgv = Lists.newArrayList(argv.subList(2, argv.size()));
+ } else {
+ modifiedArgv = Lists.newArrayList(argv);
+ }
+
+ if (!modifiedArgv.isEmpty()) {
+ // args can contain whitespace, so figure out the first word
+ String argv0 = modifiedArgv.get(0);
+ String command = ARGV_SPLITTER.split(argv0).iterator().next();
+
+ // Automatically enable CMD.EXE use if we are executing something else besides "*.exe" file.
+ // When use CMD.EXE to invoke a bat/cmd file, the file path must have '\' instead of '/'
+ if (!command.toLowerCase().endsWith(".exe")) {
+ useShell = true;
+ modifiedArgv.set(0, argv0.replace('/', '\\'));
+ }
+ } else {
+ // This is degenerate "/bin/sh -c" case. We ensure that Windows behavior is identical
+ // to the Linux - call shell that will do nothing.
+ useShell = true;
+ }
+ if (useShell) {
+ // /S - strip first and last quotes and execute everything else as is.
+ // /E:ON - enable extended command set.
+ // /V:ON - enable delayed variable expansion
+ // /D - ignore AutoRun registry entries.
+ // /C - execute command. This must be the last option before the command itself.
+ return new String[] { "CMD.EXE", "/S", "/E:ON", "/V:ON", "/D", "/C",
+ Joiner.on(' ').join(modifiedArgv) };
+ } else {
+ return modifiedArgv.toArray(new String[argv.size()]);
+ }
+ }
+
public Command build() {
+ Preconditions.checkState(system != OS.UNKNOWN, "Unidentified operating system");
+ Preconditions.checkNotNull(workingDir, "Working directory must be set");
Preconditions.checkState(!argv.isEmpty(), "At least one argument is expected");
- return new Command(argv.toArray(new String[0]), env, workingDir);
+
+ return new Command(
+ system == OS.WINDOWS ? transformArgvForWindows() : transformArgvForLinux(),
+ env, workingDir);
}
}