Add --output flag to LibraryAnalyzer and write protobuf

If no output path is given, print output to stdout.

Bug: b/479726064
Change-Id: I149971821a2666ec95b36960db34d8c6e7e0d38f
diff --git a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzer.java b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzer.java
index 59c5301..f31ddfa 100644
--- a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzer.java
+++ b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzer.java
@@ -11,6 +11,9 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.libanalyzer.proto.D8CompileResult;
+import com.android.tools.r8.libanalyzer.proto.LibraryAnalysisResult;
+import com.android.tools.r8.libanalyzer.proto.R8CompileResult;
 import com.android.tools.r8.libanalyzer.utils.DexIndexedSizeConsumer;
 import com.android.tools.r8.libanalyzer.utils.LibraryAnalyzerOptions;
 import com.android.tools.r8.origin.CommandLineOrigin;
@@ -19,6 +22,10 @@
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 
@@ -82,17 +89,12 @@
   }
 
   private void run(ExecutorService executorService) {
-    D8RunResult d8RunResult = runD8(executorService);
-    R8RunResult r8RunResult = runR8(executorService);
-    // TODO(b/479726064): Write to protobuf.
-    System.out.println(
-        "D8="
-            + (d8RunResult != null ? d8RunResult.size : "N/A")
-            + ", R8="
-            + (r8RunResult != null ? r8RunResult.size : "N/A"));
+    InternalD8CompileResult d8CompileResult = runD8(executorService);
+    InternalR8CompileResult r8CompileResult = runR8(executorService);
+    writeAnalysisResult(d8CompileResult, r8CompileResult);
   }
 
-  private D8RunResult runD8(ExecutorService executorService) {
+  private InternalD8CompileResult runD8(ExecutorService executorService) {
     DexIndexedSizeConsumer sizeConsumer = new DexIndexedSizeConsumer();
     D8Command.Builder commandBuilder =
         D8Command.builder(options.reporter).setProgramConsumer(sizeConsumer);
@@ -104,10 +106,10 @@
       options.reporter.clearAbort();
       return null;
     }
-    return new D8RunResult(sizeConsumer.size());
+    return new InternalD8CompileResult(sizeConsumer.size());
   }
 
-  private R8RunResult runR8(ExecutorService executorService) {
+  private InternalR8CompileResult runR8(ExecutorService executorService) {
     DexIndexedSizeConsumer sizeConsumer = new DexIndexedSizeConsumer();
     R8Command.Builder commandBuilder =
         R8Command.builder(options.reporter)
@@ -121,7 +123,30 @@
       options.reporter.clearAbort();
       return null;
     }
-    return new R8RunResult(sizeConsumer.size());
+    return new InternalR8CompileResult(sizeConsumer.size());
+  }
+
+  private void writeAnalysisResult(
+      InternalD8CompileResult d8CompileResult, InternalR8CompileResult r8CompileResult) {
+    LibraryAnalysisResult.Builder resultBuilder = LibraryAnalysisResult.newBuilder();
+    if (d8CompileResult != null) {
+      resultBuilder.setD8CompileResult(
+          D8CompileResult.newBuilder().setSizeBytes(d8CompileResult.size).build());
+    }
+    if (r8CompileResult != null) {
+      resultBuilder.setR8CompileResult(
+          R8CompileResult.newBuilder().setSizeBytes(r8CompileResult.size).build());
+    }
+    LibraryAnalysisResult result = resultBuilder.build();
+    if (options.outputPath != null) {
+      try (OutputStream output = Files.newOutputStream(options.outputPath)) {
+        result.writeTo(output);
+      } catch (IOException e) {
+        throw new UncheckedIOException(e);
+      }
+    } else {
+      System.out.println(result);
+    }
   }
 
   private void configure(BaseCompilerCommand.Builder<?, ?> commandBuilder) {
@@ -133,25 +158,25 @@
     app.getLibraryResourceProviders().forEach(commandBuilder::addLibraryResourceProvider);
   }
 
