Fix a bug that outputs from aspects are not downloaded.

Previously, we changed how to handle toplevel outputs with https://github.com/bazelbuild/bazel/commit/3ef2e53ab65eab006b0d01c41ebc2265754b2007. It introduced a bug that outputs from output groups registered by toplevel aspects (combination of `--aspects` and `--output_groups`) were not downloaded. This CL fixes that.

PiperOrigin-RevId: 532460057
Change-Id: I8bd2ad872c0038c3006779547994ef060ea113d7
diff --git a/src/main/java/com/google/devtools/build/lib/remote/BUILD b/src/main/java/com/google/devtools/build/lib/remote/BUILD
index cd77fd8..c3136da 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/remote/BUILD
@@ -191,10 +191,12 @@
         "//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
         "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
+        "//src/main/java/com/google/devtools/build/lib/analysis:provider_collection",
         "//src/main/java/com/google/devtools/build/lib/analysis:top_level_artifact_context",
         "//src/main/java/com/google/devtools/build/lib/clock",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//third_party:guava",
+        "//third_party:jsr305",
     ],
 )
 
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputChecker.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputChecker.java
index 8418237..8a8e324 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputChecker.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.analysis.AnalysisResult;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.ProviderCollection;
 import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
 import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
 import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
@@ -32,6 +33,7 @@
 import java.util.Set;
 import java.util.function.Supplier;
 import java.util.regex.Pattern;
