Use a CommandLine to represent executable arguments in SpawnAction

This reduced analysis memory usage for actions that construct complex
argument lists.

PiperOrigin-RevId: 218388661
diff --git a/src/main/java/com/google/devtools/build/lib/actions/CommandLine.java b/src/main/java/com/google/devtools/build/lib/actions/CommandLine.java
index b87e5a6..1516fb0 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/CommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/CommandLine.java
@@ -16,9 +16,9 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
 import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.collect.IterablesChain;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
 import com.google.devtools.build.lib.util.Fingerprint;
@@ -94,13 +94,13 @@
 
     @Override
     public Iterable<String> arguments() throws CommandLineExpansionException {
-      return Iterables.concat(executableArgs, commandLine.arguments());
+      return IterablesChain.concat(executableArgs, commandLine.arguments());
     }
 
     @Override
     public Iterable<String> arguments(ArtifactExpander artifactExpander)
         throws CommandLineExpansionException {
-      return Iterables.concat(executableArgs, commandLine.arguments(artifactExpander));
+      return IterablesChain.concat(executableArgs, commandLine.arguments(artifactExpander));
     }
   }
 
@@ -118,13 +118,13 @@
 
     @Override
     public Iterable<String> arguments() throws CommandLineExpansionException {
-      return Iterables.concat(commandLine.arguments(), executableArgs);
+      return IterablesChain.concat(commandLine.arguments(), executableArgs);
     }
 
     @Override
     public Iterable<String> arguments(ArtifactExpander artifactExpander)
         throws CommandLineExpansionException {
-      return Iterables.concat(commandLine.arguments(artifactExpander), executableArgs);
+      return IterablesChain.concat(commandLine.arguments(artifactExpander), executableArgs);
     }
   }
 
@@ -137,6 +137,9 @@
     if (executableArgs.isEmpty()) {
       return commandLine;
     }
+    if (commandLine == EMPTY) {
+      return CommandLine.of(executableArgs);
+    }
     return new PrefixedCommandLine(executableArgs, commandLine);
   }
 
@@ -149,6 +152,9 @@
     if (args.isEmpty()) {
       return commandLine;
     }
+    if (commandLine == EMPTY) {
+      return CommandLine.of(args);
+    }
     return new SuffixedCommandLine(args, commandLine);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
index e3ba208..c3c268c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
@@ -22,7 +22,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.devtools.build.lib.actions.AbstractAction;
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.ActionEnvironment;
@@ -76,7 +75,6 @@
 import com.google.errorprone.annotations.FormatMethod;
 import com.google.errorprone.annotations.FormatString;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -569,9 +567,7 @@
     private boolean isShellCommand = false;
     private boolean useDefaultShellEnvironment = false;
     protected boolean executeUnconditionally;
-    private PathFragment executable;
-    // executableArgs does not include the executable itself.
-    private List<String> executableArgs;
+    private CustomCommandLine.Builder executableArgs;
     private List<CommandLineAndParamFileInfo> commandLines = new ArrayList<>();
 
     private CharSequence progressMessage;
@@ -598,10 +594,7 @@
       this.executionInfo = other.executionInfo;
       this.isShellCommand = other.isShellCommand;
       this.useDefaultShellEnvironment = other.useDefaultShellEnvironment;
-      this.executable = other.executable;
-      this.executableArgs = (other.executableArgs != null)
-          ? Lists.newArrayList(other.executableArgs)
-          : null;
+      this.executableArgs = other.executableArgs;
       this.commandLines = new ArrayList<>(other.commandLines);
       this.progressMessage = other.progressMessage;
       this.mnemonic = other.mnemonic;
@@ -631,10 +624,9 @@
 
     @VisibleForTesting @CheckReturnValue
     public Action[] build(ActionOwner owner, BuildConfiguration configuration) {
-      ImmutableList<String> executableArgs = buildExecutableArgs();
       Action[] actions = new Action[1];
       CommandLines.Builder result = CommandLines.builder();
-      result.addCommandLine(CommandLine.of(executableArgs));
+      result.addCommandLine(executableArgs.build());
       for (CommandLineAndParamFileInfo pair : this.commandLines) {
         result.addCommandLine(pair);
       }
@@ -653,8 +645,7 @@
     @CheckReturnValue
     SpawnAction buildForActionTemplate(ActionOwner owner) {
       CommandLines.Builder result = CommandLines.builder();
-      ImmutableList<String> executableArgs = buildExecutableArgs();
-      result.addCommandLine(CommandLine.of(executableArgs));
+      result.addCommandLine(executableArgs.build());
       for (CommandLineAndParamFileInfo pair : commandLines) {
         result.addCommandLine(pair.commandLine);
       }
@@ -749,16 +740,6 @@
           extraActionInfoSupplier);
     }
 
