Post the build tool logs event from the BuildTool

Collect the relevant data in the BuildResult and post the build tool
logs event from a central location rather than a module. Maybe not the
ideal location, but we have multiple modules that need to contribute
to build tool logs, so it must be posted from a common location. It
could be in a module, except that would require modules depending on /
extending other modules, which we don't support right now.

Note that this change includes a fix to LastBuildEvent, without which
this wouldn't work since this change is changing the order of the last
two events posted.

PiperOrigin-RevId: 222061608
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/LastBuildEvent.java b/src/main/java/com/google/devtools/build/lib/buildeventstream/LastBuildEvent.java
index 59724a0..1105be6 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventstream/LastBuildEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/LastBuildEvent.java
@@ -35,6 +35,11 @@
   }
 
   @Override
+  public Collection<LocalFile> referencedLocalFiles() {
+    return event.referencedLocalFiles();
+  }
+
+  @Override
   public BuildEventStreamProtos.BuildEvent asStreamProto(BuildEventContext converters) {
     return BuildEventStreamProtos.BuildEvent.newBuilder(event.asStreamProto(converters))
         .setLastMessage(true)
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java
index 5ca0204..20cf11f 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java
@@ -19,10 +19,16 @@
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+import com.google.devtools.build.lib.buildeventstream.BuildToolLogs;
 import com.google.devtools.build.lib.skyframe.AspectValue;
 import com.google.devtools.build.lib.util.ExitCode;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.protobuf.ByteString;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import javax.annotation.Nullable;
 
 /**
@@ -45,6 +51,8 @@
   private Collection<ConfiguredTarget> skippedTargets;
   private Collection<AspectValue> successfulAspects;
 
+  private final BuildToolLogCollection buildToolLogCollection = new BuildToolLogCollection();
+
   public BuildResult(long startTimeMillis) {
     this.startTimeMillis = startTimeMillis;
   }
@@ -241,6 +249,15 @@
     return skippedTargets;
   }
 
+  /**
+   * Collection of data for the build tool logs event. This may only be modified until the
+   * BuildCompleteEvent is posted; any changes after that event is handled will not be included in
+   * the build tool logs event.
+   */
+  public BuildToolLogCollection getBuildToolLogCollection() {
+    return buildToolLogCollection;
+  }
+
   /** For debugging. */
   @Override
   public String toString() {
@@ -253,6 +270,53 @@
         .add("actualTargets", actualTargets)
         .add("testTargets", testTargets)
         .add("successfulTargets", successfulTargets)
+        .add("buildToolLogCollection", buildToolLogCollection)
         .toString();
   }
+
+  /** Collection of data for the build tool logs event. */
+  public static final class BuildToolLogCollection {
+    private final List<Pair<String, ByteString>> directValues = new ArrayList<>();
+    private final List<Pair<String, String>> directUris = new ArrayList<>();
+    private final List<Pair<String, Path>> logFiles = new ArrayList<>();
+    private boolean frozen;
+
+    public BuildToolLogCollection freeze() {
+      frozen = true;
+      return this;
+    }
+
+    public BuildToolLogCollection addDirectValue(String name, byte[] data) {
+      Preconditions.checkState(!frozen);
+      this.directValues.add(Pair.of(name, ByteString.copyFrom(data)));
+      return this;
+    }
+
+    public BuildToolLogCollection addUri(String name, String uri) {
+      Preconditions.checkState(!frozen);
+      this.directUris.add(Pair.of(name, uri));
+      return this;
+    }
+
+    public BuildToolLogCollection addLocalFile(String name, Path path) {
+      Preconditions.checkState(!frozen);
+      this.logFiles.add(Pair.of(name, path));
+      return this;
+    }
+
+    public BuildToolLogs toEvent() {
+      Preconditions.checkState(frozen);
+      return new BuildToolLogs(directValues, directUris, logFiles);
+    }
+
+    /** For debugging. */
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("directValues", directValues)
+          .add("directUris", directUris)
+          .add("logFiles", logFiles)
+          .toString();
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index dd37a7f..844cbca 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -360,6 +360,7 @@
             new BuildCompleteEvent(
                 result,
                 ImmutableList.of(BuildEventId.buildToolLogs(), BuildEventId.buildMetrics())));
+    env.getEventBus().post(result.getBuildToolLogCollection().freeze().toEvent());
     if (ie != null) {
       if (exitCondition.equals(ExitCode.SUCCESS)) {
         result.setExitCondition(ExitCode.INTERRUPTED);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java b/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
index 9f237d4..5b10cf0 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
@@ -14,13 +14,11 @@
 package com.google.devtools.build.lib.runtime;
 
 import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
 import com.google.common.eventbus.AllowConcurrentEvents;
 import com.google.common.eventbus.EventBus;
 import com.google.common.eventbus.Subscribe;
 import com.google.devtools.build.lib.actions.ActionKeyContext;
 import com.google.devtools.build.lib.actions.ActionResultReceivedEvent;
-import com.google.devtools.build.lib.buildeventstream.BuildToolLogs;
 import com.google.devtools.build.lib.buildtool.BuildRequest;
 import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
 import com.google.devtools.build.lib.buildtool.buildevent.ExecutionStartingEvent;
@@ -32,8 +30,7 @@
 import com.google.devtools.build.lib.profiler.Profiler;
 import com.google.devtools.build.lib.profiler.ProfilerTask;
 import com.google.devtools.build.lib.profiler.SilentCloseable;
-import com.google.devtools.build.lib.util.Pair;
-import com.google.protobuf.ByteString;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.logging.Logger;
@@ -93,11 +90,13 @@
   public void buildComplete(BuildCompleteEvent event) {
     try {
       // We might want to make this conditional on a flag; it can sometimes be a bit of a nuisance.
-      List<Pair<String, ByteString>> statistics = new ArrayList<>();
       List<String> items = new ArrayList<>();
       items.add(String.format("Elapsed time: %.3fs", event.getResult().getElapsedSeconds()));
-      statistics.add(Pair.of("elapsed time", ByteString.copyFromUtf8(
-          String.format("%f", event.getResult().getElapsedSeconds()))));
+      event.getResult().getBuildToolLogCollection()
+          .addDirectValue(
+              "elapsed time",
+              String.format(
+                  "%f", event.getResult().getElapsedSeconds()).getBytes(StandardCharsets.UTF_8));
 
       if (criticalPathComputer != null) {
         try (SilentCloseable c =
@@ -105,8 +104,9 @@
           AggregatedCriticalPath criticalPath =
               criticalPathComputer.aggregate();
           items.add(criticalPath.toStringSummaryNoRemote());
-          statistics.add(
-              Pair.of("critical path", ByteString.copyFromUtf8(criticalPath.toString())));
+          event.getResult().getBuildToolLogCollection()
+              .addDirectValue(
+                  "critical path", criticalPath.toString().getBytes(StandardCharsets.UTF_8));
           logger.info(criticalPath.toString());
           logger.info(
               "Slowest actions:\n  "
@@ -129,9 +129,9 @@
 
       String spawnSummary = spawnStats.getSummary();
       reporter.handle(Event.info(spawnSummary));
-      statistics.add(Pair.of("process stats", ByteString.copyFromUtf8(spawnSummary)));
 
-      reporter.post(new BuildToolLogs(statistics, ImmutableList.of(), ImmutableList.of()));
+      event.getResult().getBuildToolLogCollection()
+          .addDirectValue("process stats", spawnSummary.getBytes(StandardCharsets.UTF_8));
     } finally {
       criticalPathComputer = null;
     }