| // Copyright (c) 2026, 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.libanalyzer; |
| |
| import com.android.tools.r8.ByteArrayConsumer; |
| import com.android.tools.r8.DiagnosticsHandler; |
| import com.android.tools.r8.keepanno.annotations.KeepForApi; |
| import com.android.tools.r8.libanalyzer.proto.LibraryAnalyzerResult; |
| import com.android.tools.r8.libanalyzer.utils.LibraryAnalyzerOptions; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.AarArchiveResourceProvider; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.ArchiveResourceProvider; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.UncheckedIOException; |
| import java.nio.file.Path; |
| import java.util.function.Consumer; |
| |
| @KeepForApi |
| public final class LibraryAnalyzerCommand { |
| |
| private final AndroidApp app; |
| private final Path blastRadiusOutputPath; |
| private final AndroidApiLevel minApiLevel; |
| private final Consumer<LibraryAnalyzerResult> outputConsumer; |
| private final Reporter reporter; |
| private final int threadCount; |
| private final boolean printHelp; |
| private final boolean printVersion; |
| |
| private LibraryAnalyzerCommand( |
| AndroidApp app, |
| Path blastRadiusOutputPath, |
| AndroidApiLevel minApiLevel, |
| Consumer<LibraryAnalyzerResult> outputConsumer, |
| Reporter reporter, |
| int threadCount) { |
| this.app = app; |
| this.blastRadiusOutputPath = blastRadiusOutputPath; |
| this.minApiLevel = minApiLevel; |
| this.outputConsumer = outputConsumer; |
| this.reporter = reporter; |
| this.threadCount = threadCount; |
| this.printHelp = false; |
| this.printVersion = false; |
| } |
| |
| private LibraryAnalyzerCommand(boolean printHelp, boolean printVersion) { |
| this.app = null; |
| this.blastRadiusOutputPath = null; |
| this.minApiLevel = null; |
| this.outputConsumer = null; |
| this.reporter = new Reporter(); |
| this.threadCount = ThreadUtils.NOT_SPECIFIED; |
| this.printHelp = printHelp; |
| this.printVersion = printVersion; |
| } |
| |
| AndroidApp getApp() { |
| return app; |
| } |
| |
| LibraryAnalyzerOptions getInternalOptions() { |
| return new LibraryAnalyzerOptions( |
| blastRadiusOutputPath, minApiLevel, outputConsumer, reporter, threadCount); |
| } |
| |
| boolean isPrintHelp() { |
| return printHelp; |
| } |
| |
| boolean isPrintVersion() { |
| return printVersion; |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| public static Builder builder(DiagnosticsHandler handler) { |
| return new Builder(handler); |
| } |
| |
| @KeepForApi |
| public static class Builder { |
| |
| private final AndroidApp.Builder appBuilder; |
| private Path blastRadiusOutputPath; |
| private AndroidApiLevel minApiLevel = AndroidApiLevel.getDefault(); |
| private Consumer<LibraryAnalyzerResult> outputConsumer; |
| private final Reporter reporter; |
| private int threadCount = ThreadUtils.NOT_SPECIFIED; |
| |
| private boolean printHelp = false; |
| private boolean printVersion = false; |
| |
| private Builder() { |
| this(new Reporter()); |
| } |
| |
| private Builder(DiagnosticsHandler handler) { |
| Reporter reporter = handler instanceof Reporter ? (Reporter) handler : new Reporter(handler); |
| this.appBuilder = AndroidApp.builder(reporter); |
| this.reporter = reporter; |
| } |
| |
| AndroidApp.Builder getAppBuilder() { |
| return appBuilder; |
| } |
| |
| public Builder addAarPath(Path aarPath) { |
| appBuilder.addProgramResourceProvider(AarArchiveResourceProvider.fromArchive(aarPath)); |
| return this; |
| } |
| |
| public Builder addAarPath(Path aarPath, Origin origin) { |
| appBuilder.addProgramResourceProvider( |
| AarArchiveResourceProvider.fromArchive(aarPath, origin)); |
| return this; |
| } |
| |
| public Builder addJarPath(Path jarPath) { |
| appBuilder.addProgramResourceProvider(ArchiveResourceProvider.fromArchive(jarPath, true)); |
| return this; |
| } |
| |
| public Builder addJarPath(Path jarPath, Origin origin) { |
| appBuilder.addProgramResourceProvider( |
| ArchiveResourceProvider.fromArchive(jarPath, true, origin)); |
| return this; |
| } |
| |
| public Builder addLibraryPath(Path libraryPath) { |
| appBuilder.addLibraryFile(libraryPath); |
| return this; |
| } |
| |
| public Builder setBlastRadiusOutputPath(Path blastRadiusOutputPath) { |
| this.blastRadiusOutputPath = blastRadiusOutputPath; |
| return this; |
| } |
| |
| public <OS extends OutputStream> Builder setOutputConsumer( |
| ByteArrayConsumer<OS> outputConsumer) { |
| return setInternalOutputConsumer( |
| result -> { |
| OS outputStream; |
| try (OS autoCloseable = outputStream = outputConsumer.getOutputStream()) { |
| result.writeTo(outputStream); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| outputConsumer.finished(outputStream, reporter); |
| }); |
| } |
| |
| /** |
| * Must not be added to the public API, as that would require making the protos public API. This |
| * API is used to implement the public consumer API as well as for testing. |
| */ |
| Builder setInternalOutputConsumer(Consumer<LibraryAnalyzerResult> outputConsumer) { |
| if (this.outputConsumer == null) { |
| this.outputConsumer = outputConsumer; |
| } else { |
| this.outputConsumer = this.outputConsumer.andThen(outputConsumer); |
| } |
| return this; |
| } |
| |
| public Builder setMinApiLevel(int minMajorApiLevel, int minMinorApiLevel) { |
| return setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minMajorApiLevel, minMinorApiLevel)); |
| } |
| |
| Builder setMinApiLevel(AndroidApiLevel minApiLevel) { |
| this.minApiLevel = minApiLevel; |
| return this; |
| } |
| |
| public Builder setPrintHelp(boolean printHelp) { |
| this.printHelp = printHelp; |
| return this; |
| } |
| |
| public Builder setPrintVersion(boolean printVersion) { |
| this.printVersion = printVersion; |
| return this; |
| } |
| |
| public Builder setThreadCount(int threadCount) { |
| this.threadCount = threadCount; |
| return this; |
| } |
| |
| public LibraryAnalyzerCommand build() { |
| if (printHelp || printVersion) { |
| return new LibraryAnalyzerCommand(printHelp, printVersion); |
| } |
| validate(); |
| return new LibraryAnalyzerCommand( |
| appBuilder.build(), |
| blastRadiusOutputPath, |
| minApiLevel, |
| outputConsumer, |
| reporter, |
| threadCount); |
| } |
| |
| private void validate() { |
| if (appBuilder.getProgramResourceProviders().isEmpty()) { |
| reporter.error("LibraryAnalyzer requires an input Android Archive (AAR)."); |
| } |
| reporter.failIfPendingErrors(); |
| } |
| } |
| } |