remote: implement --experimental_remote_download_outputs=toplevel

This implements the second milestone of "Remote builds without the
Bytes" and introduces the "toplevel" option for the
--experimental_remote_download_outputs flag. This mode will only
download outputs of top level targets but not outputs of intermediate
actions.

On a build of Bazel against RBE I am still seeing a 50% speed up
compared to the "all" mode.

Progress towards #6862.

Closes #7984.

PiperOrigin-RevId: 243029119
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
index 3b1732d..4bd91ae 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
@@ -16,13 +16,18 @@
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static com.google.devtools.build.lib.remote.util.Utils.createSpawnResult;
 import static com.google.devtools.build.lib.remote.util.Utils.getInMemoryOutputPath;
+import static com.google.devtools.build.lib.remote.util.Utils.hasTopLevelOutputs;
+import static com.google.devtools.build.lib.remote.util.Utils.shouldDownloadAllSpawnOutputs;
 
 import build.bazel.remote.execution.v2.Action;
 import build.bazel.remote.execution.v2.ActionResult;
 import build.bazel.remote.execution.v2.Command;
 import build.bazel.remote.execution.v2.Digest;
 import build.bazel.remote.execution.v2.Platform;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.ExecutionStrategy;
 import com.google.devtools.build.lib.actions.FileArtifactValue;
@@ -64,6 +69,7 @@
     name = {"remote-cache"},
     contextType = SpawnCache.class)
 final class RemoteSpawnCache implements SpawnCache {
+
   private final Path execRoot;
   private final RemoteOptions options;
 
@@ -77,6 +83,14 @@
 
   private final DigestUtil digestUtil;
 
+  /**
+   * Set of artifacts that are top level outputs
+   *
+   * <p>This set is empty unless {@link RemoteOutputsMode#TOPLEVEL} is specified. If so, this set is
+   * used to decide whether to download an output.
+   */
+  private final ImmutableSet<Artifact> topLevelOutputs;
+
   RemoteSpawnCache(
       Path execRoot,
       RemoteOptions options,
@@ -84,7 +98,8 @@
       String buildRequestId,
       String commandId,
       @Nullable Reporter cmdlineReporter,
-      DigestUtil digestUtil) {
+      DigestUtil digestUtil,
+      ImmutableSet<Artifact> topLevelOutputs) {
     this.execRoot = execRoot;
     this.options = options;
     this.remoteCache = remoteCache;
@@ -92,8 +107,10 @@
     this.buildRequestId = buildRequestId;
     this.commandId = commandId;
     this.digestUtil = digestUtil;
+    this.topLevelOutputs = Preconditions.checkNotNull(topLevelOutputs, "topLevelOutputs");
   }
 
+
   @Override
   public CacheHandle lookup(Spawn spawn, SpawnExecutionContext context)
       throws InterruptedException, IOException, ExecException {
@@ -138,31 +155,34 @@
         try (SilentCloseable c = prof.profile(ProfilerTask.REMOTE_CACHE_CHECK, "check cache hit")) {
           result = remoteCache.getCachedActionResult(actionKey);
         }
+        // In case the remote cache returned a failed action (exit code != 0) we treat it as a
+        // cache miss
         if (result != null && result.getExitCode() == 0) {
-          // In case if failed action returned (exit code != 0) we treat it as a cache miss
-          // Otherwise, we know that result exists.
-          PathFragment inMemoryOutputPath = getInMemoryOutputPath(spawn);
           InMemoryOutput inMemoryOutput = null;
-          switch (remoteOutputsMode) {
-            case MINIMAL:
-              try (SilentCloseable c =
-                  prof.profile(ProfilerTask.REMOTE_DOWNLOAD, "download outputs minimal")) {
-                inMemoryOutput =
-                    remoteCache.downloadMinimal(
-                        result,
-                        spawn.getOutputFiles(),
-                        inMemoryOutputPath,
-                        context.getFileOutErr(),
-                        execRoot,
-                        context.getMetadataInjector());
-              }
-              break;
-            case ALL:
-              try (SilentCloseable c =
-                  prof.profile(ProfilerTask.REMOTE_DOWNLOAD, "download outputs")) {
-                remoteCache.download(result, execRoot, context.getFileOutErr());
-              }
-              break;
+          boolean downloadOutputs =
+              shouldDownloadAllSpawnOutputs(
+                  remoteOutputsMode,
+                  /* exitCode = */ 0,
+                  hasTopLevelOutputs(spawn.getOutputFiles(), topLevelOutputs));
+          if (downloadOutputs) {
+            try (SilentCloseable c =
+                prof.profile(ProfilerTask.REMOTE_DOWNLOAD, "download outputs")) {
+              remoteCache.download(result, execRoot, context.getFileOutErr());
+            }
+          } else {
+            PathFragment inMemoryOutputPath = getInMemoryOutputPath(spawn);
+            // inject output metadata
+            try (SilentCloseable c =
+                prof.profile(ProfilerTask.REMOTE_DOWNLOAD, "download outputs minimal")) {
+              inMemoryOutput =
+                  remoteCache.downloadMinimal(
+                      result,
+                      spawn.getOutputFiles(),
+                      inMemoryOutputPath,
+                      context.getFileOutErr(),
+                      execRoot,
+                      context.getMetadataInjector());
+            }
           }
           SpawnResult spawnResult =
               createSpawnResult(
@@ -174,10 +194,10 @@
       } catch (IOException e) {
         String errorMsg = e.getMessage();
         if (isNullOrEmpty(errorMsg)) {
-            errorMsg = e.getClass().getSimpleName();
+          errorMsg = e.getClass().getSimpleName();
         }
-          errorMsg = "Reading from Remote Cache:\n" + errorMsg;
-          report(Event.warn(errorMsg));
+        errorMsg = "Reading from Remote Cache:\n" + errorMsg;
+        report(Event.warn(errorMsg));
       } finally {
         withMetadata.detach(previous);
       }