| // 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.BaseCommand.LibraryInputOrigin; |
| import com.android.tools.r8.dex.Marker.Tool; |
| import com.android.tools.r8.errors.CompilationError; |
| 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.keepanno.annotations.KeepForApi; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.references.ClassReference; |
| import com.android.tools.r8.utils.AbortException; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.ExceptionDiagnostic; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.InternalOptions.DesugarState; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Set; |
| |
| /** |
| * Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler. |
| */ |
| @KeepForApi |
| public final class GlobalSyntheticsGeneratorCommand { |
| |
| private final GlobalSyntheticsConsumer globalsConsumer; |
| private final Reporter reporter; |
| private final int minMajorApiLevel; |
| |
| @SuppressWarnings("UnusedVariable") |
| private final int minMinorApiLevel; |
| |
| private final boolean classfileDesugaringOnly; |
| |
| private final boolean printHelp; |
| private final boolean printVersion; |
| |
| private final AndroidApp inputApp; |
| |
| private final DexItemFactory factory = new DexItemFactory(); |
| |
| private GlobalSyntheticsGeneratorCommand( |
| AndroidApp inputApp, |
| GlobalSyntheticsConsumer globalsConsumer, |
| Reporter reporter, |
| int minMajorApiLevel, |
| int minMinorApiLevel, |
| boolean classfileDesugaringOnly) { |
| this.inputApp = inputApp; |
| this.globalsConsumer = globalsConsumer; |
| this.minMajorApiLevel = minMajorApiLevel; |
| this.minMinorApiLevel = minMinorApiLevel; |
| this.classfileDesugaringOnly = classfileDesugaringOnly; |
| this.reporter = reporter; |
| this.printHelp = false; |
| this.printVersion = false; |
| } |
| |
| private GlobalSyntheticsGeneratorCommand(boolean printHelp, boolean printVersion) { |
| this.printHelp = printHelp; |
| this.printVersion = printVersion; |
| |
| this.inputApp = null; |
| this.globalsConsumer = null; |
| this.minMajorApiLevel = AndroidApiLevel.B.getLevel(); |
| this.minMinorApiLevel = 0; |
| this.classfileDesugaringOnly = false; |
| |
| reporter = new Reporter(); |
| } |
| |
| public AndroidApp getInputApp() { |
| return inputApp; |
| } |
| |
| public boolean isPrintHelp() { |
| return printHelp; |
| } |
| |
| public boolean isPrintVersion() { |
| return printVersion; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| InternalOptions getInternalOptions() { |
| InternalOptions internal = new InternalOptions(factory, reporter); |
| assert !internal.debug; |
| assert !internal.minimalMainDex; |
| internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minMajorApiLevel)); |
| assert internal.retainCompileTimeAnnotations; |
| internal.intermediate = true; |
| internal.programConsumer = |
| classfileDesugaringOnly ? new ThrowingCfConsumer() : new ThrowingDexConsumer(); |
| internal.setGlobalSyntheticsConsumer(globalsConsumer); |
| if (classfileDesugaringOnly) { |
| internal |
| .apiModelingOptions() |
| .disableApiCallerIdentification() |
| .disableOutlining() |
| .disableStubbingOfClasses(); |
| } |
| |
| // Assert and fixup defaults. |
| assert !internal.isShrinking(); |
| assert !internal.isMinifying(); |
| assert !internal.passthroughDexCode; |
| |
| internal.tool = Tool.GlobalSyntheticsGenerator; |
| internal.desugarState = DesugarState.ON; |
| internal.enableVarHandleDesugaring = true; |
| |
| internal.getArtProfileOptions().setEnableCompletenessCheckForTesting(false); |
| |
| return internal; |
| } |
| |
| private static class ThrowingCfConsumer implements ClassFileConsumer { |
| |
| @Override |
| public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) { |
| throw new Unreachable("Unexpected attempt to write a non-global artifact"); |
| } |
| |
| @Override |
| public void finished(DiagnosticsHandler handler) { |
| // Nothing to do. |
| } |
| } |
| |
| private static class ThrowingDexConsumer implements DexIndexedConsumer { |
| |
| @Override |
| public void accept( |
| int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) { |
| throw new Unreachable("Unexpected attempt to write a non-global artifact"); |
| } |
| |
| @Override |
| public void finished(DiagnosticsHandler handler) { |
| // Nothing to do. |
| } |
| } |
| |
| /** |
| * Builder for constructing a GlobalSyntheticsGeneratorCommand. |
| * |
| * <p>A builder is obtained by calling {@link GlobalSyntheticsGeneratorCommand#builder}. |
| */ |
| @KeepForApi |
| public static class Builder { |
| |
| private GlobalSyntheticsConsumer globalsConsumer = null; |
| private final Reporter reporter; |
| private int minMajorApiLevel = AndroidApiLevel.B.getLevel(); |
| private int minMinorApiLevel = 0; |
| private boolean classfileDesugaringOnly = false; |
| private boolean printHelp = false; |
| private boolean printVersion = false; |
| private final AndroidApp.Builder appBuilder = AndroidApp.builder(); |
| |
| private Builder() { |
| this(new DefaultR8DiagnosticsHandler()); |
| } |
| |
| private Builder(DiagnosticsHandler diagnosticsHandler) { |
| this.reporter = new Reporter(diagnosticsHandler); |
| } |
| |
| /** Set the min api level. */ |
| public Builder setMinApiLevel(int minMajorApiLevel) { |
| return setMinApiLevel(minMajorApiLevel, 0); |
| } |
| |
| /** Set the min api level. */ |
| public Builder setMinApiLevel(int minMajorApiLevel, int minMinorApiLevel) { |
| this.minMajorApiLevel = minMajorApiLevel; |
| this.minMinorApiLevel = minMinorApiLevel; |
| return this; |
| } |
| |
| public Builder setClassfileDesugaringOnly(boolean value) { |
| this.classfileDesugaringOnly = value; |
| return this; |
| } |
| |
| /** Set the value of the print-help flag. */ |
| public Builder setPrintHelp(boolean printHelp) { |
| this.printHelp = printHelp; |
| return this; |
| } |
| |
| /** Set the value of the print-version flag. */ |
| public Builder setPrintVersion(boolean printVersion) { |
| this.printVersion = printVersion; |
| return this; |
| } |
| |
| /** Add library file resources. */ |
| public Builder addLibraryFiles(Path... files) { |
| addLibraryFiles(Arrays.asList(files)); |
| return this; |
| } |
| |
| /** Add library file resources. */ |
| public Builder addLibraryFiles(Collection<Path> files) { |
| guard( |
| () -> { |
| for (Path path : files) { |
| try { |
| appBuilder.addLibraryFile(path); |
| } catch (CompilationError e) { |
| error(new LibraryInputOrigin(path), e); |
| } |
| } |
| }); |
| return this; |
| } |
| |
| /** Set a destination to write the resulting global synthetics output file. */ |
| public Builder setGlobalSyntheticsOutput(Path path) { |
| return setGlobalSyntheticsConsumer( |
| new GlobalSyntheticsConsumer() { |
| |
| private boolean written = false; |
| |
| @Override |
| public synchronized void accept( |
| ByteDataView data, ClassReference context, DiagnosticsHandler handler) { |
| if (written) { |
| throw new Unreachable("Unexpected attempt to repeatedly write global synthetics"); |
| } |
| written = true; |
| try { |
| Files.write(path, data.copyByteData()); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| }); |
| } |
| |
| /** Set a consumer for obtaining the resulting global synthetics output. */ |
| public Builder setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalsConsumer) { |
| this.globalsConsumer = globalsConsumer; |
| return this; |
| } |
| |
| public GlobalSyntheticsGeneratorCommand build() { |
| validate(); |
| if (isPrintHelpOrPrintVersion()) { |
| return new GlobalSyntheticsGeneratorCommand(printHelp, printVersion); |
| } |
| return new GlobalSyntheticsGeneratorCommand( |
| appBuilder.build(), |
| globalsConsumer, |
| reporter, |
| minMajorApiLevel, |
| minMinorApiLevel, |
| classfileDesugaringOnly); |
| } |
| |
| private boolean isPrintHelpOrPrintVersion() { |
| return printHelp || printVersion; |
| } |
| |
| private void validate() { |
| if (isPrintHelpOrPrintVersion()) { |
| return; |
| } |
| if (globalsConsumer == null) { |
| reporter.error("GlobalSyntheticsGenerator does not support compiling without output"); |
| } |
| } |
| |
| // Helper to guard and handle exceptions. |
| private void guard(Runnable action) { |
| try { |
| action.run(); |
| } catch (CompilationError e) { |
| reporter.error(e.toStringDiagnostic()); |
| } catch (AbortException e) { |
| // Error was reported and exception will be thrown by build. |
| } |
| } |
| |
| /** Signal an error. */ |
| public void error(Diagnostic diagnostic) { |
| reporter.error(diagnostic); |
| } |
| |
| // Helper to signify an error. |
| public void error(Origin origin, Throwable throwable) { |
| reporter.error(new ExceptionDiagnostic(throwable, origin)); |
| } |
| } |
| } |