| // Copyright (c) 2018, 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.AssertionsConfiguration.AssertionTransformation; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.ExceptionDiagnostic; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Arrays; |
| import java.util.function.Consumer; |
| |
| public class BaseCompilerCommandParser< |
| C extends BaseCompilerCommand, B extends BaseCompilerCommand.Builder<C, B>> { |
| |
| protected static final String MIN_API_FLAG = "--min-api"; |
| protected static final String THREAD_COUNT_FLAG = "--thread-count"; |
| protected static final String MAP_DIAGNOSTICS = "--map-diagnostics"; |
| protected static final String DUMP_INPUT_TO_FILE = "--dumpinputtofile"; |
| protected static final String DUMP_INPUT_TO_DIRECTORY = "--dumpinputtodirectory"; |
| |
| static final Iterable<String> ASSERTIONS_USAGE_MESSAGE = |
| Arrays.asList( |
| " --force-enable-assertions[:[<class name>|<package name>...]]", |
| " --force-ea[:[<class name>|<package name>...]]", |
| " # Forcefully enable javac generated assertion code.", |
| " --force-disable-assertions[:[<class name>|<package name>...]]", |
| " --force-da[:[<class name>|<package name>...]]", |
| " # Forcefully disable javac generated assertion code. This", |
| " # is the default handling of javac assertion code when", |
| " # generating DEX file format.", |
| " --force-passthrough-assertions[:[<class name>|<package name>...]]", |
| " --force-pa[:[<class name>|<package name>...]]", |
| " # Don't change javac generated assertion code. This", |
| " # is the default handling of javac assertion code when", |
| " # generating class file format."); |
| |
| static final Iterable<String> THREAD_COUNT_USAGE_MESSAGE = |
| Arrays.asList( |
| " " + THREAD_COUNT_FLAG + " <number of threads>", |
| " # Number of threads to use for compilation. If not specified", |
| " # the number will be based on heuristics taking the number", |
| " # of cores into account."); |
| |
| public static final Iterable<String> MAP_DIAGNOSTICS_USAGE_MESSAGE = |
| Arrays.asList( |
| " " + MAP_DIAGNOSTICS + "[:<type>] <from-level> <to-level>", |
| " # Map diagnostics of <type> (default any) reported as", |
| " # <from-level> to <to-level> where <from-level> and", |
| " # <to-level> are one of 'info', 'warning', or 'error' and the", |
| " # optional <type> is either the simple or fully qualified", |
| " # Java type name of a diagnostic. If <type> is unspecified,", |
| " # all diagnostics at <from-level> will be mapped.", |
| " # Note that fatal compiler errors cannot be mapped."); |
| |
| public static void parsePositiveIntArgument( |
| Consumer<Diagnostic> errorConsumer, |
| String flag, |
| String argument, |
| Origin origin, |
| Consumer<Integer> setter) { |
| int value; |
| try { |
| value = Integer.parseInt(argument); |
| } catch (NumberFormatException e) { |
| errorConsumer.accept( |
| new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin)); |
| return; |
| } |
| if (value < 1) { |
| errorConsumer.accept( |
| new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin)); |
| return; |
| } |
| setter.accept(value); |
| } |
| |
| private static String PACKAGE_ASSERTION_POSTFIX = "..."; |
| |
| private void addAssertionTransformation( |
| B builder, AssertionTransformation transformation, String scope) { |
| if (scope == null) { |
| builder.addAssertionsConfiguration( |
| b -> b.setTransformation(transformation).setScopeAll().build()); |
| } else { |
| assert scope.length() > 0; |
| if (scope.endsWith(PACKAGE_ASSERTION_POSTFIX)) { |
| builder.addAssertionsConfiguration( |
| b -> |
| b.setTransformation(transformation) |
| .setScopePackage( |
| scope.substring(0, scope.length() - PACKAGE_ASSERTION_POSTFIX.length())) |
| .build()); |
| } else { |
| builder.addAssertionsConfiguration( |
| b -> b.setTransformation(transformation).setScopeClass(scope).build()); |
| } |
| } |
| } |
| |
| boolean tryParseAssertionArgument(B builder, String arg, Origin origin) { |
| String FORCE_ENABLE_ASSERTIONS = "--force-enable-assertions"; |
| String FORCE_EA = "--force-ea"; |
| String FORCE_DISABLE_ASSERTIONS = "--force-disable-assertions"; |
| String FORCE_DA = "--force-da"; |
| String FORCE_PASSTHROUGH_ASSERTIONS = "--force-passthrough-assertions"; |
| String FORCE_PA = "--force-pa"; |
| |
| AssertionTransformation transformation = null; |
| String remaining = null; |
| if (arg.startsWith(FORCE_ENABLE_ASSERTIONS)) { |
| transformation = AssertionTransformation.ENABLE; |
| remaining = arg.substring(FORCE_ENABLE_ASSERTIONS.length()); |
| } else if (arg.startsWith(FORCE_EA)) { |
| transformation = AssertionTransformation.ENABLE; |
| remaining = arg.substring(FORCE_EA.length()); |
| } else if (arg.startsWith(FORCE_DISABLE_ASSERTIONS)) { |
| transformation = AssertionTransformation.DISABLE; |
| remaining = arg.substring(FORCE_DISABLE_ASSERTIONS.length()); |
| } else if (arg.startsWith(FORCE_DA)) { |
| transformation = AssertionTransformation.DISABLE; |
| remaining = arg.substring(FORCE_DA.length()); |
| } else if (arg.startsWith(FORCE_PASSTHROUGH_ASSERTIONS)) { |
| transformation = AssertionTransformation.PASSTHROUGH; |
| remaining = arg.substring(FORCE_PASSTHROUGH_ASSERTIONS.length()); |
| } else if (arg.startsWith(FORCE_PA)) { |
| transformation = AssertionTransformation.PASSTHROUGH; |
| remaining = arg.substring(FORCE_PA.length()); |
| } |
| if (transformation != null) { |
| if (remaining.length() == 0) { |
| addAssertionTransformation(builder, transformation, null); |
| return true; |
| } else { |
| if (remaining.length() == 1 || remaining.charAt(0) != ':') { |
| return false; |
| } |
| String classOrPackageScope = remaining.substring(1); |
| if (classOrPackageScope.contains(";") |
| || classOrPackageScope.contains("[") |
| || classOrPackageScope.contains("/")) { |
| builder.error( |
| new StringDiagnostic("Illegal assertion scope: " + classOrPackageScope, origin)); |
| } |
| addAssertionTransformation(builder, transformation, remaining.substring(1)); |
| return true; |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| int tryParseMapDiagnostics(B builder, String arg, String[] args, int argsIndex, Origin origin) { |
| return tryParseMapDiagnostics( |
| builder::error, builder.getReporter(), arg, args, argsIndex, origin); |
| } |
| |
| private static DiagnosticsLevel tryParseLevel( |
| Consumer<Diagnostic> errorHandler, String arg, Origin origin) { |
| if (arg.equals("error")) { |
| return DiagnosticsLevel.ERROR; |
| } |
| if (arg.equals("warning")) { |
| return DiagnosticsLevel.WARNING; |
| } |
| if (arg.equals("info")) { |
| return DiagnosticsLevel.INFO; |
| } |
| errorHandler.accept( |
| new StringDiagnostic( |
| "Invalid diagnostics level '" |
| + arg |
| + "'. Valid levels are 'error', 'warning' and 'info'.", |
| origin)); |
| |
| return null; |
| } |
| |
| public static int tryParseMapDiagnostics( |
| Consumer<Diagnostic> errorHandler, |
| Reporter reporter, |
| String arg, |
| String[] args, |
| int argsIndex, |
| Origin origin) { |
| if (!arg.startsWith(MAP_DIAGNOSTICS)) { |
| return -1; |
| } |
| if (args.length <= argsIndex + 2) { |
| errorHandler.accept(new StringDiagnostic("Missing argument(s) for " + arg + ".", origin)); |
| return args.length - argsIndex; |
| } |
| String remaining = arg.substring(MAP_DIAGNOSTICS.length()); |
| String diagnosticsClassName = ""; |
| if (remaining.length() > 0) { |
| if (remaining.length() == 1 || remaining.charAt(0) != ':') { |
| errorHandler.accept( |
| new StringDiagnostic("Invalid diagnostics type specification " + arg + ".", origin)); |
| return 0; |
| } |
| diagnosticsClassName = remaining.substring(1); |
| } |
| DiagnosticsLevel from = tryParseLevel(errorHandler, args[argsIndex + 1], origin); |
| DiagnosticsLevel to = tryParseLevel(errorHandler, args[argsIndex + 2], origin); |
| if (from != null && to != null) { |
| reporter.addDiagnosticsLevelMapping(from, diagnosticsClassName, to); |
| } |
| return 2; |
| } |
| |
| int tryParseDump(B builder, String arg, String[] args, int argsIndex, Origin origin) { |
| if (!arg.equals(DUMP_INPUT_TO_FILE) && !arg.equals(DUMP_INPUT_TO_DIRECTORY)) { |
| return -1; |
| } |
| if (args.length <= argsIndex + 1) { |
| builder.error(new StringDiagnostic("Missing argument(s) for " + arg + ".", origin)); |
| return args.length - argsIndex; |
| } |
| if (arg.equals(DUMP_INPUT_TO_FILE)) { |
| builder.dumpInputToFile(Paths.get(args[argsIndex + 1])); |
| } else { |
| assert arg.equals(DUMP_INPUT_TO_DIRECTORY); |
| builder.dumpInputToDirectory(Paths.get(args[argsIndex + 1])); |
| } |
| return 1; |
| } |
| |
| /** |
| * 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(BaseCommand.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); |
| } |
| } |
| } |