blob: c61c643001b9687bffca52899690619c1bbe2ae8 [file]
// 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();
}
}
}