| // Copyright (c) 2020, 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.tracereferences; |
| |
| import com.android.tools.r8.BaseCompilerCommandParser; |
| import com.android.tools.r8.DiagnosticsHandler; |
| import com.android.tools.r8.JdkClassFileProvider; |
| import com.android.tools.r8.StringConsumer.FileConsumer; |
| import com.android.tools.r8.StringConsumer.WriterConsumer; |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.ExceptionDiagnostic; |
| import com.android.tools.r8.utils.FlagFile; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Arrays; |
| import java.util.Set; |
| |
| class TraceReferencesCommandParser { |
| |
| private static final Set<String> OPTIONS_WITH_PARAMETER = |
| ImmutableSet.of("--lib", "--target", "--source", "--output"); |
| |
| static final String USAGE_MESSAGE = |
| String.join( |
| "\n", |
| Iterables.concat( |
| Arrays.asList( |
| "Usage: tracereferences <command> [<options>] [@<argfile>]", |
| " Where <command> is one of:", |
| " --check # Run emitting only diagnostics messages.", |
| " --keep-rules [<keep-rules-options>]", |
| " # Traced references will be output in the keep-rules", |
| " # format.", |
| " and each <argfile> is a file containing additional options (one per line)", |
| " and options are:", |
| " --lib <file|jdk-home> # Add <file|jdk-home> runtime library.", |
| " --source <file> # Add <file> as a source for tracing references.", |
| " [--target <file>] # Add <file> as a target for tracing references. When", |
| " # target is not specified all references from source", |
| " # outside of library are treated as a missing", |
| " # references.", |
| " --output <file> # Output result in <outfile>. If not passed the", |
| " # result will go to standard out."), |
| BaseCompilerCommandParser.MAP_DIAGNOSTICS_USAGE_MESSAGE, |
| Arrays.asList( |
| " --version # Print the version of tracereferences.", |
| " --help # Print this message.", |
| " and <keep-rule-options> are:", |
| " --allowobfuscation # Output keep rules with the allowobfuscation", |
| " # modifier (defaults to rules without the" |
| + " modifier)"))); |
| /** |
| * Parse the tracereferences 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 tracereferences command builder with state set up according to parsed command line. |
| */ |
| static TraceReferencesCommand.Builder parse(String[] args, Origin origin) { |
| return new TraceReferencesCommandParser().parse(args, origin, TraceReferencesCommand.builder()); |
| } |
| |
| /** |
| * Parse the tracereferences 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 tracereferences command builder with state set up according to parsed command line. |
| */ |
| static TraceReferencesCommand.Builder parse( |
| String[] args, Origin origin, DiagnosticsHandler handler) { |
| return new TraceReferencesCommandParser() |
| .parse(args, origin, TraceReferencesCommand.builder(handler)); |
| } |
| |
| private enum Command { |
| CHECK, |
| KEEP_RULES; |
| } |
| |
| private void checkCommandNotSet( |
| Command command, TraceReferencesCommand.Builder builder, Origin origin) { |
| if (command != null) { |
| builder.error(new StringDiagnostic("Multiple commands specified", origin)); |
| } |
| } |
| |
| private TraceReferencesCommand.Builder parse( |
| String[] args, Origin origin, TraceReferencesCommand.Builder builder) { |
| String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error); |
| Path output = null; |
| Command command = null; |
| boolean allowObfuscation = false; |
| if (expandedArgs.length == 0) { |
| builder.error(new StringDiagnostic("Missing command")); |
| return builder; |
| } |
| // Parse options. |
| for (int i = 0; i < expandedArgs.length; i++) { |
| String arg = expandedArgs[i].trim(); |
| String nextArg = null; |
| if (OPTIONS_WITH_PARAMETER.contains(arg)) { |
| if (++i < expandedArgs.length) { |
| nextArg = expandedArgs[i]; |
| } else { |
| builder.error( |
| new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin)); |
| break; |
| } |
| } |
| if (arg.length() == 0) { |
| continue; |
| } else if (arg.equals("--help")) { |
| builder.setPrintHelp(true); |
| return builder; |
| } else if (arg.equals("--version")) { |
| builder.setPrintVersion(true); |
| return builder; |
| } else if (arg.equals("--check")) { |
| checkCommandNotSet(command, builder, origin); |
| command = Command.CHECK; |
| } else if (arg.equals("--keep-rules")) { |
| checkCommandNotSet(command, builder, origin); |
| command = Command.KEEP_RULES; |
| } else if (arg.equals("--allowobfuscation")) { |
| allowObfuscation = true; |
| } else if (arg.equals("--lib")) { |
| addLibraryArgument(builder, origin, nextArg); |
| } else if (arg.equals("--target")) { |
| builder.addTargetFiles(Paths.get(nextArg)); |
| } else if (arg.equals("--source")) { |
| builder.addSourceFiles(Paths.get(nextArg)); |
| } else if (arg.equals("--output")) { |
| if (output != null) { |
| builder.error(new StringDiagnostic("Option '--output' passed multiple times.", origin)); |
| } else { |
| output = Paths.get(nextArg); |
| } |
| } else if (arg.startsWith("@")) { |
| builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin)); |
| } else { |
| int argsConsumed = |
| BaseCompilerCommandParser.tryParseMapDiagnostics( |
| builder::error, builder.getReporter(), arg, expandedArgs, i, origin); |
| if (argsConsumed >= 0) { |
| i += argsConsumed; |
| continue; |
| } |
| builder.error(new StringDiagnostic("Unsupported option '" + arg + "'", origin)); |
| } |
| } |
| |
| if (command == null) { |
| builder.error( |
| new StringDiagnostic( |
| "Missing command, specify one of 'check' or '--keep-rules'", origin)); |
| return builder; |
| } |
| |
| if (command == Command.CHECK && output != null) { |
| builder.error( |
| new StringDiagnostic("Using '--output' requires command '--keep-rules'", origin)); |
| return builder; |
| } |
| |
| if (command != Command.KEEP_RULES && allowObfuscation) { |
| builder.error( |
| new StringDiagnostic( |
| "Using '--allowobfuscation' requires command '--keep-rules'", origin)); |
| return builder; |
| } |
| |
| switch (command) { |
| case CHECK: |
| builder.setConsumer( |
| new TraceReferencesCheckConsumer(TraceReferencesConsumer.emptyConsumer())); |
| break; |
| case KEEP_RULES: |
| builder.setConsumer( |
| new TraceReferencesCheckConsumer( |
| TraceReferencesKeepRules.builder() |
| .setAllowObfuscation(allowObfuscation) |
| .setOutputConsumer( |
| output != null |
| ? new FileConsumer(output) |
| : new WriterConsumer(null, new PrintWriter(System.out))) |
| .build())); |
| break; |
| default: |
| throw new Unreachable(); |
| } |
| return builder; |
| } |
| |
| /** |
| * This method must match the lookup in {@link |
| * com.android.tools.r8.JdkClassFileProvider#fromJdkHome}. |
| */ |
| private static boolean isJdkHome(Path home) { |
| Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar"); |
| if (Files.exists(jrtFsJar)) { |
| return true; |
| } |
| // JDK has rt.jar in jre/lib/rt.jar. |
| Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar"); |
| if (Files.exists(rtJar)) { |
| return true; |
| } |
| // JRE has rt.jar in lib/rt.jar. |
| rtJar = home.resolve("lib").resolve("rt.jar"); |
| if (Files.exists(rtJar)) { |
| return true; |
| } |
| return false; |
| } |
| |
| static void addLibraryArgument( |
| TraceReferencesCommand.Builder builder, Origin origin, String arg) { |
| Path path = Paths.get(arg); |
| if (isJdkHome(path)) { |
| try { |
| builder.addLibraryResourceProvider(JdkClassFileProvider.fromJdkHome(path)); |
| } catch (IOException e) { |
| builder.error(new ExceptionDiagnostic(e, origin)); |
| } |
| } else { |
| builder.addLibraryFiles(path); |
| } |
| } |
| } |