-  private abstract static class RunResult {
+  private abstract static class CompileResult {
 
     final int size;
 
-    RunResult(int size) {
+    CompileResult(int size) {
       this.size = size;
     }
   }
 
-  private static class D8RunResult extends RunResult {
+  private static class InternalD8CompileResult extends CompileResult {
 
-    D8RunResult(int size) {
+    InternalD8CompileResult(int size) {
       super(size);
     }
   }
 
-  private static class R8RunResult extends RunResult {
+  private static class InternalR8CompileResult extends CompileResult {
 
-    R8RunResult(int size) {
+    InternalR8CompileResult(int size) {
       super(size);
     }
   }
diff --git a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommand.java b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommand.java
index 61aacfb..0c1fa13 100644
--- a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommand.java
+++ b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommand.java
@@ -21,15 +21,21 @@
 
   private final AndroidApp app;
   private final AndroidApiLevel minApiLevel;
+  private final Path outputPath;
   private final Reporter reporter;
   private final int threadCount;
   private final boolean printHelp;
   private final boolean printVersion;
 
   private LibraryAnalyzerCommand(
-      AndroidApp app, AndroidApiLevel minApiLevel, Reporter reporter, int threadCount) {
+      AndroidApp app,
+      AndroidApiLevel minApiLevel,
+      Path outputPath,
+      Reporter reporter,
+      int threadCount) {
     this.app = app;
     this.minApiLevel = minApiLevel;
+    this.outputPath = outputPath;
     this.reporter = reporter;
     this.threadCount = threadCount;
     this.printHelp = false;
@@ -39,6 +45,7 @@
   private LibraryAnalyzerCommand(boolean printHelp, boolean printVersion) {
     this.app = null;
     this.minApiLevel = null;
+    this.outputPath = null;
     this.reporter = new Reporter();
     this.threadCount = ThreadUtils.NOT_SPECIFIED;
     this.printHelp = printHelp;
@@ -50,7 +57,7 @@
   }
 
   LibraryAnalyzerOptions getInternalOptions() {
-    return new LibraryAnalyzerOptions(minApiLevel, reporter, threadCount);
+    return new LibraryAnalyzerOptions(minApiLevel, outputPath, reporter, threadCount);
   }
 
   boolean isPrintHelp() {
@@ -73,6 +80,7 @@
 
     private final AndroidApp.Builder appBuilder;
     private AndroidApiLevel minApiLevel = AndroidApiLevel.getDefault();
+    private Path outputPath;
     private final Reporter reporter;
     private int threadCount;
 
@@ -98,6 +106,11 @@
       return this;
     }
 
+    public Builder setOutputPath(Path outputPath) {
+      this.outputPath = outputPath;
+      return this;
+    }
+
     public Builder setMinApiLevel(int minMajorApiLevel, int minMinorApiLevel) {
       return setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minMajorApiLevel, minMinorApiLevel));
     }
@@ -127,13 +140,15 @@
         return new LibraryAnalyzerCommand(printHelp, printVersion);
       }
       validate();
-      return new LibraryAnalyzerCommand(appBuilder.build(), minApiLevel, reporter, threadCount);
+      return new LibraryAnalyzerCommand(
+          appBuilder.build(), minApiLevel, outputPath, reporter, threadCount);
     }
 
     private void validate() {
       if (appBuilder.getProgramResourceProviders().isEmpty()) {
         reporter.error("LibraryAnalyzer requires an input Android Archive (AAR).");
       }
+      reporter.failIfPendingErrors();
     }
   }
 }
diff --git a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommandParser.java b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommandParser.java
index 58acf50..2c91e96 100644
--- a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommandParser.java
+++ b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/LibraryAnalyzerCommandParser.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.BaseCompilerCommandParser.LIB_FLAG;
 import static com.android.tools.r8.BaseCompilerCommandParser.MIN_API_FLAG;
+import static com.android.tools.r8.BaseCompilerCommandParser.OUTPUT_FLAG;
 import static com.android.tools.r8.BaseCompilerCommandParser.THREAD_COUNT_FLAG;
 import static com.android.tools.r8.BaseCompilerCommandParser.parsePositiveIntArgument;
 
@@ -26,7 +27,7 @@
   private static final String AAR_FLAG = "--aar";
 
   private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
-      ImmutableSet.of(AAR_FLAG, LIB_FLAG, MIN_API_FLAG, THREAD_COUNT_FLAG);
+      ImmutableSet.of(AAR_FLAG, LIB_FLAG, MIN_API_FLAG, OUTPUT_FLAG, THREAD_COUNT_FLAG);
 
   private static final String USAGE_MESSAGE =
       StringUtils.lines(
@@ -35,6 +36,7 @@
           "  --aar <path>            # Path to Android Archive (AAR) that should be analyzed.",
           "  --lib <path>            # Path to file or JDK home to use as a library resource.",
           "  --min-api <major.minor> # Minimum API level to use for analysis.",
+          "  --output <path>         # Path where to write the analysis result (protobuf).",
           "  --thread-count <int>    # Number of threads to use.",
           "  --help                  # Print this message.",
           "  --version               # Print the version.");
@@ -72,6 +74,8 @@
             builder.getAppBuilder(), nextArg, origin, reporter);
       } else if (arg.equals(MIN_API_FLAG)) {
         builder.setMinApiLevel(AndroidApiLevel.parseAndroidApiLevel(nextArg));
+      } else if (arg.equals(OUTPUT_FLAG)) {
+        builder.setOutputPath(Paths.get(nextArg));
       } else if (arg.equals(THREAD_COUNT_FLAG)) {
         parsePositiveIntArgument(
             reporter::error, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
diff --git a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/utils/LibraryAnalyzerOptions.java b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/utils/LibraryAnalyzerOptions.java
index 180ddd5..ee053d9 100644
--- a/src/libanalyzer/java/com/android/tools/r8/libanalyzer/utils/LibraryAnalyzerOptions.java
+++ b/src/libanalyzer/java/com/android/tools/r8/libanalyzer/utils/LibraryAnalyzerOptions.java
@@ -6,17 +6,21 @@
 import com.android.tools.r8.threading.ThreadingModule;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Reporter;
+import java.nio.file.Path;
 
 public class LibraryAnalyzerOptions {
 
   public final AndroidApiLevel minApiLevel;
+  public final Path outputPath;
   public final Reporter reporter;
   public final int threadCount;
 
   private ThreadingModule lazyThreadingModule = null;
 
-  public LibraryAnalyzerOptions(AndroidApiLevel minApiLevel, Reporter reporter, int threadCount) {
+  public LibraryAnalyzerOptions(
+      AndroidApiLevel minApiLevel, Path outputPath, Reporter reporter, int threadCount) {
     this.minApiLevel = minApiLevel;
+    this.outputPath = outputPath;
     this.reporter = reporter;
     this.threadCount = threadCount;
   }
diff --git a/src/libanalyzer/proto/libraryanalysisresult.proto b/src/libanalyzer/proto/libraryanalysisresult.proto
index ba6c1c0..8783bc7 100644
--- a/src/libanalyzer/proto/libraryanalysisresult.proto
+++ b/src/libanalyzer/proto/libraryanalysisresult.proto
@@ -15,4 +15,15 @@
 option java_package = "com.android.tools.r8.libanalyzer.proto";
 
 // Analysis result produced by the library analyzer.
-message LibraryAnalysisResult {}
+message LibraryAnalysisResult {
+  D8CompileResult d8_compile_result = 1;
+  R8CompileResult r8_compile_result = 2;
+}
+
+message D8CompileResult {
+  uint32 size_bytes = 1;
+}
+
+message R8CompileResult {
+  uint32 size_bytes = 1;
+}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index f8567c6..fabf62a 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -20,6 +20,7 @@
   protected static final String BUILD_METADATA_OUTPUT_FLAG = "--build-metadata-output";
   public static final String LIB_FLAG = "--lib";
   public static final String MIN_API_FLAG = "--min-api";
+  public static final String OUTPUT_FLAG = "--output";
   protected static final String STARTUP_PROFILE_FLAG = "--startup-profile";
   public static final String THREAD_COUNT_FLAG = "--thread-count";
   protected static final String MAP_DIAGNOSTICS = "--map-diagnostics";