|  | // 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.graph.DexItemFactory; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | 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.FileUtils; | 
|  | 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.nio.file.Path; | 
|  | import java.util.Arrays; | 
|  | import java.util.Collection; | 
|  |  | 
|  | /** | 
|  | * Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler. | 
|  | */ | 
|  | public final class GlobalSyntheticsGeneratorCommand { | 
|  |  | 
|  | private final ProgramConsumer programConsumer; | 
|  | private final Reporter reporter; | 
|  | private final int minApiLevel; | 
|  |  | 
|  | private final boolean printHelp; | 
|  | private final boolean printVersion; | 
|  |  | 
|  | private final AndroidApp inputApp; | 
|  |  | 
|  | private final DexItemFactory factory = new DexItemFactory(); | 
|  |  | 
|  | private GlobalSyntheticsGeneratorCommand( | 
|  | AndroidApp inputApp, ProgramConsumer programConsumer, Reporter reporter, int minApiLevel) { | 
|  | this.inputApp = inputApp; | 
|  | this.programConsumer = programConsumer; | 
|  | this.minApiLevel = minApiLevel; | 
|  | 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.programConsumer = null; | 
|  | this.minApiLevel = AndroidApiLevel.B.getLevel(); | 
|  |  | 
|  | 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(minApiLevel)); | 
|  | assert !internal.intermediate; | 
|  | assert internal.retainCompileTimeAnnotations; | 
|  | internal.programConsumer = programConsumer; | 
|  |  | 
|  | // 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; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Builder for constructing a GlobalSyntheticsGeneratorCommand. | 
|  | * | 
|  | * <p>A builder is obtained by calling {@link GlobalSyntheticsGeneratorCommand#builder}. | 
|  | */ | 
|  | public static class Builder { | 
|  |  | 
|  | private ProgramConsumer programConsumer = null; | 
|  | private final Reporter reporter; | 
|  | private int minApiLevel = AndroidApiLevel.B.getLevel(); | 
|  | 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 minApiLevel) { | 
|  | this.minApiLevel = minApiLevel; | 
|  | 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 an output path to consume the resulting program. */ | 
|  | public Builder setProgramConsumerOutput(Path path) { | 
|  | return setProgramConsumer( | 
|  | FileUtils.isArchive(path) | 
|  | ? new DexIndexedConsumer.ArchiveConsumer(path, false) | 
|  | : new DexIndexedConsumer.DirectoryConsumer(path, false)); | 
|  | } | 
|  |  | 
|  | /** Set a consumer for obtaining the resulting program. */ | 
|  | public Builder setProgramConsumer(ProgramConsumer programConsumer) { | 
|  | this.programConsumer = programConsumer; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public GlobalSyntheticsGeneratorCommand build() { | 
|  | validate(); | 
|  | if (isPrintHelpOrPrintVersion()) { | 
|  | return new GlobalSyntheticsGeneratorCommand(printHelp, printVersion); | 
|  | } | 
|  | return new GlobalSyntheticsGeneratorCommand( | 
|  | appBuilder.build(), programConsumer, reporter, minApiLevel); | 
|  | } | 
|  |  | 
|  | private boolean isPrintHelpOrPrintVersion() { | 
|  | return printHelp || printVersion; | 
|  | } | 
|  |  | 
|  | private void validate() { | 
|  | if (isPrintHelpOrPrintVersion()) { | 
|  | return; | 
|  | } | 
|  | if (!(programConsumer instanceof DexIndexedConsumer)) { | 
|  | reporter.error( | 
|  | "GlobalSyntheticsGenerator does not support compiling to dex per class or class files"); | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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)); | 
|  | } | 
|  | } | 
|  | } |