[GlobalSynthetics] Scaffolding around a global synthetics compiler

Bug: b/280016114
Change-Id: Ifc7a97d360113620492b08dcfc9443158b2e3cb4
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 03ae2fd..48f5c36 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -280,8 +280,8 @@
   }
 
   /**
-   * This method must match the lookup in
-   * {@link com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
+   * This method must match the lookup in {@link
+   * com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
    */
   private static boolean isJdkHome(Path home) {
     Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
@@ -295,10 +295,7 @@
     }
     // JRE has rt.jar in lib/rt.jar.
     rtJar = home.resolve("lib").resolve("rt.jar");
-    if (Files.exists(rtJar)) {
-      return true;
-    }
-    return false;
+    return Files.exists(rtJar);
   }
 
   static void addLibraryArgument(BaseCommand.Builder builder, Origin origin, String arg) {
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
new file mode 100644
index 0000000..2efea81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SelfRetraceTest;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * The GlobalSyntheticsGenerator, a tool for generating a dex file for all possible global
+ * synthetics.
+ */
+public class GlobalSyntheticsGenerator {
+
+  /**
+   * Main API entry for the global synthetics generator.
+   *
+   * @param command GlobalSyntheticsGenerator command.
+   */
+  public static void run(GlobalSyntheticsGeneratorCommand command)
+      throws CompilationFailedException {
+    runForTesting(command.getInputApp(), command.getInternalOptions());
+  }
+
+  /**
+   * Main API entry for the global synthetics generator.
+   *
+   * @param command GlobalSyntheticsGenerator command.
+   * @param executor executor service from which to get threads for multi-threaded processing.
+   */
+  public static void run(GlobalSyntheticsGeneratorCommand command, ExecutorService executor)
+      throws CompilationFailedException {
+    run(command.getInputApp(), command.getInternalOptions(), executor);
+  }
+
+  static void runForTesting(AndroidApp app, InternalOptions options)
+      throws CompilationFailedException {
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    run(app, options, executorService);
+  }
+
+  private static void run(AndroidApp app, InternalOptions options, ExecutorService executorService)
+      throws CompilationFailedException {
+    try {
+      ExceptionUtils.withD8CompilationHandler(
+          options.reporter,
+          () -> {
+            throw new RuntimeException("Implement GlobalSyntheticsGenerator");
+          });
+    } finally {
+      executorService.shutdown();
+    }
+  }
+
+  private static void run(String[] args) throws CompilationFailedException {
+    GlobalSyntheticsGeneratorCommand command =
+        GlobalSyntheticsGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build();
+    if (command.isPrintHelp()) {
+      SelfRetraceTest.test();
+      System.out.println(GlobalSyntheticsGeneratorCommandParser.getUsageMessage());
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("GlobalSynthetics " + Version.getVersionString());
+      return;
+    }
+    run(command);
+  }
+
+  /**
+   * Command-line entry to GlobalSynthetics.
+   *
+   * <p>See {@link GlobalSyntheticsGeneratorCommandParser#getUsageMessage()} or run {@code
+   * globalsynthetics --help} for usage information.
+   */
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      throw new RuntimeException(
+          StringUtils.joinLines(
+              "Invalid invocation.", GlobalSyntheticsGeneratorCommandParser.getUsageMessage()));
+    }
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
new file mode 100644
index 0000000..2187b67
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
@@ -0,0 +1,260 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.nio.file.Path;
+import java.util.Collection;
+
+/**
+ * Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler.
+ */
+public final class GlobalSyntheticsGeneratorCommand extends BaseCommand {
+
+  private final ProgramConsumer programConsumer;
+  private final StringConsumer classNameConsumer;
+  private final Reporter reporter;
+  private final int minApiLevel;
+
+  private final DexItemFactory factory = new DexItemFactory();
+
+  private GlobalSyntheticsGeneratorCommand(
+      AndroidApp androidApp,
+      ProgramConsumer programConsumer,
+      StringConsumer ClassNameConsumer,
+      Reporter reporter,
+      int minApiLevel) {
+    super(androidApp);
+    this.programConsumer = programConsumer;
+    this.classNameConsumer = ClassNameConsumer;
+    this.minApiLevel = minApiLevel;
+    this.reporter = reporter;
+  }
+
+  private GlobalSyntheticsGeneratorCommand(boolean printHelp, boolean printVersion) {
+    super(printHelp, printVersion);
+    this.programConsumer = null;
+    this.classNameConsumer = null;
+    this.minApiLevel = AndroidApiLevel.B.getLevel();
+
+    reporter = new Reporter();
+  }
+
+  /**
+   * Parse the GlobalSyntheticsGenerator command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @return GlobalSyntheticsGenerator command builder with state according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin) {
+    return GlobalSyntheticsGeneratorCommandParser.parse(args, origin);
+  }
+
+  /**
+   * Parse the GlobalSyntheticsGenerator command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return GlobalSyntheticsGenerator command builder with state according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return GlobalSyntheticsGeneratorCommandParser.parse(args, origin, handler);
+  }
+
+  protected static class DefaultR8DiagnosticsHandler implements DiagnosticsHandler {
+
+    @Override
+    public void error(Diagnostic error) {
+      if (error instanceof DexFileOverflowDiagnostic) {
+        DexFileOverflowDiagnostic overflowDiagnostic = (DexFileOverflowDiagnostic) error;
+        DiagnosticsHandler.super.error(
+            new StringDiagnostic(
+                overflowDiagnostic.getDiagnosticMessage()
+                    + ". Library too large. GlobalSyntheticsGenerator can only produce a single"
+                    + " .dex file"));
+        return;
+      }
+      DiagnosticsHandler.super.error(error);
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(diagnosticsHandler);
+  }
+
+  @Override
+  InternalOptions getInternalOptions() {
+    InternalOptions internal = new InternalOptions(factory, reporter);
+    assert !internal.debug;
+    assert !internal.minimalMainDex;
+    internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minApiLevel));
+    assert !internal.intermediate;
+    assert internal.retainCompileTimeAnnotations;
+    internal.programConsumer = programConsumer;
+
+    // Assert and fixup defaults.
+    assert !internal.isShrinking();
+    assert !internal.isMinifying();
+    assert !internal.passthroughDexCode;
+
+    return internal;
+  }
+
+  /**
+   * Builder for constructing a GlobalSyntheticsGeneratorCommand.
+   *
+   * <p>A builder is obtained by calling {@link GlobalSyntheticsGeneratorCommand#builder}.
+   */
+  public static class Builder
+      extends BaseCommand.Builder<GlobalSyntheticsGeneratorCommand, Builder> {
+
+    private ProgramConsumer programConsumer = null;
+    private StringConsumer globalSyntheticClassesListConsumer = null;
+    private Reporter reporter;
+    private int minApiLevel = AndroidApiLevel.B.getLevel();
+
+    private Builder() {
+      this(new DefaultR8DiagnosticsHandler());
+    }
+
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
+      this.reporter = new Reporter(diagnosticsHandler);
+    }
+
+    @Override
+    Builder self() {
+      return this;
+    }
+
+    public Builder setReporter(Reporter reporter) {
+      this.reporter = reporter;
+      return self();
+    }
+
+    public Builder setMinApiLevel(int minApiLevel) {
+      this.minApiLevel = minApiLevel;
+      return self();
+    }
+
+    @Override
+    void validate() {
+      if (isPrintHelp() || isPrintVersion()) {
+        return;
+      }
+      if (!(programConsumer instanceof DexIndexedConsumer)) {
+        reporter.error("G8 does not support compiling to dex per class or class files");
+      }
+    }
+
+    @Override
+    public GlobalSyntheticsGeneratorCommand makeCommand() {
+      if (isPrintHelp() || isPrintVersion()) {
+        return new GlobalSyntheticsGeneratorCommand(isPrintHelp(), isPrintVersion());
+      }
+      validate();
+      return new GlobalSyntheticsGeneratorCommand(
+          getAppBuilder().build(),
+          programConsumer,
+          globalSyntheticClassesListConsumer,
+          reporter,
+          minApiLevel);
+    }
+
+    public Builder setGlobalSyntheticClassesListOutput(Path path) {
+      return setGlobalSyntheticClassesListConsumer(new StringConsumer.FileConsumer(path));
+    }
+
+    public Builder setGlobalSyntheticClassesListConsumer(
+        StringConsumer globalSyntheticClassesListOutput) {
+      this.globalSyntheticClassesListConsumer = globalSyntheticClassesListOutput;
+      return self();
+    }
+
+    public Builder setProgramConsumerOutput(Path path) {
+      return setProgramConsumer(
+          FileUtils.isArchive(path)
+              ? new DexIndexedConsumer.ArchiveConsumer(path, false)
+              : new DexIndexedConsumer.DirectoryConsumer(path, false));
+    }
+
+    public Builder setProgramConsumer(ProgramConsumer programConsumer) {
+      this.programConsumer = programConsumer;
+      return self();
+    }
+
+    @Override
+    public Builder addProgramFiles(Collection<Path> files) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addProgramResourceProvider(ProgramResourceProvider programProvider) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addClasspathFiles(Path... files) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addClasspathFiles(Collection<Path> files) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addClassProgramData(byte[] data, Origin origin) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    Builder addDexProgramData(byte[] data, Origin origin) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addMainDexListFiles(Path... files) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addMainDexListFiles(Collection<Path> files) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addMainDexClasses(String... classes) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+
+    @Override
+    public Builder addMainDexClasses(Collection<String> classes) {
+      throw new Unreachable("Should not be used for global synthetics generation");
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
new file mode 100644
index 0000000..36e5bbd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import static com.android.tools.r8.BaseCompilerCommandParser.parsePositiveIntArgument;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Set;
+
+public class GlobalSyntheticsGeneratorCommandParser {
+
+  private static final String LOWER_CASE_NAME = "globalsynthetics";
+  private static final String MIN_API_FLAG = "--min-api";
+
+  private static final String USAGE_MESSAGE =
+      StringUtils.lines("Usage: " + LOWER_CASE_NAME + " [options] " + "where options are:");
+
+  public static List<ParseFlagInfo> getFlags() {
+    return ImmutableList.<ParseFlagInfo>builder()
+        .add(ParseFlagInfoImpl.getMinApi())
+        .add(ParseFlagInfoImpl.getLib())
+        .add(ParseFlagInfoImpl.flag1("--output", "<dex-file>", "Output result in <dex-file>."))
+        .add(
+            ParseFlagInfoImpl.flag1(
+                "--classes-list-output", "<file>", "Output list of generated classes in <file>"))
+        .add(ParseFlagInfoImpl.getVersion(LOWER_CASE_NAME))
+        .add(ParseFlagInfoImpl.getHelp())
+        .build();
+  }
+
+  static String getUsageMessage() {
+    StringBuilder builder = new StringBuilder();
+    StringUtils.appendLines(builder, USAGE_MESSAGE);
+    new ParseFlagPrinter().addFlags(getFlags()).appendLinesToBuilder(builder);
+    return builder.toString();
+  }
+
+  private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
+      ImmutableSet.of("--output", "--lib", MIN_API_FLAG, "---classes-list-output");
+
+  public static GlobalSyntheticsGeneratorCommand.Builder parse(String[] args, Origin origin) {
+    return new GlobalSyntheticsGeneratorCommandParser()
+        .parse(args, origin, GlobalSyntheticsGeneratorCommand.builder());
+  }
+
+  public static GlobalSyntheticsGeneratorCommand.Builder parse(
+      String[] args, Origin origin, DiagnosticsHandler handler) {
+    return new GlobalSyntheticsGeneratorCommandParser()
+        .parse(args, origin, GlobalSyntheticsGeneratorCommand.builder(handler));
+  }
+
+  private GlobalSyntheticsGeneratorCommand.Builder parse(
+      String[] args, Origin origin, GlobalSyntheticsGeneratorCommand.Builder builder) {
+    Path outputPath = null;
+    boolean hasDefinedApiLevel = false;
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
+    for (int i = 0; i < expandedArgs.length; i++) {
+      String arg = expandedArgs[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
+        if (++i < expandedArgs.length) {
+          nextArg = expandedArgs[i];
+        } else {
+          builder.error(
+              new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
+          break;
+        }
+      }
+      if (arg.length() == 0) {
+        continue;
+      } else if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--output")) {
+        if (outputPath != null) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot output both to '" + outputPath + "' and '" + nextArg + "'", origin));
+          continue;
+        }
+        outputPath = Paths.get(nextArg);
+      } else if (arg.equals(MIN_API_FLAG)) {
+        if (hasDefinedApiLevel) {
+          builder.error(
+              new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", origin));
+        } else {
+          parsePositiveIntArgument(
+              builder::error, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel);
+          hasDefinedApiLevel = true;
+        }
+      } else if (arg.equals("--lib")) {
+        builder.addLibraryFiles(Paths.get(nextArg));
+      } else if (arg.equals("--classes-list-output")) {
+        builder.setGlobalSyntheticClassesListOutput(Paths.get(nextArg));
+      } else if (arg.startsWith("--")) {
+        builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
+      }
+    }
+    if (outputPath == null) {
+      outputPath = Paths.get(".");
+    }
+    return builder.setProgramConsumerOutput(outputPath);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Partition.java b/src/main/java/com/android/tools/r8/retrace/Partition.java
index c7b942f..1582b5e 100644
--- a/src/main/java/com/android/tools/r8/retrace/Partition.java
+++ b/src/main/java/com/android/tools/r8/retrace/Partition.java
@@ -29,7 +29,7 @@
 
   private static final String USAGE_MESSAGE =
       StringUtils.lines(
-          "Usage: partition [options] <proguard-map>"
+          "Usage: partition [options] <proguard-map> "
               + "where <proguard-map> is a generated mapping file and options are:");
 
   public static List<ParseFlagInfo> getFlags() {