| // 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); | 
 |     } | 
 |   } | 
 | } |