+import javax.annotation.Nullable;
 
 /** A {@link RemoteArtifactChecker} that checks the TTL of remote metadata. */
 public class RemoteOutputChecker implements RemoteArtifactChecker {
@@ -79,36 +81,40 @@
   public void afterTopLevelTargetAnalysis(
       ConfiguredTarget configuredTarget,
       Supplier<TopLevelArtifactContext> topLevelArtifactContextSupplier) {
-    addTopLevelTarget(configuredTarget, topLevelArtifactContextSupplier);
+    addTopLevelTarget(configuredTarget, configuredTarget, topLevelArtifactContextSupplier);
   }
 
   public void afterAnalysis(AnalysisResult analysisResult) {
     for (var target : analysisResult.getTargetsToBuild()) {
-      addTopLevelTarget(target, analysisResult::getTopLevelContext);
+      addTopLevelTarget(target, target, analysisResult::getTopLevelContext);
+    }
+    for (var aspect : analysisResult.getAspectsMap().values()) {
+      addTopLevelTarget(aspect, /* configuredTarget= */ null, analysisResult::getTopLevelContext);
     }
     var targetsToTest = analysisResult.getTargetsToTest();
     if (targetsToTest != null) {
       for (var target : targetsToTest) {
-        addTopLevelTarget(target, analysisResult::getTopLevelContext);
+        addTopLevelTarget(target, target, analysisResult::getTopLevelContext);
       }
     }
   }
 
   private void addTopLevelTarget(
-      ConfiguredTarget toplevelTarget,
+      ProviderCollection target,
+      @Nullable ConfiguredTarget configuredTarget,
       Supplier<TopLevelArtifactContext> topLevelArtifactContextSupplier) {
-    if (shouldDownloadToplevelOutputs(toplevelTarget)) {
+    if (shouldDownloadToplevelOutputs(configuredTarget)) {
       var topLevelArtifactContext = topLevelArtifactContextSupplier.get();
       var artifactsToBuild =
-          TopLevelArtifactHelper.getAllArtifactsToBuild(toplevelTarget, topLevelArtifactContext)
+          TopLevelArtifactHelper.getAllArtifactsToBuild(target, topLevelArtifactContext)
               .getImportantArtifacts();
       toplevelArtifactsToDownload.addAll(artifactsToBuild.toList());
 
-      addRunfiles(toplevelTarget);
+      addRunfiles(target);
     }
   }
 
-  private void addRunfiles(ConfiguredTarget buildTarget) {
+  private void addRunfiles(ProviderCollection buildTarget) {
     var runfilesProvider = buildTarget.getProvider(FilesToRunProvider.class);
     if (runfilesProvider == null) {
       return;
@@ -125,7 +131,11 @@
     }
   }
 
-  private boolean shouldDownloadToplevelOutputs(ConfiguredTarget configuredTarget) {
+  public void addInputToDownload(ActionInput file) {
+    inputsToDownload.add(file);
+  }
+
+  private boolean shouldDownloadToplevelOutputs(@Nullable ConfiguredTarget configuredTarget) {
     switch (commandMode) {
       case RUN:
         // Always download outputs of toplevel targets in RUN mode
diff --git a/src/test/java/com/google/devtools/build/lib/remote/BuildWithoutTheBytesIntegrationTestBase.java b/src/test/java/com/google/devtools/build/lib/remote/BuildWithoutTheBytesIntegrationTestBase.java
index 83bb5b4..ae63822 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/BuildWithoutTheBytesIntegrationTestBase.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/BuildWithoutTheBytesIntegrationTestBase.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertThrows;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.eventbus.Subscribe;
 import com.google.devtools.build.lib.actions.ActionExecutedEvent;
@@ -517,6 +518,93 @@
   }
 
   @Test
+  public void downloadToplevel_outputsFromAspect_notAggregated() throws Exception {
+    setDownloadToplevel();
+    writeCopyAspectRule(/* aggregate= */ false);
+    write(
+        "BUILD",
+        "genrule(",
+        "  name = 'foo',",
+        "  srcs = ['foo.in'],",
+        "  outs = ['foo.out'],",
+        "  cmd = 'cat $(SRCS) > $@',",
+        ")",
+        "genrule(",
+        "  name = 'foobar',",
+        "  srcs = [':foo'],",
+        "  outs = ['foobar.out'],",
+        "  cmd = 'cat $(location :foo) > $@ && echo bar >> $@',",
+        ")");
+    write("foo.in", "foo");
+
+    addOptions("--aspects=rules.bzl%copy_aspect", "--output_groups=+copy");
+    buildTarget("//:foobar");
+    waitDownloads();
+
+    assertValidOutputFile("foobar.out", "foo" + lineSeparator() + "bar\n");
+    assertOutputDoesNotExist("foo.in.copy");
+    assertValidOutputFile("foo.out.copy", "foo" + lineSeparator());
+  }
+
+  @Test
+  public void downloadToplevel_outputsFromAspect_aggregated() throws Exception {
+    setDownloadToplevel();
+    writeCopyAspectRule(/* aggregate= */ true);
+    write(
+        "BUILD",
+        "genrule(",
+        "  name = 'foo',",
+        "  srcs = ['foo.in'],",
+        "  outs = ['foo.out'],",
+        "  cmd = 'cat $(SRCS) > $@',",
+        ")",
+        "genrule(",
+        "  name = 'foobar',",
+        "  srcs = [':foo'],",
+        "  outs = ['foobar.out'],",
+        "  cmd = 'cat $(location :foo) > $@ && echo bar >> $@',",
+        ")");
+    write("foo.in", "foo");
+
+    addOptions("--aspects=rules.bzl%copy_aspect", "--output_groups=+copy");
+    buildTarget("//:foobar");
+    waitDownloads();
+
+    assertValidOutputFile("foobar.out", "foo" + lineSeparator() + "bar\n");
+    assertValidOutputFile("foo.in.copy", "foo" + lineSeparator());
+    assertValidOutputFile("foo.out.copy", "foo" + lineSeparator());
+  }
+
+  @Test
+  public void downloadToplevel_outputsFromAspect_notDownloadedIfNoOutputGroups() throws Exception {
+    setDownloadToplevel();
+    writeCopyAspectRule(/* aggregate= */ true);
+    write(
+        "BUILD",
+        "genrule(",
+        "  name = 'foo',",
+        "  srcs = ['foo.in'],",
+        "  outs = ['foo.out'],",
+        "  cmd = 'cat $(SRCS) > $@',",
+        ")",
+        "genrule(",
+        "  name = 'foobar',",
+        "  srcs = [':foo'],",
+        "  outs = ['foobar.out'],",
+        "  cmd = 'cat $(location :foo) > $@ && echo bar >> $@',",
+        ")");
+    write("foo.in", "foo");
+
+    addOptions("--aspects=rules.bzl%copy_aspect");
+    buildTarget("//:foobar");
+    waitDownloads();
+
+    assertValidOutputFile("foobar.out", "foo" + lineSeparator() + "bar\n");
+    assertOutputDoesNotExist("foo.in.copy");
+    assertOutputDoesNotExist("foo.out.copy");
+  }
+
+  @Test
   public void downloadToplevel_outputsFromImportantOutputGroupAreDownloaded() throws Exception {
     setDownloadToplevel();
     write(
@@ -1333,6 +1421,44 @@
         ")");
   }
 
+  protected void writeCopyAspectRule(boolean aggregate) throws IOException {
+    var lines = ImmutableList.<String>builder();
+    lines.add(
+        "def _copy_aspect_impl(target, ctx):",
+        "  files = []",
+        "  for src in ctx.rule.files.srcs:",
+        "    dst = ctx.actions.declare_file(src.basename + '.copy')",
+        "    ctx.actions.run_shell(",
+        "      inputs = [src],",
+        "      outputs = [dst],",
+        "      command = '''",
+        "cp $1 $2",
+        "''',",
+        "      arguments = [src.path, dst.path],",
+        "    )",
+        "    files.append(dst)",
+        "");
+    if (aggregate) {
+      lines.add(
+          "  files = depset(",
+          "    direct = files,",
+          "    transitive = [src[OutputGroupInfo].copy for src in ctx.rule.attr.srcs if"
+              + " OutputGroupInfo in src],",
+          "  )");
+    } else {
+      lines.add("  files = depset(files)");
+    }
+    lines.add(
+        "",
+        "  return [OutputGroupInfo(copy = files)]",
+        "",
+        "copy_aspect = aspect(",
+        "  implementation = _copy_aspect_impl,",
+        "  attr_aspects = ['srcs'],",
+        ")");
+    write("rules.bzl", lines.build().toArray(new String[0]));
+  }
+
   protected static class ActionEventCollector {
     private final List<ActionExecutedEvent> actionExecutedEvents = new ArrayList<>();
     private final List<CachedActionEvent> cachedActionEvents = new ArrayList<>();