Symlink creation: pass symlinks to OutputService

This allows implementations of the OutputService to skip reading the
manifest file and instead use the in-memory data.

PiperOrigin-RevId: 281464135
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
index bd1c7a8..4dba210 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
@@ -13,8 +13,10 @@
 // limitations under the License.
 package com.google.devtools.build.lib.exec;
 
+import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.actions.ActionExecutionContext;
 import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.EnvironmentalExecException;
 import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.ExecutionStrategy;
 import com.google.devtools.build.lib.actions.RunningActionEvent;
@@ -22,6 +24,9 @@
 import com.google.devtools.build.lib.analysis.actions.SymlinkTreeActionContext;
 import com.google.devtools.build.lib.profiler.AutoProfiler;
 import com.google.devtools.build.lib.vfs.OutputService;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.logging.Logger;
@@ -52,8 +57,25 @@
             "running " + action.prettyPrint(), logger, /*minTimeForLoggingInMilliseconds=*/ 100)) {
       try {
         if (outputService != null && outputService.canCreateSymlinkTree()) {
+          Map<PathFragment, Path> symlinks = null;
+          if (action.getRunfiles() != null) {
+            try {
+              symlinks =
+                  Maps.transformValues(
+                      action
+                          .getRunfiles()
+                          .getRunfilesInputs(
+                              actionExecutionContext.getEventHandler(),
+                              action.getOwner().getLocation(),
+                              actionExecutionContext.getPathResolver()),
+                      (artifact) -> artifact.getPath());
+            } catch (IOException e) {
+              throw new EnvironmentalExecException(e);
+            }
+          }
           outputService.createSymlinkTree(
               actionExecutionContext.getInputPath(action.getInputManifest()),
+              symlinks,
               actionExecutionContext.getInputPath(action.getOutputManifest()),
               action.isFilesetTree(),
               action.getOutputManifest().getExecPath().getParentDirectory());
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java
index c1157a0..da925e6 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java
@@ -107,7 +107,11 @@
 
   @Override
   public void createSymlinkTree(
-      Path inputManifest, Path outputManifest, boolean filesetTree, PathFragment symlinkTreeRoot) {
+      Path inputManifest,
+      @Nullable Map<PathFragment, Path> symlinks,
+      Path outputManifest,
+      boolean filesetTree,
+      PathFragment symlinkTreeRoot) {
     throw new UnsupportedOperationException();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
index 1fa759f..8f604a5 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
@@ -120,14 +120,20 @@
    * Creates the symlink tree
    *
    * @param inputPath the input manifest
+   * @param symlinks the symlinks to create
    * @param outputPath the output manifest
    * @param filesetTree is true iff we're constructing a Fileset
    * @param symlinkTreeRoot the symlink tree root, relative to the execRoot
    * @throws ExecException on failure
    * @throws InterruptedException
    */
-  void createSymlinkTree(Path inputPath, Path outputPath, boolean filesetTree,
-      PathFragment symlinkTreeRoot) throws ExecException, InterruptedException;
+  void createSymlinkTree(
+      Path inputPath,
+      @Nullable Map<PathFragment, Path> symlinks,
+      Path outputPath,
+      boolean filesetTree,
+      PathFragment symlinkTreeRoot)
+      throws ExecException, InterruptedException;
 
   /**
    * Cleans the entire output tree.