| // Copyright (c) 2025, 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.graph.DexItemFactory; |
| import com.android.tools.r8.keepanno.annotations.KeepForApi; |
| import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions; |
| import com.android.tools.r8.origin.CommandLineOrigin; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.CliParserUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.android.tools.r8.utils.internal.CliParser; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| |
| /** Experimental API to extract keep rules from keep annotations. */ |
| @KeepForApi |
| public class ExtractKeepAnnoRulesCommand extends BaseCommand { |
| |
| private final StringConsumer rulesConsumer; |
| private final KeepRuleExtractorOptions extractorOptions; |
| private final DexItemFactory factory; |
| private final Reporter reporter; |
| |
| @KeepForApi |
| public static class Builder extends BaseCommand.Builder<ExtractKeepAnnoRulesCommand, Builder> { |
| |
| private StringConsumer rulesConsumer = null; |
| private KeepRuleExtractorOptions extractorOptions = KeepRuleExtractorOptions.getR8Options(); |
| |
| private Builder() {} |
| |
| private Builder(DiagnosticsHandler diagnosticsHandler) { |
| super(diagnosticsHandler); |
| } |
| |
| @Override |
| Builder self() { |
| return this; |
| } |
| |
| /** TBD - still experimental */ |
| public Builder setRulesOutputPath(Path rulesOutputPath) { |
| rulesConsumer = new StringConsumer.FileConsumer(rulesOutputPath); |
| return self(); |
| } |
| |
| /** TBD - still experimental */ |
| public Builder setRulesConsumer(StringConsumer rulesConsumer) { |
| this.rulesConsumer = rulesConsumer; |
| return self(); |
| } |
| |
| /** TBD - still experimental and still package private */ |
| Builder setExtractorOptions(KeepRuleExtractorOptions extractorOptions) { |
| this.extractorOptions = extractorOptions; |
| return self(); |
| } |
| |
| @Override |
| protected ExtractKeepAnnoRulesCommand makeCommand() { |
| // If printing versions ignore everything else. |
| if (isPrintHelp() || isPrintVersion()) { |
| return new ExtractKeepAnnoRulesCommand(isPrintHelp(), isPrintVersion()); |
| } |
| |
| return new ExtractKeepAnnoRulesCommand( |
| new DexItemFactory(), |
| getAppBuilder().build(), |
| rulesConsumer, |
| extractorOptions, |
| getReporter()); |
| } |
| } |
| |
| static String usageMessage() { |
| return CliParserUtils.getUsageMessage(createParser()); |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| public static Builder builder(DiagnosticsHandler diagnosticsHandler) { |
| return new Builder(diagnosticsHandler); |
| } |
| |
| public static Builder parse(String[] args) { |
| Builder builder = builder(); |
| createParser() |
| .parse( |
| args, |
| builder, |
| err -> builder.error(new StringDiagnostic(err, CommandLineOrigin.INSTANCE))); |
| return builder; |
| } |
| |
| public StringConsumer getRulesConsumer() { |
| return rulesConsumer; |
| } |
| |
| public KeepRuleExtractorOptions getExtractorOptions() { |
| return extractorOptions; |
| } |
| |
| Reporter getReporter() { |
| return reporter; |
| } |
| |
| private static CliParser<Builder> createParser() { |
| var header = "Usage: EXPERIMENTAL tool to extract keep rules from keep annotations"; |
| var parser = new CliParser<Builder>(header); |
| // TODO(b/425263915): Need to support more, including extracting for different versions and |
| // keepedge AST proto. |
| return parser |
| .option1( |
| "--rules-output", |
| "<file>", |
| "Output the extracted keep rules.", |
| (b, arg) -> b.setRulesOutputPath(Paths.get(arg))) |
| .option1( |
| "--rules-target", |
| "<r8|pg>", |
| "Optimizer rules are for (default 'r8').", |
| (b, arg) -> { |
| if (arg.equals("r8")) { |
| b.setExtractorOptions(KeepRuleExtractorOptions.getR8Options()); |
| } else if (arg.equals("pg")) { |
| b.setExtractorOptions(KeepRuleExtractorOptions.getPgOptions()); |
| } else { |
| b.getReporter() |
| .fatalError( |
| new StringDiagnostic( |
| "Unsupported argument '" + arg + "' to --rules-target", |
| CommandLineOrigin.INSTANCE)); |
| } |
| }) |
| .option0("--version", "Print the version.", b -> b.setPrintVersion(true)) |
| .option0("--help", "Print this message.", b -> b.setPrintHelp(true), "-h") |
| .positional((b, arg) -> b.addProgramFiles(Paths.get(arg))); |
| } |
| |
| private ExtractKeepAnnoRulesCommand( |
| DexItemFactory factory, |
| AndroidApp inputApp, |
| StringConsumer rulesConsumer, |
| KeepRuleExtractorOptions extractorOptions, |
| Reporter reporter) { |
| super(inputApp); |
| this.factory = factory; |
| this.rulesConsumer = rulesConsumer; |
| this.extractorOptions = extractorOptions; |
| this.reporter = reporter; |
| } |
| |
| private ExtractKeepAnnoRulesCommand(boolean printHelp, boolean printVersion) { |
| super(printHelp, printVersion); |
| this.factory = new DexItemFactory(); |
| this.rulesConsumer = null; |
| this.extractorOptions = null; |
| this.reporter = new Reporter(); |
| } |
| |
| @Override |
| InternalOptions getInternalOptions() { |
| InternalOptions internal = new InternalOptions(factory, reporter); |
| internal.programConsumer = DexIndexedConsumer.emptyConsumer(); |
| return internal; |
| } |
| } |