[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() {