Support multiple command lines / params files in SpawnAction.

This is necessary for the upcoming Skylark implementation of param files.

PiperOrigin-RevId: 168744486
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
index bfc88be..e3139b4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
@@ -715,15 +715,6 @@
     }
 
     /**
-     * Concatenates the passed prefix string and the object's string representation.
-     *
-     * <p>Prefer {@link Builder#addPrefixed}, as it will be more memory efficient.
-     */
-    Builder addWithDynamicPrefix(String prefix, @Nullable Object arg) {
-      return addPrefixedInternal(prefix, arg);
-    }
-
-    /**
      * Adds the passed strings to the command line.
      *
      * <p>If you are converting long lists or nested sets of a different type to string lists,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java
deleted file mode 100644
index 01781d4..0000000
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// 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.google.devtools.build.lib.analysis.actions;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.devtools.build.lib.actions.ActionOwner;
-import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.actions.CommandLineExpansionException;
-import com.google.devtools.build.lib.actions.ParameterFile;
-import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
-import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
-import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.vfs.PathFragment;
-import java.util.List;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-/**
- * A command-line implementation that wraps another command line and puts the arguments in a
- * parameter file if necessary
- *
- * <p>The Linux kernel has a limit for the command line length, and that can be easily reached
- * if, for example, a command is listing all its inputs on the command line.
- */
-@Immutable
-public final class ParamFileHelper {
-
-  /**
-   * Returns a params file artifact or null for a given command description.
-   *
-   * <p>Returns null if parameter files are not to be used according to paramFileInfo, or if the
-   * command line is short enough that a parameter file is not needed.
-   *
-   * <p>Make sure to add the returned artifact (if not null) as an input of the corresponding
-   * action.
-   *
-   * @param executableArgs leading arguments that should never be wrapped in a parameter file
-   * @param commandLine a {@link CommandLine} that provides the arguments (in addition to
-   *     executableArgs)
-   * @param paramFileInfo parameter file information
-   * @param configuration the configuration
-   * @param analysisEnvironment the analysis environment
-   * @param outputs outputs of the action (used to construct a filename for the params file)
-   */
-  static Artifact getParamsFileMaybe(
-      List<String> executableArgs,
-      CommandLine commandLine,
-      @Nullable ParamFileInfo paramFileInfo,
-      BuildConfiguration configuration,
-      AnalysisEnvironment analysisEnvironment,
-      Iterable<Artifact> outputs) {
-    if (paramFileInfo == null) {
-      return null;
-    }
-    if (!paramFileInfo.always()
-        && getParamFileSize(executableArgs, commandLine) < configuration.getMinParamFileSize()) {
-      return null;
-    }
-
-    Artifact output = Iterables.getFirst(outputs, null);
-    Preconditions.checkNotNull(output);
-    PathFragment paramFilePath = ParameterFile.derivePath(output.getRootRelativePath());
-    return analysisEnvironment.getDerivedArtifact(paramFilePath, output.getRoot());
-  }
-
-  /**
-   * Creates a command line using an external params file.
-   *
-   * <p>Call this with the result of {@link #getParamsFileMaybe} if it is not null.
-   *
-   * @param executableArgs leading arguments that should never be wrapped in a parameter file
-   * @param paramFileInfo parameter file information
-   * @param parameterFile the output parameter file artifact
-   */
-  public static CommandLine createWithParamsFile(
-      ImmutableList<String> executableArgs, ParamFileInfo paramFileInfo, Artifact parameterFile) {
-    return CustomCommandLine.builder()
-        .addAll(executableArgs)
-        // This is actually a constant, but there is no way to suppress the warning
-        .addWithDynamicPrefix(paramFileInfo.getFlag(), parameterFile)
-        .build();
-  }
-
-  /**
-   * Creates an action to write the parameter file.
-   *
-   * @param commandLine a {@link CommandLine} that provides the arguments (in addition to
-   *     executableArgs)
-   * @param owner owner of the action
-   * @param parameterFile the output parameter file artifact
-   * @param paramFileInfo parameter file information
-   */
-  public static ParameterFileWriteAction createParameterFileWriteAction(
-      CommandLine commandLine,
-      ActionOwner owner,
-      Artifact parameterFile,
-      ParamFileInfo paramFileInfo) {
-    return new ParameterFileWriteAction(
-        owner, parameterFile, commandLine, paramFileInfo.getFileType(), paramFileInfo.getCharset());
-  }
-
-  /**
-   * Creates a command line without using a params file.
-   *
-   * <p>Call this if {@link #getParamsFileMaybe} returns null.
-   *
-   * @param executableArgs leading arguments that should never be wrapped in a parameter file
-   * @param commandLine a {@link CommandLine} that provides the arguments (in addition to
-   *     executableArgs)
-   */
-  public static CommandLine createWithoutParamsFile(
-      List<String> executableArgs, CommandLine commandLine) {
-    if (executableArgs.isEmpty()) {
-      return commandLine;
-    }
-    return CommandLine.concat(ImmutableList.copyOf(executableArgs), commandLine);
-  }
-
-  /** Estimates the params file size for the given arguments. */
-  private static int getParamFileSize(List<String> executableArgs, CommandLine commandLine) {
-    try {
-      Iterable<String> actualArguments = commandLine.arguments();
-      return getParamFileSize(executableArgs) + getParamFileSize(actualArguments);
-    } catch (CommandLineExpansionException e) {
-      // CommandLineExpansionException is thrown deterministically. We can ignore
-      // it here and pretend that a params file is not necessary at this stage,
-      // and an error will be thrown later at execution time.
-      return 0;
-    }
-  }
-
-  private static int getParamFileSize(Iterable<String> args) {
-    int size = 0;
-    for (String s : args) {
-      size += s.length() + 1;
-    }
-    return size;
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java
index 32ec7a8..33a9245 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java
@@ -14,6 +14,8 @@
 
 package com.google.devtools.build.lib.analysis.actions;
 
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
 import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.errorprone.annotations.CompileTimeConstant;
@@ -32,11 +34,7 @@
   private final String flag;
   private final boolean always;
 
-  public ParamFileInfo(
-      ParameterFileType fileType,
-      Charset charset,
-      @CompileTimeConstant String flag,
-      boolean always) {
+  private ParamFileInfo(ParameterFileType fileType, Charset charset, String flag, boolean always) {
     this.fileType = Preconditions.checkNotNull(fileType);
     this.charset = Preconditions.checkNotNull(charset);
     this.flag = Preconditions.checkNotNull(flag);
@@ -88,4 +86,42 @@
         && flag.equals(other.flag)
         && always == other.always;
   }
+
+  public static Builder builder(ParameterFileType parameterFileType) {
+    return new Builder(parameterFileType);
+  }
+
+  /** Builder for a ParamFileInfo. */
+  public static class Builder {
+    private final ParameterFileType fileType;
+    private Charset charset = ISO_8859_1;
+    private String flag = "@";
+    private boolean always;
+
+    private Builder(ParameterFileType fileType) {
+      this.fileType = fileType;
+    }
+
+    /** Sets the encoding to write the parameter file with. */
+    public Builder setCharset(Charset charset) {
+      this.charset = charset;
+      return this;
+    }
+
+    /** Sets a prefix to use for the flag that is passed to original command. */
+    public Builder setFlag(@CompileTimeConstant String flag) {
+      this.flag = flag;
+      return this;
+    }
+
+    /** Set whether the parameter file is always used, regardless of parameter file length. */
+    public Builder setUseAlways(boolean always) {
+      this.always = always;
+      return this;
+    }
+
+    public ParamFileInfo build() {
+      return new ParamFileInfo(fileType, charset, flag, always);
+    }
+  }
 }
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 8f690a4..ac9aa72 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
@@ -14,8 +14,6 @@
 
 package com.google.devtools.build.lib.analysis.actions;
 
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
@@ -31,6 +29,7 @@
 import com.google.devtools.build.lib.actions.ActionInputHelper;
 import com.google.devtools.build.lib.actions.ActionOwner;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
 import com.google.devtools.build.lib.actions.BaseSpawn;
 import com.google.devtools.build.lib.actions.CommandAction;
 import com.google.devtools.build.lib.actions.CommandLineExpansionException;
@@ -38,7 +37,7 @@
 import com.google.devtools.build.lib.actions.EmptyRunfilesSupplier;
 import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.ExecutionInfoSpecifier;
-import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
+import com.google.devtools.build.lib.actions.ParameterFile;
 import com.google.devtools.build.lib.actions.ResourceSet;
 import com.google.devtools.build.lib.actions.RunfilesSupplier;
 import com.google.devtools.build.lib.actions.Spawn;
@@ -62,7 +61,6 @@
 import com.google.errorprone.annotations.FormatMethod;
 import com.google.errorprone.annotations.FormatString;
 import com.google.protobuf.GeneratedMessage.GeneratedExtension;
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -521,6 +519,17 @@
    */
   public static class Builder {
 
+    private static class CommandLineAndParamFileInfo {
+      private final CommandLine commandLine;
+      @Nullable private final ParamFileInfo paramFileInfo;
+
+      private CommandLineAndParamFileInfo(
+          CommandLine commandLine, @Nullable ParamFileInfo paramFileInfo) {
+        this.commandLine = commandLine;
+        this.paramFileInfo = paramFileInfo;
+      }
+    }
+
     private final NestedSetBuilder<Artifact> toolsBuilder = NestedSetBuilder.stableOrder();
     private final NestedSetBuilder<Artifact> inputsBuilder = NestedSetBuilder.stableOrder();
     private final List<Artifact> outputs = new ArrayList<>();
@@ -535,10 +544,9 @@
     private PathFragment executable;
     // executableArgs does not include the executable itself.
     private List<String> executableArgs;
-    @Nullable private CommandLine commandLine;
+    private List<CommandLineAndParamFileInfo> commandLines = new ArrayList<>();
 
     private CharSequence progressMessage;
-    private ParamFileInfo paramFileInfo = null;
     private String mnemonic = "Unknown";
     protected ExtraActionInfoSupplier<?> extraActionInfoSupplier = null;
     private boolean disableSandboxing = false;
@@ -566,9 +574,8 @@
       this.executableArgs = (other.executableArgs != null)
           ? Lists.newArrayList(other.executableArgs)
           : null;
-      this.commandLine = other.commandLine;
+      this.commandLines = Lists.newArrayList(other.commandLines);
       this.progressMessage = other.progressMessage;
-      this.paramFileInfo = other.paramFileInfo;
       this.mnemonic = other.mnemonic;
     }
 
@@ -598,37 +605,91 @@
     @VisibleForTesting @CheckReturnValue
     public Action[] build(ActionOwner owner, AnalysisEnvironment analysisEnvironment,
         BuildConfiguration configuration) {
-      CommandLine commandLine = this.commandLine != null ? this.commandLine : CommandLine.EMPTY;
-      // Check to see if we need to use param file.
-      Artifact paramsFile = ParamFileHelper.getParamsFileMaybe(
-          buildExecutableArgs(configuration.getShellExecutable()),
-          commandLine,
-          paramFileInfo,
-          configuration,
-          analysisEnvironment,
-          outputs);
-
-      // If param file is to be used, set up the param file write action as well.
-      ParameterFileWriteAction paramFileWriteAction = null;
-      if (paramsFile != null) {
-        paramFileWriteAction =
-            ParamFileHelper.createParameterFileWriteAction(
-                commandLine, owner, paramsFile, paramFileInfo);
+      List<Action> paramFileActions = new ArrayList<>(commandLines.size());
+      CommandLine actualCommandLine =
+          buildCommandLine(owner, analysisEnvironment, configuration, paramFileActions);
+      Action[] actions = new Action[1 + paramFileActions.size()];
+      Action spawnAction =
+          buildSpawnAction(owner, actualCommandLine, configuration.getActionEnvironment());
+      actions[0] = spawnAction;
+      for (int i = 0; i < paramFileActions.size(); ++i) {
+        actions[i + 1] = paramFileActions.get(i);
       }
+      return actions;
+    }
 
-      List<Action> actions = new ArrayList<>(2);
-      actions.add(
-          buildSpawnAction(
-              owner,
-              commandLine,
-              configuration.getActionEnvironment(),
-              configuration.getShellExecutable(),
-              paramsFile));
-      if (paramFileWriteAction != null) {
-        actions.add(paramFileWriteAction);
+    private CommandLine buildCommandLine(
+        ActionOwner owner,
+        AnalysisEnvironment analysisEnvironment,
+        BuildConfiguration configuration,
+        List<Action> paramFileActions) {
+      ImmutableList<String> executableArgs =
+          buildExecutableArgs(configuration.getShellExecutable());
+      boolean hasConditionalParamFile =
+          commandLines.stream().anyMatch(c -> c.paramFileInfo != null && !c.paramFileInfo.always());
+      boolean spillToParamFiles = false;
+      if (hasConditionalParamFile) {
+        int totalLen = getParamFileSize(executableArgs);
+        for (CommandLineAndParamFileInfo commandLineAndParamFileInfo : commandLines) {
+          totalLen += getCommandLineSize(commandLineAndParamFileInfo.commandLine);
+        }
+        // To reduce implementation complexity we either spill all or none of the param files.
+        spillToParamFiles = totalLen > configuration.getMinParamFileSize();
       }
+      // We a name based on the output, starting at <output>-2.params
+      // and then incrementing
+      int paramFileNameSuffix = 2;
+      SpawnActionCommandLine.Builder result = new SpawnActionCommandLine.Builder();
+      result.addExecutableArguments(executableArgs);
+      for (CommandLineAndParamFileInfo commandLineAndParamFileInfo : commandLines) {
+        CommandLine commandLine = commandLineAndParamFileInfo.commandLine;
+        ParamFileInfo paramFileInfo = commandLineAndParamFileInfo.paramFileInfo;
+        boolean useParamsFile =
+            paramFileInfo != null && (paramFileInfo.always() || spillToParamFiles);
+        if (useParamsFile) {
+          Artifact output = Iterables.getFirst(outputs, null);
+          Preconditions.checkNotNull(output);
+          PathFragment paramFilePath =
+              ParameterFile.derivePath(
+                  output.getRootRelativePath(), Integer.toString(paramFileNameSuffix));
+          Artifact paramFile =
+              analysisEnvironment.getDerivedArtifact(paramFilePath, output.getRoot());
+          inputsBuilder.add(paramFile);
+          ParameterFileWriteAction paramFileWriteAction =
+              new ParameterFileWriteAction(
+                  owner,
+                  paramFile,
+                  commandLine,
+                  paramFileInfo.getFileType(),
+                  paramFileInfo.getCharset());
+          paramFileActions.add(paramFileWriteAction);
+          ++paramFileNameSuffix;
+          result.addParamFile(paramFile, paramFileInfo);
+        } else {
+          result.addCommandLine(commandLine);
+        }
+      }
+      return result.build();
+    }
 
-      return actions.toArray(new Action[actions.size()]);
+    private static int getCommandLineSize(CommandLine commandLine) {
+      try {
+        Iterable<String> actualArguments = commandLine.arguments();
+        return getParamFileSize(actualArguments);
+      } catch (CommandLineExpansionException e) {
+        // CommandLineExpansionException is thrown deterministically. We can ignore
+        // it here and pretend that a params file is not necessary at this stage,
+        // and an error will be thrown later at execution time.
+        return 0;
+      }
+    }
+
+    private static int getParamFileSize(Iterable<String> args) {
+      int size = 0;
+      for (String s : args) {
+        size += s.length() + 1; // Account for the space character
+      }
+      return size;
     }
 
     /**
@@ -643,26 +704,11 @@
      *
      * @param owner the {@link ActionOwner} for the SpawnAction
      * @param configEnv the config's action environment to use. May be null if not used.
-     * @param defaultShellExecutable the default shell executable path. May be null if not used.
-     * @param paramsFile the parameter file for the SpawnAction. May be null if not used.
      * @return the SpawnAction and any actions required by it, with the first item always being the
      *     SpawnAction itself.
      */
     SpawnAction buildSpawnAction(
-        ActionOwner owner,
-        CommandLine commandLine,
-        @Nullable ActionEnvironment configEnv,
-        @Nullable PathFragment defaultShellExecutable,
-        @Nullable Artifact paramsFile) {
-      ImmutableList<String> argv = buildExecutableArgs(defaultShellExecutable);
-      CommandLine actualCommandLine;
-      if (paramsFile != null) {
-        inputsBuilder.add(paramsFile);
-        actualCommandLine = ParamFileHelper.createWithParamsFile(argv, paramFileInfo, paramsFile);
-      } else {
-        actualCommandLine = ParamFileHelper.createWithoutParamsFile(argv, commandLine);
-      }
-
+        ActionOwner owner, CommandLine commandLine, @Nullable ActionEnvironment configEnv) {
       NestedSet<Artifact> tools = toolsBuilder.build();
 
       // Tools are by definition a subset of the inputs, so make sure they're present there, too.
@@ -692,7 +738,7 @@
           inputsAndTools,
           ImmutableList.copyOf(outputs),
           resourceSet,
-          actualCommandLine,
+          commandLine,
           isShellCommand,
           env,
           ImmutableMap.copyOf(executionInfo),
@@ -702,6 +748,21 @@
           mnemonic);
     }
 
+    /**
+     * Builds the command line, forcing no params file.
+     *
+     * <p>This method is invoked by {@link SpawnActionTemplate} in the execution phase.
+     */
+    CommandLine buildCommandLineWithoutParamsFiles() {
+      SpawnActionCommandLine.Builder result = new SpawnActionCommandLine.Builder();
+      ImmutableList<String> executableArgs = buildExecutableArgs(null);
+      result.addExecutableArguments(executableArgs);
+      for (CommandLineAndParamFileInfo commandLineAndParamFileInfo : commandLines) {
+        result.addCommandLine(commandLineAndParamFileInfo.commandLine);
+      }
+      return result.build();
+    }
+
     /** Creates a SpawnAction. */
     protected SpawnAction createSpawnAction(
         ActionOwner owner,
@@ -1062,15 +1123,36 @@
     }
 
     /**
-     * Sets a delegate to compute the command line at a later time.
+     * Adds a delegate to compute the command line at a later time.
+     *
+     * <p>The arguments are added after the executable arguments. If you add multiple command lines,
+     * they are expanded in the corresponding order.
      *
      * <p>The main intention of this method is to save memory by allowing client-controlled sharing
      * between actions and configured targets. Objects passed to this method MUST be immutable.
      *
      * <p>See also {@link CustomCommandLine}.
      */
-    public Builder setCommandLine(CommandLine commandLine) {
-      this.commandLine = commandLine;
+    public Builder addCommandLine(CommandLine commandLine) {
+      this.commandLines.add(new CommandLineAndParamFileInfo(commandLine, null));
+      return this;
+    }
+
+    /**
+     * Adds a delegate to compute the command line at a later time, optionally spilled to a params
+     * file.
+     *
+     * <p>The arguments are added after the executable arguments. If you add multiple command lines,
+     * they are expanded in the corresponding order. If the command line is spilled to a params
+     * file, it is replaced with an argument pointing to the param file.
+     *
+     * <p>The main intention of this method is to save memory by allowing client-controlled sharing
+     * between actions and configured targets. Objects passed to this method MUST be immutable.
+     *
+     * <p>See also {@link CustomCommandLine}.
+     */
+    public Builder addCommandLine(CommandLine commandLine, @Nullable ParamFileInfo paramFileInfo) {
+      this.commandLines.add(new CommandLineAndParamFileInfo(commandLine, paramFileInfo));
       return this;
     }
 
@@ -1207,50 +1289,80 @@
       return this;
     }
 
-    /**
-     * Enable use of a parameter file and set the encoding to ISO-8859-1 (latin1).
-     *
-     * <p>In order to use parameter files, at least one output artifact must be specified.
-     */
-    public Builder useParameterFile(ParameterFileType parameterFileType) {
-      return useParameterFile(parameterFileType, ISO_8859_1, "@");
-    }
-
-    /**
-     * Force the use of a parameter file and set the encoding to ISO-8859-1 (latin1).
-     *
-     * <p>In order to use parameter files, at least one output artifact must be specified.
-     */
-    public Builder alwaysUseParameterFile(ParameterFileType parameterFileType) {
-      return useParameterFile(parameterFileType, ISO_8859_1, "@", /*always=*/ true);
-    }
-
-    /**
-     * Enable or disable the use of a parameter file, set the encoding to the given value, and
-     * specify the argument prefix to use in passing the parameter file name to the tool.
-     *
-     * <p>The default argument prefix is "@". In order to use parameter files, at least one output
-     * artifact must be specified.
-     */
-    public Builder useParameterFile(
-        ParameterFileType parameterFileType,
-        Charset charset,
-        @CompileTimeConstant String flagPrefix) {
-      return useParameterFile(parameterFileType, charset, flagPrefix, /*always=*/ false);
-    }
-
-    private Builder useParameterFile(
-        ParameterFileType parameterFileType,
-        Charset charset,
-        @CompileTimeConstant String flagPrefix,
-        boolean always) {
-      paramFileInfo = new ParamFileInfo(parameterFileType, charset, flagPrefix, always);
-      return this;
-    }
-
     public Builder disableSandboxing() {
       this.disableSandboxing = true;
       return this;
     }
   }
+
+  /**
+   * Command line implementation that optimises for containing executable args, command lines, and
+   * command lines spilled to param files.
+   */
+  private static class SpawnActionCommandLine extends CommandLine {
+    private final Object[] values;
+
+    SpawnActionCommandLine(Object[] values) {
+      this.values = values;
+    }
+
+    @Override
+    public Iterable<String> arguments() throws CommandLineExpansionException {
+      return expandArguments(null);
+    }
+
+    @Override
+    public Iterable<String> arguments(ArtifactExpander artifactExpander)
+        throws CommandLineExpansionException {
+      return expandArguments(artifactExpander);
+    }
+
+    private Iterable<String> expandArguments(@Nullable ArtifactExpander artifactExpander)
+        throws CommandLineExpansionException {
+      ImmutableList.Builder<String> result = ImmutableList.builder();
+      int count = values.length;
+      for (int i = 0; i < count; ++i) {
+        Object value = values[i];
+        if (value instanceof String) {
+          result.add((String) value);
+        } else if (value instanceof Artifact) {
+          Artifact paramFile = (Artifact) value;
+          String flag = (String) values[++i];
+          result.add(flag + paramFile.getExecPathString());
+        } else if (value instanceof CommandLine) {
+          CommandLine commandLine = (CommandLine) value;
+          if (artifactExpander != null) {
+            result.addAll(commandLine.arguments(artifactExpander));
+          } else {
+            result.addAll(commandLine.arguments());
+          }
+        }
+      }
+      return result.build();
+    }
+
+    private static class Builder {
+      private List<Object> values = new ArrayList<>();
+
+      Builder addExecutableArguments(ImmutableList<String> executableArguments) {
+        values.addAll(executableArguments);
+        return this;
+      }
+
+      Builder addParamFile(Artifact paramFile, ParamFileInfo paramFileInfo) {
+        values.add(paramFile);
+        values.add(paramFileInfo.getFlag());
+        return this;
+      }
+
+      Builder addCommandLine(CommandLine commandLine) {
+        values.add(commandLine);
+        return this;
+      }
+
+      SpawnActionCommandLine build() {
+        return new SpawnActionCommandLine(values.toArray());
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplate.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplate.java
index db40f23..0b35944 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplate.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplate.java
@@ -121,17 +121,13 @@
 
     CommandLine commandLine = commandLineTemplate.evaluateTreeFileArtifacts(
         ImmutableList.of(inputTreeFileArtifact, outputTreeFileArtifact));
-    actionBuilder.setCommandLine(commandLine);
+    actionBuilder.addCommandLine(commandLine);
 
     // Note that we pass in nulls below because SpawnActionTemplate does not support param file, and
     // it does not use any default value for executable or shell environment. They must be set
     // explicitly via builder method #setExecutable and #setEnvironment.
     return actionBuilder.buildSpawnAction(
-        getOwner(),
-        commandLine,
-        /*configEnv=*/ null,
-        /*defaultShellExecutable=*/ null,
-        /*paramsFile=*/ null);
+        getOwner(), actionBuilder.buildCommandLineWithoutParamsFiles(), /*configEnv=*/ null);
   }
 
   /**
@@ -206,7 +202,7 @@
   @Override
   public Iterable<String> getClientEnvironmentVariables() {
     return spawnActionBuilder
-        .buildSpawnAction(getOwner(), CommandLine.of(ImmutableList.of()), null, null, null)
+        .buildSpawnAction(getOwner(), CommandLine.of(ImmutableList.of()), null)
         .getClientEnvironmentVariables();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
index 8b134bb..be6d634 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
@@ -382,10 +382,10 @@
       SkylarkList skylarkList = ((SkylarkList) arguments);
       @SuppressWarnings("unchecked")
       List<String> argumentsContents = skylarkList.getContents(String.class, "arguments");
-      builder.setCommandLine(CustomCommandLine.builder().addAll(argumentsContents).build());
+      builder.addCommandLine(CustomCommandLine.builder().addAll(argumentsContents).build());
     } else {
       Args args = (Args) arguments;
-      builder.setCommandLine(args.build());
+      builder.addCommandLine(args.build());
     }
     if (executableUnchecked instanceof Artifact) {
       Artifact executable = (Artifact) executableUnchecked;
@@ -564,10 +564,10 @@
       @SuppressWarnings("unchecked")
       List<String> argumentsContents = argumentList.getContents(String.class, "arguments");
       commandLine.addAll(argumentsContents);
-      builder.setCommandLine(commandLine.build());
+      builder.addCommandLine(commandLine.build());
     } else {
       Args args = (Args) arguments;
-      builder.setCommandLine(CommandLine.concat(ImmutableList.of(""), args.build()));
+      builder.addCommandLine(CommandLine.concat(ImmutableList.of(""), args.build()));
     }
 
     if (commandUnchecked instanceof String) {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
index 1aed86d..08cfb61 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
@@ -317,7 +317,7 @@
             .addOutput(zipFile)
             .setExecutable(zipper)
             .useDefaultShellEnvironment()
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("cC")
                     .addExecPath(zipFile)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java
index c4c194b..c3c6365 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
 import com.google.devtools.build.lib.analysis.actions.CommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.rules.android.ResourceContainer.ResourceType;
 import com.google.devtools.build.lib.util.OS;
@@ -127,6 +128,7 @@
       args.add("--throwOnResourceConflict");
     }
 
+    ParamFileInfo paramFileInfo = null;
     if (OS.getCurrent() == OS.WINDOWS) {
       // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
       // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
@@ -136,7 +138,7 @@
       // list-type and list-of-list-type flags that use such problematic separators in favor of
       // multi-value flags (to remove one level of listing) and by changing all list separators to a
       // platform-safe character (= comma).
-      builder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
+      paramFileInfo = ParamFileInfo.builder(ParameterFileType.UNQUOTED).setUseAlways(true).build();
     }
 
     ruleContext.registerAction(
@@ -144,7 +146,7 @@
             .useDefaultShellEnvironment()
             .addInputs(ImmutableList.<Artifact>copyOf(ins))
             .addOutputs(ImmutableList.<Artifact>copyOf(outs))
-            .setCommandLine(CommandLine.of(args))
+            .addCommandLine(CommandLine.of(args), paramFileInfo)
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Building AAR package for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java
index e3d5a12..389abc0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java
@@ -182,7 +182,7 @@
         .setProgressMessage("Extracting %s from %s", filename, aar.getFilename())
         .addInput(aar)
         .addOutput(outputArtifact)
-        .setCommandLine(
+        .addCommandLine(
             CustomCommandLine.builder()
                 .addExecPath("x", aar)
                 .addPath("-d", outputArtifact.getExecPath().getParentDirectory())
@@ -201,7 +201,7 @@
         .setMnemonic("AarResourcesExtractor")
         .addInput(aar)
         .addOutput(outputTree)
-        .setCommandLine(
+        .addCommandLine(
             CustomCommandLine.builder()
                 .addExecPath("--input_aar", aar)
                 .addExecPath("--output_res_dir", outputTree)
@@ -221,7 +221,7 @@
         .addInput(aar)
         .addOutput(jarsTreeArtifact)
         .addOutput(singleJarParamFile)
-        .setCommandLine(
+        .addCommandLine(
             CustomCommandLine.builder()
                 .addExecPath("--input_aar", aar)
                 .addExecPath("--output_dir", jarsTreeArtifact)
@@ -238,7 +238,7 @@
         .addInput(jarsTreeArtifact)
         .addOutput(mergedJar)
         .addInput(paramFile)
-        .setCommandLine(
+        .addCommandLine(
             CustomCommandLine.builder()
                 .addExecPath("--output", mergedJar)
                 .add("--dont_change_compression")
@@ -259,7 +259,7 @@
             .setProgressMessage("Filtering AAR native libs by architecture")
             .addInput(aar)
             .addOutput(outputZip)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath("--input_aar", aar)
                     .add("--cpu", ruleContext.getConfiguration().getCpu())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java
index 3a5d8d3..310c7f6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder;
 import com.google.devtools.build.lib.analysis.config.CompilationMode;
@@ -108,8 +109,8 @@
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_aapt_java_generator", Mode.HOST))
             .addOutput(javaSourcesJar)
-            .setCommandLine(CommandLine.of(args))
-            .useParameterFile(ParameterFileType.UNQUOTED)
+            .addCommandLine(
+                CommandLine.of(args), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .setProgressMessage("Generating Java resources")
             .setMnemonic("AaptJavaGenerator");
     if (rTxt != null) {
@@ -149,8 +150,8 @@
             .addOutput(apk)
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_aapt_apk_generator", Mode.HOST))
-            .setCommandLine(CommandLine.of(args))
-            .useParameterFile(ParameterFileType.UNQUOTED)
+            .addCommandLine(
+                CommandLine.of(args), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .setProgressMessage("Generating apk resources")
             .setMnemonic("AaptResourceApk")
             .build(ruleContext));
@@ -277,8 +278,9 @@
             .addOutputs(outputs.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_aapt_apk_generator", Mode.HOST))
-            .setCommandLine(CommandLine.of(aaptCommand))
-            .useParameterFile(ParameterFileType.UNQUOTED)
+            .addCommandLine(
+                CommandLine.of(aaptCommand),
+                ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .setProgressMessage("Generating Proguard configuration for resources")
             .setMnemonic("AaptProguardConfiguration")
             .build(ruleContext));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index c45aeec..067a45a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -529,7 +529,7 @@
           || getMultidexMode(ruleContext) == MultidexMode.LEGACY) {
         commandLine.add("--keep-main-dex");
       }
-      rexActionBuilder.setCommandLine(commandLine.build());
+      rexActionBuilder.addCommandLine(commandLine.build());
       ruleContext.registerAction(rexActionBuilder.build(ruleContext));
     } else {
       finalDexes = dexingOutput.classesDexZip;
@@ -1036,7 +1036,7 @@
                 .setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips", Mode.HOST))
                 .addInputs(shardDexes)
                 .addOutput(classesDex)
-                .setCommandLine(mergeCommandLine)
+                .addCommandLine(mergeCommandLine)
                 .build(ruleContext));
         if (incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED)) {
           // Using the deploy jar for java resources gives better "bazel mobile-install" performance
@@ -1140,7 +1140,7 @@
       dexmerger.addInput(mainDexList);
       commandLine.addExecPath("--main-dex-list", mainDexList);
     }
-    dexmerger.setCommandLine(commandLine.build());
+    dexmerger.addCommandLine(commandLine.build());
     ruleContext.registerAction(dexmerger.build(ruleContext));
   }
 
@@ -1320,7 +1320,7 @@
       }
     }
 
-    shardAction.setCommandLine(shardCommandLine.build());
+    shardAction.addCommandLine(shardCommandLine.build());
     ruleContext.registerAction(shardAction.build(ruleContext));
     return javaResourceJar;
   }
@@ -1354,7 +1354,7 @@
             .setMnemonic("TrimDexZip")
             .addInput(inputZip)
             .addOutput(outputZip)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("--exclude_build_data")
                     .add("--dont_change_compression")
@@ -1420,7 +1420,7 @@
     androidSemantics.addMainDexListActionArguments(
         ruleContext, streamlinedBuilder, streamlinedCommandLine, proguardOutputMap);
 
-    streamlinedBuilder.setCommandLine(streamlinedCommandLine.build());
+    streamlinedBuilder.addCommandLine(streamlinedCommandLine.build());
     ruleContext.registerAction(streamlinedBuilder.build(ruleContext));
 
     // Create the main dex classes list.
@@ -1435,7 +1435,7 @@
             .addOutput(mainDexList)
             .addInput(strippedJar)
             .addInput(jar)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath(mainDexList)
                     .addExecPath(strippedJar)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryMobileInstall.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryMobileInstall.java
index b99940c..7d3f500 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryMobileInstall.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryMobileInstall.java
@@ -28,6 +28,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -160,12 +161,12 @@
                 ruleContext.getExecutablePrerequisite("$build_incremental_dexmanifest", Mode.HOST))
             .addOutput(incrementalDexManifest)
             .addInputs(dexingOutput.shardDexZips)
-            .useParameterFile(ParameterFileType.UNQUOTED)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath(incrementalDexManifest)
                     .addExecPaths(dexingOutput.shardDexZips)
-                    .build())
+                    .build(),
+                ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .build(ruleContext));
 
     Artifact stubData = ruleContext.getImplicitOutputArtifact(
@@ -277,7 +278,7 @@
             .setExecutable(ruleContext.getExecutablePrerequisite("$strip_resources", Mode.HOST))
             .addInput(resourceApk.getArtifact())
             .addOutput(splitMainApkResources)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath("--input_resource_apk", resourceApk.getArtifact())
                     .addExecPath("--output_resource_apk", splitMainApkResources)
@@ -450,7 +451,7 @@
       }
     }
 
-    builder.setCommandLine(commandLine.build());
+    builder.addCommandLine(commandLine.build());
     ruleContext.registerAction(builder.build(ruleContext));
   }
 
@@ -485,7 +486,7 @@
       commandLine.addExecPath("--split_apk", splitApk);
     }
 
-    builder.setCommandLine(commandLine.build());
+    builder.addCommandLine(commandLine.build());
     ruleContext.registerAction(builder.build(ruleContext));
   }
 
@@ -504,7 +505,7 @@
             .addOutput(splitResources)
             .addInput(splitManifest)
             .addInput(sdk.getAndroidJar())
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("package")
                     .addExecPath("-F", splitResources)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
index d590dd635c2..7a116e0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
@@ -230,7 +230,7 @@
             .addOutput(classesDex)
             .setProgressMessage("Converting %s to dex format", jarToDex.getExecPathString())
             .setMnemonic("AndroidDexer")
-            .setCommandLine(commandLine.build())
+            .addCommandLine(commandLine.build())
             .setResources(ResourceSet.createWithRamCpuIo(4096.0, 5.0, 0.0));
     if (mainDexList != null) {
       builder.addInput(mainDexList);
@@ -508,7 +508,7 @@
               .addInput(jarJarRuleFile)
               .addInput(binaryResourcesJar)
               .addOutput(resourcesJar)
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder()
                       .add("process")
                       .addExecPath(jarJarRuleFile)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java
index 22952e0..7eec313 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java
@@ -337,7 +337,7 @@
         spawnBuilder.addInput(defaultProperties.get());
         commandLine.addPrefixedExecPath("--default_properties_file=", defaultProperties.get());
       }
-      spawnBuilder.setCommandLine(commandLine.build());
+      spawnBuilder.addCommandLine(commandLine.build());
       ruleContext.registerAction(spawnBuilder.build(ruleContext));
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java
index 2524c03..4d55602 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -333,7 +334,7 @@
             .addOutput(idlClassJar)
             .addOutput(idlSourceJar)
             .setExecutable(ruleContext.getExecutablePrerequisite("$idlclass", Mode.HOST))
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath("--manifest_proto", manifestProtoOutput)
                     .addExecPath("--class_jar", classJar)
@@ -342,8 +343,8 @@
                     .add("--temp_dir")
                     .addPath(idlTempDir)
                     .addExecPaths(ImmutableList.copyOf(generatedIdlJavaFiles))
-                    .build())
-            .useParameterFile(ParameterFileType.SHELL_QUOTED)
+                    .build(),
+                ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).build())
             .setProgressMessage("Building idl jars %s", idlClassJar.prettyPrint())
             .setMnemonic("AndroidIdlJars")
             .build(ruleContext));
@@ -372,7 +373,7 @@
             .addOutput(output)
             .setProgressMessage("Android IDL generation")
             .setMnemonic("AndroidIDLGenerate")
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("-b") // Fail if trying to compile a parcelable.
                     .addAll(importArgs)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java
index 29b96b6..039b91a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java
@@ -47,7 +47,7 @@
             .setExecutable(ruleContext.getPrerequisite("$android_manifest_merge_tool", Mode.HOST))
             .setProgressMessage("Merging Android Manifests")
             .setMnemonic("AndroidManifestMerger")
-            .setCommandLine(commandLine.build())
+            .addCommandLine(commandLine.build())
             .build(ruleContext));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java
index 0d39a86..fe0b68f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.rules.android.ResourceContainerConverter.Builder.SeparatorType;
@@ -182,20 +183,16 @@
     }
 
     SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
-
-    if (OS.getCurrent() == OS.WINDOWS) {
-      // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
-      // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
-      // semantics are very complicated (more so than in Bash), so let's just always use a parameter
-      // file.
-      // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
-      // list-type and list-of-list-type flags that use such problematic separators in favor of
-      // multi-value flags (to remove one level of listing) and by changing all list separators to a
-      // platform-safe character (= comma).
-      spawnActionBuilder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
-    } else {
-      spawnActionBuilder.useParameterFile(ParameterFileType.UNQUOTED);
-    }
+    ParamFileInfo.Builder paramFileInfo = ParamFileInfo.builder(ParameterFileType.UNQUOTED);
+    // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
+    // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
+    // semantics are very complicated (more so than in Bash), so let's just always use a parameter
+    // file.
+    // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
+    // list-type and list-of-list-type flags that use such problematic separators in favor of
+    // multi-value flags (to remove one level of listing) and by changing all list separators to a
+    // platform-safe character (= comma).
+    paramFileInfo.setUseAlways(OS.getCurrent() == OS.WINDOWS);
 
     // Create the spawn action.
     ruleContext.registerAction(
@@ -203,7 +200,7 @@
             .useDefaultShellEnvironment()
             .addTransitiveInputs(inputs.build())
             .addOutputs(ImmutableList.copyOf(outs))
-            .setCommandLine(builder.build())
+            .addCommandLine(builder.build(), paramFileInfo.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Merging Android resources for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java
index 6a0f7a6..bd10d0b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -145,19 +146,16 @@
     builder.addExecPath("--output", output);
 
     SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
-    if (OS.getCurrent() == OS.WINDOWS) {
-      // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
-      // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
-      // semantics are very complicated (more so than in Bash), so let's just always use a parameter
-      // file.
-      // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
-      // list-type and list-of-list-type flags that use such problematic separators in favor of
-      // multi-value flags (to remove one level of listing) and by changing all list separators to a
-      // platform-safe character (= comma).
-      spawnActionBuilder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
-    } else {
-      spawnActionBuilder.useParameterFile(ParameterFileType.UNQUOTED);
-    }
+    ParamFileInfo.Builder paramFileInfo = ParamFileInfo.builder(ParameterFileType.UNQUOTED);
+    // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
+    // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
+    // semantics are very complicated (more so than in Bash), so let's just always use a parameter
+    // file.
+    // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
+    // list-type and list-of-list-type flags that use such problematic separators in favor of
+    // multi-value flags (to remove one level of listing) and by changing all list separators to a
+    // platform-safe character (= comma).
+    paramFileInfo.setUseAlways(OS.getCurrent() == OS.WINDOWS);
 
     // Create the spawn action.
     ruleContext.registerAction(
@@ -165,7 +163,7 @@
             .useDefaultShellEnvironment()
             .addTransitiveInputs(inputs.build())
             .addOutputs(ImmutableList.of(output))
-            .setCommandLine(builder.build())
+            .addCommandLine(builder.build(), paramFileInfo.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Parsing Android resources for %s", ruleContext.getLabel())
@@ -198,11 +196,12 @@
       // Create the spawn action.
       ruleContext.registerAction(
           new SpawnAction.Builder()
-              .useParameterFile(ParameterFileType.UNQUOTED)
               .useDefaultShellEnvironment()
               .addTransitiveInputs(inputs.build())
               .addOutputs(ImmutableList.copyOf(outs))
-              .setCommandLine(flatFileBuilder.build())
+              .addCommandLine(
+                  flatFileBuilder.build(),
+                  ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
               .setExecutable(
                   ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
               .setProgressMessage("Compiling Android resources for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java
index 50cfa8a..ac051ea 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceValidatorActionBuilder.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import java.util.ArrayList;
 import java.util.List;
@@ -193,12 +194,12 @@
 
     ruleContext.registerAction(
         new SpawnAction.Builder()
-            .useParameterFile(ParameterFileType.UNQUOTED)
             .useDefaultShellEnvironment()
             .addTool(sdk.getAapt2())
             .addInputs(inputs.build())
             .addOutputs(outs.build())
-            .setCommandLine(builder.build())
+            .addCommandLine(
+                builder.build(), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage(
@@ -269,12 +270,12 @@
     // Create the spawn action.
     ruleContext.registerAction(
         spawnActionBuilder
-            .useParameterFile(ParameterFileType.UNQUOTED)
             .useDefaultShellEnvironment()
             .addTool(sdk.getAapt())
             .addInputs(inputs.build())
             .addOutputs(ImmutableList.copyOf(outs))
-            .setCommandLine(builder.build())
+            .addCommandLine(
+                builder.build(), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Validating Android resources for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
index 6fd04a2..57f9a4f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
@@ -23,6 +23,7 @@
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.Builder;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion;
@@ -278,19 +279,16 @@
 
     configureCommonFlags(outs, inputs, builder);
 
-    if (OS.getCurrent() == OS.WINDOWS) {
-      // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
-      // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
-      // semantics are very complicated (more so than in Bash), so let's just always use a parameter
-      // file.
-      // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
-      // list-type and list-of-list-type flags that use such problematic separators in favor of
-      // multi-value flags (to remove one level of listing) and by changing all list separators to a
-      // platform-safe character (= comma).
-      this.spawnActionBuilder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
-    } else {
-      this.spawnActionBuilder.useParameterFile(ParameterFileType.UNQUOTED);
-    }
+    ParamFileInfo.Builder paramFileInfo = ParamFileInfo.builder(ParameterFileType.UNQUOTED);
+    // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
+    // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
+    // semantics are very complicated (more so than in Bash), so let's just always use a parameter
+    // file.
+    // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
+    // list-type and list-of-list-type flags that use such problematic separators in favor of
+    // multi-value flags (to remove one level of listing) and by changing all list separators to a
+    // platform-safe character (= comma).
+    paramFileInfo.setUseAlways(OS.getCurrent() == OS.WINDOWS);
 
     // Create the spawn action.
     ruleContext.registerAction(
@@ -300,7 +298,7 @@
             .addTool(sdk.getAapt2())
             .addTransitiveInputs(inputs.build())
             .addOutputs(ImmutableList.<Artifact>copyOf(outs))
-            .setCommandLine(builder.build())
+            .addCommandLine(builder.build(), paramFileInfo.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Processing Android resources for %s", ruleContext.getLabel())
@@ -344,19 +342,16 @@
     builder.addExecPath("--aapt", sdk.getAapt().getExecutable());
     configureCommonFlags(outs, inputs, builder);
 
-    if (OS.getCurrent() == OS.WINDOWS) {
-      // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
-      // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
-      // semantics are very complicated (more so than in Bash), so let's just always use a parameter
-      // file.
-      // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
-      // list-type and list-of-list-type flags that use such problematic separators in favor of
-      // multi-value flags (to remove one level of listing) and by changing all list separators to a
-      // platform-safe character (= comma).
-      this.spawnActionBuilder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
-    } else {
-      this.spawnActionBuilder.useParameterFile(ParameterFileType.UNQUOTED);
-    }
+    ParamFileInfo.Builder paramFileInfo = ParamFileInfo.builder(ParameterFileType.UNQUOTED);
+    // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
+    // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
+    // semantics are very complicated (more so than in Bash), so let's just always use a parameter
+    // file.
+    // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
+    // list-type and list-of-list-type flags that use such problematic separators in favor of
+    // multi-value flags (to remove one level of listing) and by changing all list separators to a
+    // platform-safe character (= comma).
+    paramFileInfo.setUseAlways(OS.getCurrent() == OS.WINDOWS);
 
     // Create the spawn action.
     ruleContext.registerAction(
@@ -366,7 +361,7 @@
             .addTool(sdk.getAapt())
             .addTransitiveInputs(inputs.build())
             .addOutputs(ImmutableList.copyOf(outs))
-            .setCommandLine(builder.build())
+            .addCommandLine(builder.build(), paramFileInfo.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Processing Android resources for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java
index bd1f60e..c7abbb8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java
@@ -229,7 +229,7 @@
       commandLine.addExecPath(classesDex);
     }
 
-    actionBuilder.setCommandLine(commandLine.build());
+    actionBuilder.addCommandLine(commandLine.build());
     ruleContext.registerAction(actionBuilder.build(ruleContext));
   }
 
@@ -303,7 +303,7 @@
               .setProgressMessage("Extracting Java resources from deploy jar for %s", apkName)
               .addInput(javaResourceZip)
               .addOutput(extractedJavaResourceZip)
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder()
                       .addExecPath(javaResourceZip)
                       .addExecPath(extractedJavaResourceZip)
@@ -338,9 +338,9 @@
       singleJarCommandLine.addAll("--nocompress_suffixes", noCompressExtensions);
     }
 
-    compressedApkActionBuilder.setCommandLine(compressedApkCommandLine.build());
+    compressedApkActionBuilder.addCommandLine(compressedApkCommandLine.build());
     ruleContext.registerAction(compressedApkActionBuilder.build(ruleContext));
-    singleJarActionBuilder.setCommandLine(singleJarCommandLine.build());
+    singleJarActionBuilder.addCommandLine(singleJarCommandLine.build());
     ruleContext.registerAction(singleJarActionBuilder.build(ruleContext));
   }
 
@@ -355,7 +355,7 @@
             .setMnemonic("AndroidZipAlign")
             .addInput(inputApk)
             .addOutput(zipAlignedApk)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("-p") // memory page aligment for stored shared object files
                     .add("4")
@@ -382,7 +382,7 @@
             .addInput(signingKey)
             .addOutput(signedAndZipalignedApk)
             .addInput(unsignedApk)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("sign")
                     .add("--ks")
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
index 91579b6..97028a0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
@@ -89,7 +89,7 @@
       commandLine.add("--override_package", overridePackage);
     }
 
-    builder.setCommandLine(commandLine.build());
+    builder.addCommandLine(commandLine.build());
     ruleContext.registerAction(builder.build(ruleContext));
     return new ApplicationManifest(ruleContext, result, targetAaptVersion);
   }
@@ -123,7 +123,7 @@
       commandLine.add("--override_package", overridePackage);
     }
 
-    builder.setCommandLine(commandLine.build());
+    builder.addCommandLine(commandLine.build());
     ruleContext.registerAction(builder.build(ruleContext));
 
     return new ApplicationManifest(ruleContext, stubManifest, targetAaptVersion);
@@ -142,7 +142,7 @@
             .setMnemonic("InjectInstantRunStubApplication")
             .addInput(manifest)
             .addOutput(stubManifest)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("--mode=instant_run")
                     .addExecPath("--input_manifest", manifest)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java b/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java
index bf5491f..b3db32e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java
@@ -401,7 +401,7 @@
             .addOutput(result)
             .setMnemonic("Desugar")
             .setProgressMessage("Desugaring %s for Android", jar.prettyPrint())
-            .setCommandLine(CustomCommandLine.builder().addPrefixedExecPath("@", paramFile).build())
+            .addCommandLine(CustomCommandLine.builder().addPrefixedExecPath("@", paramFile).build())
             .build(ruleContext));
     return result;
   }
@@ -453,7 +453,7 @@
             .setMnemonic("DexBuilder")
             .setProgressMessage(
                 "Dexing %s with applicable dexopts %s", jar.prettyPrint(), incrementalDexopts)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder().addPrefixedExecPath("@", paramFile).build());
     if (getAndroidConfig(ruleContext).useWorkersWithDexbuilder()) {
       dexbuilder.setExecutionInfo(ExecutionRequirements.WORKER_MODE_ENABLED);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/LibraryRGeneratorActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/LibraryRGeneratorActionBuilder.java
index 185953c..b0e9e66 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/LibraryRGeneratorActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/LibraryRGeneratorActionBuilder.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
@@ -89,11 +90,11 @@
     SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
     ruleContext.registerAction(
         spawnActionBuilder
-            .useParameterFile(ParameterFileType.UNQUOTED)
             .useDefaultShellEnvironment()
             .addTransitiveInputs(inputs.build())
             .addOutputs(ImmutableList.<Artifact>of(rJavaClassJar))
-            .setCommandLine(builder.build())
+            .addCommandLine(
+                builder.build(), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .setExecutable(executable)
             .setProgressMessage("Generating Library R Classes: %s", ruleContext.getLabel())
             .setMnemonic("LibraryRClassGenerator")
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java
index 1203a2f..91ee23e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java
@@ -23,6 +23,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -131,26 +132,23 @@
       outputs.add(logOut);
     }
 
-    if (OS.getCurrent() == OS.WINDOWS) {
-      // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
-      // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
-      // semantics are very complicated (more so than in Bash), so let's just always use a parameter
-      // file.
-      // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
-      // list-type and list-of-list-type flags that use such problematic separators in favor of
-      // multi-value flags (to remove one level of listing) and by changing all list separators to a
-      // platform-safe character (= comma).
-      spawnActionBuilder.alwaysUseParameterFile(ParameterFileType.SHELL_QUOTED);
-    } else {
-      spawnActionBuilder.useParameterFile(ParameterFileType.SHELL_QUOTED);
-    }
+    ParamFileInfo.Builder paramFileInfo = ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED);
+    // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
+    // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
+    // semantics are very complicated (more so than in Bash), so let's just always use a parameter
+    // file.
+    // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
+    // list-type and list-of-list-type flags that use such problematic separators in favor of
+    // multi-value flags (to remove one level of listing) and by changing all list separators to a
+    // platform-safe character (= comma).
+    paramFileInfo.setUseAlways(OS.getCurrent() == OS.WINDOWS);
 
     ruleContext.registerAction(
         this.spawnActionBuilder
             .useDefaultShellEnvironment()
             .addTransitiveInputs(inputs.build())
             .addOutputs(outputs.build())
-            .setCommandLine(builder.build())
+            .addCommandLine(builder.build(), paramFileInfo.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Merging manifest for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/RClassGeneratorActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/RClassGeneratorActionBuilder.java
index 137bd65..dd79c8f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/RClassGeneratorActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/RClassGeneratorActionBuilder.java
@@ -24,12 +24,12 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion;
-import com.google.devtools.build.lib.util.OS;
 import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.Nullable;
@@ -121,27 +121,13 @@
     // Create the spawn action.
     SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
 
-    if (OS.getCurrent() == OS.WINDOWS) {
-      // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
-      // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
-      // semantics are very complicated (more so than in Bash), so let's just always use a parameter
-      // file.
-      // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
-      // list-type and list-of-list-type flags that use such problematic separators in favor of
-      // multi-value flags (to remove one level of listing) and by changing all list separators to a
-      // platform-safe character (= comma).
-      spawnActionBuilder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
-    } else {
-      spawnActionBuilder.useParameterFile(ParameterFileType.SHELL_QUOTED);
-    }
-
     ruleContext.registerAction(
         spawnActionBuilder
-            .useParameterFile(ParameterFileType.SHELL_QUOTED)
             .useDefaultShellEnvironment()
             .addTransitiveInputs(inputs.build())
             .addOutputs(ImmutableList.<Artifact>copyOf(outs))
-            .setCommandLine(builder.build())
+            .addCommandLine(
+                builder.build(), ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Generating R Classes: %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java
index 4bae0bc..7434e34 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java
@@ -239,7 +239,7 @@
             .addTool(aapt)
             .addInputs(inputs.build())
             .addOutputs(outputs.build())
-            .setCommandLine(commandLine.build())
+            .addCommandLine(commandLine.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Shrinking resources for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/RobolectricResourceSymbolsActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/RobolectricResourceSymbolsActionBuilder.java
index e752d82..1eaa346 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/RobolectricResourceSymbolsActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/RobolectricResourceSymbolsActionBuilder.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -92,26 +93,23 @@
     builder.addExecPath("--classJarOutput", classJarOut);
     SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
 
-    if (OS.getCurrent() == OS.WINDOWS) {
-      // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
-      // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
-      // semantics are very complicated (more so than in Bash), so let's just always use a parameter
-      // file.
-      // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
-      // list-type and list-of-list-type flags that use such problematic separators in favor of
-      // multi-value flags (to remove one level of listing) and by changing all list separators to a
-      // platform-safe character (= comma).
-      spawnActionBuilder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
-    } else {
-      spawnActionBuilder.useParameterFile(ParameterFileType.UNQUOTED);
-    }
+    ParamFileInfo.Builder paramFile = ParamFileInfo.builder(ParameterFileType.UNQUOTED);
+    // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
+    // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
+    // semantics are very complicated (more so than in Bash), so let's just always use a parameter
+    // file.
+    // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
+    // list-type and list-of-list-type flags that use such problematic separators in favor of
+    // multi-value flags (to remove one level of listing) and by changing all list separators to a
+    // platform-safe character (= comma).
+    paramFile.setUseAlways(OS.getCurrent() == OS.WINDOWS);
 
     ruleContext.registerAction(
         spawnActionBuilder
             .useDefaultShellEnvironment()
             .addTransitiveInputs(inputs.build())
             .addOutput(classJarOut)
-            .setCommandLine(builder.build())
+            .addCommandLine(builder.build(), paramFile.build())
             .setExecutable(
                 ruleContext.getExecutablePrerequisite("$android_resources_busybox", Mode.HOST))
             .setProgressMessage("Generating R classes for %s", ruleContext.getLabel())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
index 8c91d6b..97b1742 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
@@ -22,7 +22,7 @@
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.ExecutionRequirements;
-import com.google.devtools.build.lib.actions.ParameterFile;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.OutputGroupProvider;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
@@ -35,6 +35,7 @@
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.test.ExecutionInfo;
 import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
@@ -627,7 +628,8 @@
     CustomCommandLine.Builder commandLine = CustomCommandLine.builder();
 
     Action[] build(RuleContext context) {
-      spawnAction.setCommandLine(commandLine.build());
+      spawnAction.addCommandLine(
+          commandLine.build(), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build());
       return spawnAction.build(context);
     }
   }
@@ -696,8 +698,7 @@
     packager
         .spawnAction
         .addTransitiveInputs(dwpTools)
-        .setExecutable(cppConfiguration.getDwpExecutable())
-        .useParameterFile(ParameterFile.ParameterFileType.UNQUOTED);
+        .setExecutable(cppConfiguration.getDwpExecutable());
     return packager;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
index 7acc41f9..48afc44 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
@@ -154,7 +154,7 @@
               .setProgressMessage(
                   "LLVMUnzipProfileAction: Generating %s", rawProfileArtifact.prettyPrint())
               .setMnemonic("LLVMUnzipProfileAction")
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder()
                       .addExecPath("xf", zipProfileArtifact)
                       .add(
@@ -192,7 +192,7 @@
             .setExecutable(cppConfiguration.getLLVMProfDataExecutable())
             .setProgressMessage("LLVMProfDataAction: Generating %s", profileArtifact.prettyPrint())
             .setMnemonic("LLVMProfDataAction")
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("merge")
                     .add("-o")
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
index 5273d32..da85efa 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
@@ -664,7 +664,7 @@
             .setExecutionInfo(executionInfoBuilder.build())
             .setProgressMessage("Stripping %s for %s", output.prettyPrint(), context.getLabel())
             .setMnemonic("CcStrip")
-            .setCommandLine(CustomCommandLine.builder().addAll(commandLine).build())
+            .addCommandLine(CustomCommandLine.builder().addAll(commandLine).build())
             .build(context);
     context.registerAction(stripAction);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java
index c7ff290..cce33c4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java
@@ -28,6 +28,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.IterablesChain;
 import com.google.devtools.build.lib.util.Preconditions;
@@ -278,8 +279,9 @@
               .addOutput(outputJar)
               .setResources(resourceSet)
               .setJarExecutable(JavaCommon.getHostJavaExecutable(ruleContext), singlejar, jvmArgs)
-              .setCommandLine(commandLine)
-              .alwaysUseParameterFile(ParameterFileType.SHELL_QUOTED)
+              .addCommandLine(
+                  commandLine,
+                  ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).setUseAlways(true).build())
               .setProgressMessage("Building deploy jar %s", outputJar.prettyPrint())
               .setMnemonic("JavaDeployJar")
               .setExecutionInfo(ExecutionRequirements.WORKER_MODE_ENABLED)
@@ -291,8 +293,9 @@
               .addOutput(outputJar)
               .setResources(resourceSet)
               .setExecutable(singlejar)
-              .setCommandLine(commandLine)
-              .alwaysUseParameterFile(ParameterFileType.SHELL_QUOTED)
+              .addCommandLine(
+                  commandLine,
+                  ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).setUseAlways(true).build())
               .setProgressMessage("Building deploy jar %s", outputJar.prettyPrint())
               .setMnemonic("JavaDeployJar")
               .build(ruleContext));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
index 90a81bd..4605b94 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
@@ -459,7 +459,7 @@
                     JavaCommon.getHostJavaExecutable(ruleContext),
                     getGenClassJar(ruleContext),
                     javaToolchain.getJvmOptions())
-                .setCommandLine(
+                .addCommandLine(
                     CustomCommandLine.builder()
                         .addExecPath("--manifest_proto", manifestProto)
                         .addExecPath("--class_jar", classJar)
@@ -805,7 +805,7 @@
               .useDefaultShellEnvironment()
               .setProgressMessage("Extracting interface %s", ruleContext.getLabel())
               .setMnemonic("JavaIjar")
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder()
                       .addExecPath(inputJar)
                       .addExecPath(interfaceJar)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java
index 13432648..1ede1d0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java
@@ -40,6 +40,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -385,12 +386,15 @@
       if ((noFallback || directJars.isEmpty()) && !requiresAnnotationProcessing) {
         SpawnAction.Builder builder = new SpawnAction.Builder();
         NestedSet<Artifact> classpath;
+        final ParamFileInfo paramFileInfo;
         if (!directJars.isEmpty() || classpathEntries.isEmpty()) {
           classpath = directJars;
+          paramFileInfo = null;
         } else {
           classpath = classpathEntries;
           // Transitive classpath actions may exceed the command line length limit.
-          builder.alwaysUseParameterFile(ParameterFileType.UNQUOTED);
+          paramFileInfo =
+              ParamFileInfo.builder(ParameterFileType.UNQUOTED).setUseAlways(true).build();
         }
         CustomCommandLine.Builder commandLine =
             baseCommandLine(CustomCommandLine.builder(), classpath);
@@ -402,7 +406,7 @@
             .addTransitiveInputs(baseInputs)
             .addTransitiveInputs(classpath)
             .addOutputs(outputs)
-            .setCommandLine(commandLine.build())
+            .addCommandLine(commandLine.build(), paramFileInfo)
             .setJarExecutable(
                 JavaCommon.getHostJavaExecutable(ruleContext),
                 javaToolchain.getHeaderCompiler(),
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
index 8646b90..b3eac8b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -102,8 +103,9 @@
             .addInput(oneVersionWhitelist)
             .addTransitiveInputs(jarsToCheck)
             .setExecutable(oneVersionTool)
-            .setCommandLine(oneVersionArgs)
-            .alwaysUseParameterFile(ParameterFileType.SHELL_QUOTED)
+            .addCommandLine(
+                oneVersionArgs,
+                ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).setUseAlways(true).build())
             .setMnemonic("JavaOneVersion")
             .setProgressMessage("Checking for one-version violations in %s", ruleContext.getLabel())
             .build(ruleContext));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java
index 8255830..108e933 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java
@@ -434,7 +434,7 @@
       proguardAction
           .setProgressMessage("Trimming binary with Proguard")
           .addOutput(proguardOutputJar);
-      proguardAction.setCommandLine(commandLine.build());
+      proguardAction.addCommandLine(commandLine.build());
       ruleContext.registerAction(proguardAction.build(ruleContext));
     } else {
       // Optimization passes have been specified, so run proguard in multiple phases.
@@ -463,7 +463,7 @@
           .setProgressMessage("Trimming binary with Proguard: Verification/Shrinking Pass")
           .addOutput(lastStageOutput);
       initialCommandLine.add("-runtype INITIAL").addExecPath("-nextstageoutput", lastStageOutput);
-      initialAction.setCommandLine(initialCommandLine.build());
+      initialAction.addCommandLine(initialCommandLine.build());
       ruleContext.registerAction(initialAction.build(ruleContext));
 
       for (int i = 1; i <= optimizationPasses; i++) {
@@ -517,7 +517,7 @@
               .add("-runtype OPTIMIZATION")
               .addExecPath("-laststageoutput", lastStageOutput)
               .addExecPath("-nextstageoutput", optimizationOutput);
-          optimizationAction.setCommandLine(optimizationCommandLine.build());
+          optimizationAction.addCommandLine(optimizationCommandLine.build());
           ruleContext.registerAction(optimizationAction.build(ruleContext));
           lastStageOutput = optimizationOutput;
         }
@@ -546,7 +546,7 @@
           .addInput(lastStageOutput)
           .addOutput(proguardOutputJar);
       finalCommandLine.add("-runtype FINAL").addExecPath("-laststageoutput", lastStageOutput);
-      finalAction.setCommandLine(finalCommandLine.build());
+      finalAction.addCommandLine(finalCommandLine.build());
       ruleContext.registerAction(finalAction.build(ruleContext));
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java
index b4cbd42..0dd83de 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java
@@ -134,7 +134,7 @@
             .setExecutable(proguardWhitelister)
             .setProgressMessage("Validating proguard configuration")
             .setMnemonic("ValidateProguard")
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath("--path", specToValidate)
                     .addExecPath("--output", output)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/ResourceJarActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/ResourceJarActionBuilder.java
index 0d9cf42..4f5643c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/ResourceJarActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/ResourceJarActionBuilder.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -123,6 +124,7 @@
     if (!classpathResources.isEmpty()) {
       command.addExecPaths("--classpath_resources", classpathResources);
     }
+    ParamFileInfo paramFileInfo = null;
     // TODO(b/37444705): remove this logic and always call useParameterFile once the bug is fixed
     // Most resource jar actions are very small and expanding the argument list for
     // ParamFileHelper#getParamsFileMaybe is expensive, so avoid doing that work if
@@ -132,7 +134,7 @@
     if (sizeGreaterThanOrEqual(
             Iterables.concat(messages, resources.values(), resourceJars, classpathResources), 10)
         || ruleContext.getConfiguration().getMinParamFileSize() < 10000) {
-      builder.useParameterFile(ParameterFileType.SHELL_QUOTED);
+      paramFileInfo = ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).build();
     }
     ruleContext.registerAction(
         builder
@@ -141,7 +143,7 @@
             .addInputs(resources.values())
             .addTransitiveInputs(resourceJars)
             .addInputs(classpathResources)
-            .setCommandLine(command.build())
+            .addCommandLine(command.build(), paramFileInfo)
             .setProgressMessage("Building Java resource jar")
             .setMnemonic(MNEMONIC)
             .build(ruleContext));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/SingleJarActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/SingleJarActionBuilder.java
index 155d435..d91cfed 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/SingleJarActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/SingleJarActionBuilder.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.actions.CommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -73,8 +74,9 @@
         .addOutput(outputJar)
         .addInputs(resources.values())
         .addInputs(resourceJars)
-        .setCommandLine(sourceJarCommandLine(outputJar, resources, resourceJars))
-        .alwaysUseParameterFile(ParameterFileType.SHELL_QUOTED)
+        .addCommandLine(
+            sourceJarCommandLine(outputJar, resources, resourceJars),
+            ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).setUseAlways(true).build())
         .setProgressMessage("Building source jar %s", outputJar.prettyPrint())
         .setMnemonic("JavaSourceJar");
     ruleContext.registerAction(builder.build(ruleContext));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java
index 70556f0..6918e23 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java
@@ -162,7 +162,7 @@
     ruleContext.registerAction(
         ObjcRuleClasses.spawnAppleEnvActionBuilder(appleConfiguration, platform)
             .setExecutable(xcrunwrapper(ruleContext))
-            .setCommandLine(copyCommandLine)
+            .addCommandLine(copyCommandLine)
             .setMnemonic("CopyStubExecutable")
             .addOutput(outputBinary)
             .disableSandboxing()
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java
index d9d25fc..9735630 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java
@@ -240,7 +240,7 @@
           ObjcRuleClasses.spawnAppleEnvActionBuilder(appleConfiguration, platform)
               .setMnemonic("StoryboardCompile")
               .setExecutable(attributes.ibtoolWrapper())
-              .setCommandLine(ibActionsCommandLine(archiveRoot, zipOutput, storyboardInput))
+              .addCommandLine(ibActionsCommandLine(archiveRoot, zipOutput, storyboardInput))
               .addOutput(zipOutput)
               .addInput(storyboardInput)
               .build(ruleContext));
@@ -290,7 +290,7 @@
               .setExecutable(attributes.momcWrapper())
               .addOutput(outputZip)
               .addInputs(datamodel.getInputs())
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder()
                       .addExecPath(outputZip)
                       .addDynamicString(datamodel.archiveRootForMomczip())
@@ -319,7 +319,7 @@
           ObjcRuleClasses.spawnAppleEnvActionBuilder(appleConfiguration, platform)
               .setMnemonic("XibCompile")
               .setExecutable(attributes.ibtoolWrapper())
-              .setCommandLine(ibActionsCommandLine(archiveRoot, zipOutput, original))
+              .addCommandLine(ibActionsCommandLine(archiveRoot, zipOutput, original))
               .addOutput(zipOutput)
               .addInput(original)
               // Disable sandboxing due to Bazel issue #2189.
@@ -335,7 +335,7 @@
           ObjcRuleClasses.spawnAppleEnvActionBuilder(appleConfiguration, platform)
               .setMnemonic("ConvertStringsPlist")
               .setExecutable(PathFragment.create("/usr/bin/plutil"))
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder()
                       .add("-convert")
                       .add("binary1")
@@ -377,7 +377,7 @@
             .addTransitiveInputs(mergingContentArtifacts)
             .addOutput(bundling.getIntermediateArtifacts().mergedInfoplist())
             .addInput(plMergeControlArtifact)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath("--control", plMergeControlArtifact)
                     .build())
@@ -420,10 +420,7 @@
             .addTransitiveInputs(objcProvider.get(ASSET_CATALOG))
             .addOutput(zipOutput)
             .addOutput(actoolPartialInfoplist)
-            .setCommandLine(actoolzipCommandLine(
-                objcProvider,
-                zipOutput,
-                actoolPartialInfoplist))
+            .addCommandLine(actoolzipCommandLine(objcProvider, zipOutput, actoolPartialInfoplist))
             .disableSandboxing()
             .build(ruleContext));
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
index cf15e60..159821f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
@@ -1138,7 +1138,7 @@
               .addTransitiveInputs(j2ObjcDependencyMappingFiles)
               .addTransitiveInputs(j2ObjcHeaderMappingFiles)
               .addTransitiveInputs(j2ObjcArchiveSourceMappingFiles)
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder().addFormatted("@%s", paramFile.getExecPath()).build())
               .addOutput(prunedJ2ObjcArchive)
               .build(ruleContext));
@@ -1231,7 +1231,7 @@
                 appleConfiguration, appleConfiguration.getSingleArchPlatform())
             .setMnemonic("ObjcBinarySymbolStrip")
             .setExecutable(xcrunwrapper(ruleContext))
-            .setCommandLine(symbolStripCommandLine(stripArgs, binaryToLink, strippedBinary))
+            .addCommandLine(symbolStripCommandLine(stripArgs, binaryToLink, strippedBinary))
             .addOutput(strippedBinary)
             .addInput(binaryToLink)
             .build(ruleContext));
@@ -1431,7 +1431,7 @@
     }
     ruleContext.registerAction(
         builder
-            .setCommandLine(cmdLine.add("--").addAll(args).build())
+            .addCommandLine(cmdLine.add("--").addAll(args).build())
             .addInputs(compilationArtifacts.getPrivateHdrs())
             .addTransitiveInputs(attributes.hdrs())
             .addTransitiveInputs(objcProvider.get(ObjcProvider.HEADER))
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
index c90d5a7..a2daa20 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
@@ -35,6 +35,7 @@
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -567,7 +568,7 @@
             .addTransitiveInputs(depsHeaderMappingFiles)
             .addTransitiveInputs(depsClassMappingFiles)
             .addInput(paramFile)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder().addFormatted("@%s", paramFile.getExecPath()).build())
             .addOutputs(j2ObjcSource.getObjcSrcs())
             .addOutputs(j2ObjcSource.getObjcHdrs())
@@ -594,16 +595,18 @@
             "--source_jars", VectorArg.join(",").each(ImmutableList.copyOf(sourceJars)));
       }
       headerMapCommandLine.addExecPath("--output_mapping_file", outputHeaderMappingFile);
-      ruleContext.registerAction(new SpawnAction.Builder()
-          .setMnemonic("GenerateJ2objcHeaderMap")
-          .setExecutable(ruleContext.getPrerequisiteArtifact("$j2objc_header_map", Mode.HOST))
-          .addInput(ruleContext.getPrerequisiteArtifact("$j2objc_header_map", Mode.HOST))
-          .addInputs(sources)
-          .addInputs(sourceJars)
-          .setCommandLine(headerMapCommandLine.build())
-          .useParameterFile(ParameterFileType.SHELL_QUOTED)
-          .addOutput(outputHeaderMappingFile)
-          .build(ruleContext));
+      ruleContext.registerAction(
+          new SpawnAction.Builder()
+              .setMnemonic("GenerateJ2objcHeaderMap")
+              .setExecutable(ruleContext.getPrerequisiteArtifact("$j2objc_header_map", Mode.HOST))
+              .addInput(ruleContext.getPrerequisiteArtifact("$j2objc_header_map", Mode.HOST))
+              .addInputs(sources)
+              .addInputs(sourceJars)
+              .addCommandLine(
+                  headerMapCommandLine.build(),
+                  ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).build())
+              .addOutput(outputHeaderMappingFile)
+              .build(ruleContext));
     }
 
     return new J2ObjcMappingFileProvider(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/LegacyCompilationSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/LegacyCompilationSupport.java
index 4dc2556..8eee5e2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/LegacyCompilationSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/LegacyCompilationSupport.java
@@ -420,7 +420,7 @@
         compileBuilder
             .setMnemonic("ObjcCompile")
             .setExecutable(xcrunwrapper(ruleContext))
-            .setCommandLine(commandLine)
+            .addCommandLine(commandLine)
             .addOutput(objFile)
             .addOutputs(gcnoFile.asSet())
             .addOutput(dotdFile.artifact())
@@ -510,7 +510,7 @@
                 appleConfiguration, appleConfiguration.getSingleArchPlatform())
             .setMnemonic("ObjcLink")
             .setExecutable(libtool(ruleContext))
-            .setCommandLine(
+            .addCommandLine(
                 new CustomCommandLine.Builder()
                     .add("-static")
                     .addExecPath("-filelist", objList)
@@ -533,7 +533,7 @@
                 appleConfiguration, appleConfiguration.getSingleArchPlatform())
             .setMnemonic("ObjcLink")
             .setExecutable(libtool(ruleContext))
-            .setCommandLine(
+            .addCommandLine(
                 new CustomCommandLine.Builder()
                     .add("-static")
                     .add("-arch_only", appleConfiguration.getSingleArchitecture())
@@ -634,7 +634,7 @@
                 appleConfiguration, appleConfiguration.getSingleArchPlatform())
             .setMnemonic("ObjcLink")
             .setShellCommand(ImmutableList.of("/bin/bash", "-c"))
-            .setCommandLine(new SingleArgCommandLine(commandLine))
+            .addCommandLine(new SingleArgCommandLine(commandLine))
             .addOutput(binaryToLink)
             .addOutputs(dsymBundleZip.asSet())
             .addOutputs(linkmap.asSet())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/LipoSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/LipoSupport.java
index 20471b3..f18f610 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/LipoSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/LipoSupport.java
@@ -53,7 +53,7 @@
               .addTransitiveInputs(inputBinaries)
               .addOutput(outputBinary)
               .setExecutable(CompilationSupport.xcrunwrapper(ruleContext))
-              .setCommandLine(
+              .addCommandLine(
                   CustomCommandLine.builder()
                       .add(ObjcRuleClasses.LIPO)
                       .addExecPaths("-create", inputBinaries)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java
index 5d7bb41..e13c67c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java
@@ -457,7 +457,7 @@
             .addOutputs(getGeneratedProtoOutputs(outputProtos, HEADER_SUFFIX))
             .addOutputs(getProtoSourceFilesForCompilation(outputProtos))
             .setExecutable(attributes.getProtoCompiler().getExecPath())
-            .setCommandLine(getGenerationCommandLine(protoInputsFile))
+            .addCommandLine(getGenerationCommandLine(protoInputsFile))
             .build(ruleContext));
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ProtocolBuffers2Support.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtocolBuffers2Support.java
index 67dd444..7e13b35 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ProtocolBuffers2Support.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtocolBuffers2Support.java
@@ -75,7 +75,7 @@
             .addOutputs(getGeneratedProtoOutputs(getHeaderExtension()))
             .addOutputs(getGeneratedProtoOutputs(getSourceExtension()))
             .setExecutable(PathFragment.create("/usr/bin/python"))
-            .setCommandLine(getGenerationCommandLine())
+            .addCommandLine(getGenerationCommandLine())
             .build(ruleContext));
     return this;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
index d33cb96..648bba2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
@@ -390,7 +390,7 @@
             .setMnemonic("EnvironmentPlist")
             .setExecutable(attributes.environmentPlist())
             .addOutput(getGeneratedEnvironmentPlist())
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .add("--platform", platformWithVersion)
                     .addExecPath("--output", getGeneratedEnvironmentPlist())
@@ -609,7 +609,7 @@
             .addTransitiveInputs(entitlements)
             .addOutput(intermediateArtifacts.entitlements())
             .addInput(plMergeControlArtifact)
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder()
                     .addExecPath("--control", plMergeControlArtifact)
                     .build())
@@ -861,7 +861,7 @@
             .addInput(bundleMergeControlArtifact)
             .addTransitiveInputs(bundling.getBundleContentArtifacts())
             .addOutput(intermediateArtifacts.unprocessedIpa())
-            .setCommandLine(
+            .addCommandLine(
                 CustomCommandLine.builder().addExecPath(bundleMergeControlArtifact).build())
             .build(ruleContext));
   }
@@ -1048,7 +1048,7 @@
         ObjcRuleClasses.spawnAppleEnvActionBuilder(appleConfiguration, platform)
             .setMnemonic("SwiftStdlibCopy")
             .setExecutable(attributes.swiftStdlibToolWrapper())
-            .setCommandLine(commandLine.build())
+            .addCommandLine(commandLine.build())
             .addOutput(intermediateArtifacts.swiftFrameworksFileZip())
             .addInput(combinedArchBinary)
             .build(ruleContext));
@@ -1080,7 +1080,7 @@
         ObjcRuleClasses.spawnAppleEnvActionBuilder(configuration, platform)
             .setMnemonic("SwiftCopySwiftSupport")
             .setExecutable(attributes.swiftStdlibToolWrapper())
-            .setCommandLine(commandLine.build())
+            .addCommandLine(commandLine.build())
             .addOutput(intermediateArtifacts.swiftSupportZip())
             .addInput(combinedArchBinary)
             .build(ruleContext));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilder.java
index 265b7d6..6022fc4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilder.java
@@ -28,7 +28,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.actions.ParameterFile;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
 import com.google.devtools.build.lib.actions.ResourceSet;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
 import com.google.devtools.build.lib.analysis.MakeVariableExpander;
@@ -37,6 +37,7 @@
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
 import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -251,12 +252,13 @@
     }
 
     result
-        .useParameterFile(ParameterFile.ParameterFileType.UNQUOTED)
         .addOutputs(outputs)
         .setResources(GENPROTO_RESOURCE_SET)
         .useDefaultShellEnvironment()
         .setExecutable(compilerTarget)
-        .setCommandLine(createProtoCompilerCommandLine().build())
+        .addCommandLine(
+            createProtoCompilerCommandLine().build(),
+            ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
         .setProgressMessage("Generating %s proto_library %s", language, ruleContext.getLabel())
         .setMnemonic(MNEMONIC);
 
@@ -455,12 +457,11 @@
     }
 
     result
-        .useParameterFile(ParameterFile.ParameterFileType.UNQUOTED)
         .addOutputs(outputs)
         .setResources(GENPROTO_RESOURCE_SET)
         .useDefaultShellEnvironment()
         .setExecutable(compilerTarget)
-        .setCommandLine(
+        .addCommandLine(
             createCommandLineFromToolchains(
                 toolchainInvocations,
                 protosToCompile,
@@ -468,7 +469,8 @@
                 areDepsStrict(ruleContext) ? protosInDirectDeps : null,
                 ruleLabel,
                 allowServices,
-                ruleContext.getFragment(ProtoConfiguration.class).protocOpts()))
+                ruleContext.getFragment(ProtoConfiguration.class).protocOpts()),
+            ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
         .setProgressMessage("Generating %s proto_library %s", flavorName, ruleContext.getLabel())
         .setMnemonic(MNEMONIC);
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PythonUtils.java b/src/main/java/com/google/devtools/build/lib/rules/python/PythonUtils.java
index 824fef7..fe2133f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PythonUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PythonUtils.java
@@ -125,7 +125,7 @@
             .setExecutable(py2to3converter)
             .setProgressMessage("Converting to Python 3: %s", input.prettyPrint())
             .setMnemonic("2to3")
-            .setCommandLine(commandLine.build())
+            .addCommandLine(commandLine.build())
             .build(ruleContext));
     return output;
   }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
index 04414a4..1bc7baf 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
@@ -87,7 +87,7 @@
             .setProgressMessage("hi, mom!")
             .setMnemonic("Dummy")
             .setEnvironment(environmentVariables)
-            .setCommandLine(CommandLine.of(arguments))
+            .addCommandLine(CommandLine.of(arguments))
             .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
     collectingAnalysisEnvironment.registerAction(actions);
     return (SpawnAction) actions[0];
@@ -173,8 +173,9 @@
                 jarArtifact,
                 "MyMainClass",
                 asList("-jvmarg"))
-            .useParameterFile(ParameterFileType.UNQUOTED)
-            .setCommandLine(CustomCommandLine.builder().add("-X").build())
+            .addCommandLine(
+                CustomCommandLine.builder().add("-X").build(),
+                ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
     collectingAnalysisEnvironment.registerAction(actions);
     SpawnAction action = (SpawnAction) actions[0];
@@ -216,8 +217,12 @@
                 jarArtifact,
                 "MyMainClass",
                 asList("-jvmarg"))
-            .setCommandLine(CustomCommandLine.builder().add("-X").build())
-            .useParameterFile(ParameterFileType.UNQUOTED, ISO_8859_1, "--flagfile=")
+            .addCommandLine(
+                CustomCommandLine.builder().add("-X").build(),
+                ParamFileInfo.builder(ParameterFileType.UNQUOTED)
+                    .setCharset(ISO_8859_1)
+                    .setFlag("--flagfile=")
+                    .build())
             .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
     collectingAnalysisEnvironment.registerAction(actions);
     SpawnAction action = (SpawnAction) actions[0];
@@ -253,7 +258,7 @@
                 "MyMainClass",
                 asList("-jvmarg"))
             .addExecutableArguments("execArg1", "execArg2")
-            .setCommandLine(CustomCommandLine.builder().add("arg1").build())
+            .addCommandLine(CustomCommandLine.builder().add("arg1").build())
             .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
     collectingAnalysisEnvironment.registerAction(actions);
     SpawnAction action = (SpawnAction) actions[0];
@@ -286,8 +291,9 @@
                 "MyMainClass",
                 asList("-jvmarg"))
             .addExecutableArguments("execArg1", "execArg2")
-            .useParameterFile(ParameterFileType.UNQUOTED)
-            .setCommandLine(CustomCommandLine.builder().add("arg1").add("arg2").add("arg3").build())
+            .addCommandLine(
+                CustomCommandLine.builder().add("arg1").add("arg2").add("arg3").build(),
+                ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
             .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
     collectingAnalysisEnvironment.registerAction(actions);
     SpawnAction action = (SpawnAction) actions[0];
@@ -338,8 +344,9 @@
             builder()
                 .addOutput(output1)
                 .setExecutable(executable)
-                .useParameterFile(ParameterFileType.UNQUOTED)
-                .setCommandLine(CustomCommandLine.builder().addDynamicString(longOption).build())
+                .addCommandLine(
+                    CustomCommandLine.builder().addDynamicString(longOption).build(),
+                    ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
                 .build(
                     ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig)[
                 0]);
@@ -352,8 +359,9 @@
             builder()
                 .addOutput(output2)
                 .setExecutable(executable)
-                .useParameterFile(ParameterFileType.UNQUOTED)
-                .setCommandLine(CustomCommandLine.builder().addDynamicString(longOption).build())
+                .addCommandLine(
+                    CustomCommandLine.builder().addDynamicString(longOption).build(),
+                    ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
                 .build(
                     ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig)[
                 0]);
@@ -361,6 +369,48 @@
   }
 
   @Test
+  public void testMultipleCommandLines() throws Exception {
+    Artifact input = getSourceArtifact("input");
+    Artifact output = getBinArtifactWithNoOwner("output");
+    Action[] actions =
+        builder()
+            .addInput(input)
+            .addOutput(output)
+            .setExecutable(scratch.file("/bin/xxx").asFragment())
+            .addCommandLine(CommandLine.of(ImmutableList.of("arg1")))
+            .addCommandLine(CommandLine.of(ImmutableList.of("arg2")))
+            .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+    SpawnAction action = (SpawnAction) actions[0];
+    assertThat(action.getArguments()).containsExactly("/bin/xxx", "arg1", "arg2");
+  }
+
+  @Test
+  public void testMultipleParameterFiles() throws Exception {
+    useConfiguration("--min_param_file_size=0");
+    Artifact input = getSourceArtifact("input");
+    Artifact output = getBinArtifactWithNoOwner("output");
+    Artifact paramFile1 = getBinArtifactWithNoOwner("output-2.params");
+    Artifact paramFile2 = getBinArtifactWithNoOwner("output-3.params");
+    Action[] actions =
+        builder()
+            .addInput(input)
+            .addOutput(output)
+            .setExecutable(scratch.file("/bin/xxx").asFragment())
+            .addCommandLine(
+                CommandLine.of(ImmutableList.of("arg1")),
+                ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
+            .addCommandLine(
+                CommandLine.of(ImmutableList.of("arg2")),
+                ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
+            .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+    SpawnAction action = (SpawnAction) actions[0];
+    assertThat(actions).hasLength(3);
+    assertThat(action.getArguments())
+        .containsExactly(
+            "/bin/xxx", "@" + paramFile1.getExecPathString(), "@" + paramFile2.getExecPathString());
+  }
+
+  @Test
   public void testExtraActionInfo() throws Exception {
     SpawnAction action = createCopyFromWelcomeToDestination(ImmutableMap.<String, String>of());
     ExtraActionInfo info = action.getExtraActionInfo().build();