Merge "Introduce the concept of an OutputSink to allow different processing of generated output based on usage scenario."
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 8dbc785..e3d399d 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -14,48 +16,61 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 
 public class ExtractMarker {
-  private static class Command extends BaseCommand {
+  private static class Command {
 
-    public static class Builder
-        extends BaseCommand.Builder<ExtractMarker.Command, ExtractMarker.Command.Builder> {
-
+    public static class Builder {
+      private boolean printHelp = false;
       private boolean verbose;
+      private boolean summary;
+      private List<Path> programFiles = new ArrayList<>();
 
-      @Override
-      ExtractMarker.Command.Builder self() {
+      public Builder setPrintHelp(boolean printHelp) {
+        this.printHelp = printHelp;
         return this;
       }
 
+      public boolean isPrintHelp() {
+        return printHelp;
+      }
+
       public Builder setVerbose(boolean verbose) {
         this.verbose = verbose;
-        return self();
+        return this;
       }
 
-      @Override
+      public Builder setSummary(boolean summary) {
+        this.summary = summary;
+        return this;
+      }
+
+      public Builder addProgramFile(Path programFile) {
+        programFiles.add(programFile);
+        return this;
+      }
+
       public ExtractMarker.Command build() throws CompilationException, IOException {
         // If printing versions ignore everything else.
         if (isPrintHelp()) {
           return new ExtractMarker.Command(isPrintHelp());
         }
-        validate();
-        return new ExtractMarker.Command(getAppBuilder().build(), verbose, programFiles);
+        return new ExtractMarker.Command(verbose, summary, programFiles);
       }
     }
 
     static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
         "Usage: extractmarker [options] <input-files>",
         " where <input-files> are dex or vdex files",
-        "  --version               # Print the version of r8.",
         "  --verbose               # More verbose output.",
+        "  --summary               # Print summary at the end.",
         "  --help                  # Print this message."));
 
     public static ExtractMarker.Command.Builder builder() {
-      // Allow vdex files for the extract marker tool.
-      return new ExtractMarker.Command.Builder().setVdexAllowed();
+      return new Builder();
     }
 
     public static ExtractMarker.Command.Builder parse(String[] args)
@@ -73,32 +88,42 @@
           continue;
         } else if (arg.equals("--verbose")) {
           builder.setVerbose(true);
+        } else if (arg.equals("--summary")) {
+          builder.setSummary(true);
         } else if (arg.equals("--help")) {
           builder.setPrintHelp(true);
         } else {
           if (arg.startsWith("--")) {
             throw new CompilationException("Unknown option: " + arg);
           }
-          builder.addProgramFiles(Paths.get(arg));
+          builder.addProgramFile(Paths.get(arg));
         }
       }
     }
 
-    private final List<Path> programFiles;
+    private final boolean printHelp;
     private final boolean verbose;
+    private final boolean summary;
+    private final List<Path> programFiles;
 
-    private Command(AndroidApp inputApp, boolean verbose, List<Path> programFiles) {
-      super(inputApp);
-      this.programFiles = programFiles;
+    private Command(boolean verbose, boolean summary, List<Path> programFiles) {
+      this.printHelp = false;
       this.verbose = verbose;
+      this.summary = summary;
+      this.programFiles = programFiles;
     }
 
     private Command(boolean printHelp) {
-      super(printHelp, false);
+      this.printHelp = printHelp;
       this.verbose = false;
+      this.summary = false;
       programFiles = ImmutableList.of();
     }
 
+    public boolean isPrintHelp() {
+      return printHelp;
+    }
+
     public List<Path> getProgramFiles() {
       return programFiles;
     }
@@ -107,9 +132,8 @@
       return verbose;
     }
 
-    @Override
-    InternalOptions getInternalOptions() {
-      return new InternalOptions();
+    public boolean getSummary() {
+      return summary;
     }
   }
 
@@ -122,33 +146,48 @@
       return;
     }
 