-    private ImmutableList<String> buildExecutableArgs() {
-      Preconditions.checkNotNull(executable);
-      Preconditions.checkNotNull(executableArgs);
-
-      return ImmutableList.<String>builder()
-          .add(executable.getPathString())
-          .addAll(executableArgs)
-          .build();
-    }
-
     /**
      * Adds an artifact that is necessary for executing the spawn itself (e.g. a compiler), in
      * contrast to an artifact that is necessary for the spawn to do its work (e.g. source code).
@@ -934,8 +915,7 @@
      * {@link #setShellCommand(String)}.
      */
     public Builder setExecutable(PathFragment executable) {
-      this.executable = executable;
-      this.executableArgs = Lists.newArrayList();
+      this.executableArgs = CustomCommandLine.builder().addPath(executable);
       this.isShellCommand = false;
       return this;
     }
@@ -982,11 +962,12 @@
 
     private Builder setJavaExecutable(PathFragment javaExecutable, Artifact deployJar,
         List<String> jvmArgs, String... launchArgs) {
-      this.executable = javaExecutable;
-      this.executableArgs = Lists.newArrayList();
-      executableArgs.add("-Xverify:none");
-      executableArgs.addAll(jvmArgs);
-      Collections.addAll(executableArgs, launchArgs);
+      this.executableArgs =
+          CustomCommandLine.builder()
+              .addPath(javaExecutable)
+              .add("-Xverify:none")
+              .addAll(ImmutableList.copyOf(jvmArgs))
+              .addAll(ImmutableList.copyOf(launchArgs));
       toolsBuilder.add(deployJar);
       this.isShellCommand = false;
       return this;
@@ -1032,9 +1013,9 @@
      * #setExecutable(Artifact)}, {@link #setJavaExecutable}, or {@link #setShellCommand(String)}.
      */
     public Builder setShellCommand(PathFragment shExecutable, String command) {
-      this.executable = shExecutable;
       // 0=shell command switch, 1=command
-      this.executableArgs = Lists.newArrayList("-c", command);
+      this.executableArgs =
+          CustomCommandLine.builder().addPath(shExecutable).add("-c").addDynamicString(command);
       this.isShellCommand = true;
       return this;
     }
@@ -1044,9 +1025,7 @@
      * commands to be executed.
      */
     public Builder setShellCommand(Iterable<String> command) {
-      this.executable = PathFragment.create(Iterables.getFirst(command, null));
-      // The first item of the commands is the shell executable that should be used.
-      this.executableArgs = ImmutableList.copyOf(Iterables.skip(command, 1));
+      this.executableArgs = CustomCommandLine.builder().addAll(ImmutableList.copyOf(command));
       this.isShellCommand = true;
       return this;
     }
@@ -1062,22 +1041,26 @@
       return this;
     }
 
-    /**
-     * Appends the arguments to the list of executable arguments.
-     */
+    /** Returns a {@link CustomCommandLine.Builder} for executable arguments. */
+    public CustomCommandLine.Builder executableArguments() {
+      Preconditions.checkState(executableArgs != null);
+      return this.executableArgs;
+    }
+
+    /** Appends the arguments to the list of executable arguments. */
     public Builder addExecutableArguments(String... arguments) {
       Preconditions.checkState(executableArgs != null);
-      Collections.addAll(executableArgs, arguments);
+      this.executableArgs.addAll(ImmutableList.copyOf(arguments));
       return this;
     }
 
     /**
-     * Add multiple arguments in the order they are returned by the collection
-     * to the list of executable arguments.
+     * Add multiple arguments in the order they are returned by the collection to the list of
+     * executable arguments.
      */
     public Builder addExecutableArguments(Iterable<String> arguments) {
       Preconditions.checkState(executableArgs != null);
-      Iterables.addAll(executableArgs, arguments);
+      this.executableArgs.addAll(ImmutableList.copyOf(arguments));
       return this;
     }