Add param file support to sandboxed spawn runners.

Spawns can contain param files in the form of virtual action inputs. Sandboxed runners must write these prior to execution of the action.

RELNOTES: None
PiperOrigin-RevId: 194951208
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
index 43db377..29b6065 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
@@ -16,22 +16,21 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.lib.actions.ActionExecutionContext;
 import com.google.devtools.build.lib.actions.ActionInput;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
+import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
 import com.google.devtools.build.lib.actions.Spawn;
 import com.google.devtools.build.lib.actions.Spawns;
 import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
 import com.google.devtools.build.lib.actions.cache.VirtualActionInput.EmptyActionInput;
 import com.google.devtools.build.lib.analysis.test.TestConfiguration;
-import com.google.devtools.build.lib.exec.SpawnInputExpander;
 import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext;
-import com.google.devtools.build.lib.rules.fileset.FilesetActionContext;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.common.options.OptionsProvider;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -43,40 +42,31 @@
   /**
    * Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
    * host filesystem where the input files can be found.
+   *
+   * <p>Also writes any supported {@link VirtualActionInput}s found.
+   *
+   * @throws IOException If any files could not be written.
    */
-  public static Map<PathFragment, Path> getInputFiles(
-      SpawnInputExpander spawnInputExpander,
-      Path execRoot,
-      Spawn spawn,
-      ActionExecutionContext executionContext)
-      throws IOException {
-    Map<PathFragment, ActionInput> inputMap =
-        spawnInputExpander.getInputMapping(
-            spawn,
-            executionContext.getArtifactExpander(),
-            executionContext.getActionInputFileCache(),
-            executionContext.getContext(FilesetActionContext.class));
-    return postProcess(inputMap, spawn, executionContext.getArtifactExpander(), execRoot);
-  }
-
-  /**
-   * Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
-   * host filesystem where the input files can be found.
-   */
-  public static Map<PathFragment, Path> getInputFiles(
+  public static Map<PathFragment, Path> processInputFiles(
       Spawn spawn, SpawnExecutionContext context, Path execRoot) throws IOException {
-    return postProcess(context.getInputMapping(), spawn, context.getArtifactExpander(), execRoot);
+    return processInputFiles(
+        context.getInputMapping(), spawn, context.getArtifactExpander(), execRoot);
   }
 
   /**
    * Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
    * host filesystem where the input files can be found.
+   *
+   * <p>Also writes any supported {@link VirtualActionInput}s found.
+   *
+   * @throws IOException If any files could not be written.
    */
-  private static Map<PathFragment, Path> postProcess(
+  private static Map<PathFragment, Path> processInputFiles(
       Map<PathFragment, ActionInput> inputMap,
-      Spawn spawn, 
+      Spawn spawn,
       ArtifactExpander artifactExpander,
-      Path execRoot) {
+      Path execRoot)
+      throws IOException {
     // SpawnInputExpander#getInputMapping uses ArtifactExpander#expandArtifacts to expand
     // middlemen and tree artifacts, which expands empty tree artifacts to no entry. However,
     // actions that accept TreeArtifacts as inputs generally expect that the empty directory is
@@ -96,15 +86,26 @@
 
     Map<PathFragment, Path> inputFiles = new TreeMap<>();
     for (Map.Entry<PathFragment, ActionInput> e : inputMap.entrySet()) {
-      if (e.getValue() instanceof VirtualActionInput) {
-        // TODO(ulfjack): Handle all virtual inputs, e.g., by writing them to a file.
-        Preconditions.checkState(e.getValue() instanceof EmptyActionInput);
+      PathFragment pathFragment = e.getKey();
+      ActionInput actionInput = e.getValue();
+      if (actionInput instanceof VirtualActionInput) {
+        if (actionInput instanceof ParamFileActionInput) {
+          ParamFileActionInput paramFileInput = (ParamFileActionInput) actionInput;
+          Path outputPath = execRoot.getRelative(paramFileInput.getExecPath());
+          outputPath.getParentDirectory().createDirectoryAndParents();
+          try (OutputStream outputStream = outputPath.getOutputStream()) {
+            paramFileInput.writeTo(outputStream);
+          }
+        } else {
+          // TODO(ulfjack): Handle all virtual inputs, e.g., by writing them to a file.
+          Preconditions.checkState(actionInput instanceof EmptyActionInput);
+        }
       }
       Path inputPath =
-          e.getValue() instanceof EmptyActionInput
+          actionInput instanceof EmptyActionInput
               ? null
-              : execRoot.getRelative(e.getValue().getExecPath());
-      inputFiles.put(e.getKey(), inputPath);
+              : execRoot.getRelative(actionInput.getExecPath());
+      inputFiles.put(pathFragment, inputPath);
     }
     return inputFiles;
   }