-    try {
-      AndroidApp app = command.getInputApp();
-      InternalOptions options = new InternalOptions();
-      // Dex code is not needed for getting the marker. VDex files typically contains quickened byte
-      // codes which cannot be read, and we want to get the marker from vdex files as well.
-      options.skipReadingDexCode = true;
-      DexApplication dexApp = new ApplicationReader(app, options, new Timing("ExtractMarker"))
-          .read();
-      Marker readMarker = dexApp.dexItemFactory.extractMarker();
-      if (command.getVerbose()) {
-        for (int i = 0; i < command.getProgramFiles().size(); i++) {
-          if (i != 0) {
-            System.out.print(", ");
-          }
-          System.out.print(command.getProgramFiles().get(i));
+    InternalOptions options = new InternalOptions();
+    // Dex code is not needed for getting the marker. VDex files typically contains quickened byte
+    // codes which cannot be read, and we want to get the marker from vdex files as well.
+    int d8Count = 0;
+    int r8Count = 0;
+    int otherCount = 0;
+    for (Path programFile : command.getProgramFiles()) {
+      try {
+        options.skipReadingDexCode = true;
+        options.minApiLevel = AndroidApiLevel.P.getLevel();
+        AndroidApp.Builder appBuilder = AndroidApp.builder();
+        appBuilder.setVdexAllowed();
+        appBuilder.addProgramFiles(FilteredClassPath.unfiltered(programFile));
+        DexApplication dexApp =
+            new ApplicationReader(appBuilder.build(), options, new Timing("ExtractMarker"))
+                .read();
+        Marker readMarker = dexApp.dexItemFactory.extractMarker();
+        if (command.getVerbose()) {
+          System.out.print(programFile);
+          System.out.print(": ");
         }
-        System.out.print(": ");
+        if (readMarker == null) {
+          System.out.println("D8/R8 marker not found.");
+          otherCount++;
+        } else {
+          System.out.println(readMarker.toString());
+          if (readMarker.isD8()) {
+            d8Count++;
+          } else {
+            r8Count++;
+          }
+        }
+      } catch (CompilationError e) {
+        System.out.println(
+            "Failed to read dex/vdex file `" + programFile +"`: '" + e.getMessage() + "'");
       }
-      if (readMarker == null) {
-        System.out.println("D8/R8 marker not found.");
-        System.exit(1);
-      } else {
-        System.out.println(readMarker.toString());
-      }
-    } catch (CompilationError e) {
-      System.out.println("Failed to read dex file: '" + e.getMessage() + "'");
-      System.exit(0);
+    }
+    if (command.getSummary()) {
+      System.out.println("D8: " + d8Count);
+      System.out.println("R8: " + r8Count);
+      System.out.println("Other: " + otherCount);
+      System.out.println("Total: " + (d8Count + r8Count + otherCount));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 4762fd4..9e76f31 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -3,13 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import java.util.Map;
+import java.util.TreeMap;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 
-import java.util.Map;
-import java.util.TreeMap;
-
 
 /**
  * Abstraction for hidden dex marker intended for the main dex file.
@@ -40,6 +39,18 @@
     }
   }
 
+  public Tool getTool() {
+    return tool;
+  }
+
+  public boolean isD8() {
+    return tool == Tool.D8;
+  }
+
+  public boolean isR8() {
+    return tool == Tool.R8;
+  }
+
   public Marker put(String key, int value) {
     // value is converted to Long ensuring equals works with the parsed json string.
     return internalPut(key, new Long(value));
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 0cddbb6..a292423 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -25,6 +25,7 @@
   public static final String JAR_EXTENSION = ".jar";
   public static final String ZIP_EXTENSION = ".zip";
   public static final String JAVA_EXTENSION = ".java";
+  public static final String MODULE_INFO_CLASS = "module-info.class";
 
   public static boolean isDexFile(Path path) {
     String name = path.getFileName().toString().toLowerCase();
@@ -38,6 +39,10 @@
 
   public static boolean isClassFile(Path path) {
     String name = path.getFileName().toString().toLowerCase();
+    // Android does not support Java 9 module, thus skip module-info.
+    if (name.equals(MODULE_INFO_CLASS)) {
+      return false;
+    }
     return name.endsWith(CLASS_EXTENSION);
   }