Merge "Add some gradle benchmarks"
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index d28f93f..8573e0f 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8;
 
-import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.DexIndexedConsumer.DirectoryConsumer;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.graph.AppInfo;
@@ -19,6 +19,8 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
@@ -31,7 +33,7 @@
   public static void run(
       D8Command command,
       FeatureClassMapping featureClassMapping,
-      String outputArchive,
+      String output,
       String proguardMap)
       throws IOException, CompilationException, ExecutionException {
     InternalOptions options = command.getInternalOptions();
@@ -65,8 +67,12 @@
           AppInfo appInfo = new AppInfo(featureApp);
           featureApp = D8.optimize(featureApp, appInfo, options, timing, executor);
           // We create a specific consumer for each split.
-          DexIndexedConsumer consumer =
-              new ArchiveConsumer(Paths.get(outputArchive + "." + entry.getKey() + ".zip"));
+          Path outputDir = Paths.get(output).resolve(entry.getKey());
+          if (!Files.exists(outputDir)) {
+            Files.createDirectory(outputDir);
+          }
+          DexIndexedConsumer consumer = new DirectoryConsumer(outputDir);
+
           try {
             new ApplicationWriter(
                     featureApp,
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index e3753a7..5e8d54d 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
@@ -23,23 +24,55 @@
 
 public class DexSplitter {
 
-  private static final String DEFAULT_OUTPUT_ARCHIVE_FILENAME = "split";
+  private static final String DEFAULT_OUTPUT_DIR = "output";
 
   private static final boolean PRINT_ARGS = false;
 
+  public static class FeatureJar {
+    private String jar;
+    private String outputName;
+
+    public FeatureJar(String jar, String outputName) {
+      this.jar = jar;
+      this.outputName = outputName;
+    }
+
+    public FeatureJar(String jar) {
+      this(jar, featureNameFromJar(jar));
+    }
+
+    public String getJar() {
+      return jar;
+    }
+
+    public String getOutputName() {
+      return outputName;
+    }
+
+    private static String featureNameFromJar(String jar) {
+      Path jarPath = Paths.get(jar);
+      String featureName = jarPath.getFileName().toString();
+      if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
+        featureName = featureName.substring(0, featureName.length() - 4);
+      }
+      return featureName;
+    }
+
+  }
+
   public static class Options {
     private List<String> inputArchives = new ArrayList<>();
-    private List<String> featureJars = new ArrayList<>();
-    private String splitBaseName = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
+    private List<FeatureJar> featureJars = new ArrayList<>();
+    private String output = DEFAULT_OUTPUT_DIR;
     private String featureSplitMapping;
     private String proguardMap;
 
-    public String getSplitBaseName() {
-      return splitBaseName;
+    public String getOutput() {
+      return output;
     }
 
-    public void setSplitBaseName(String splitBaseName) {
-      this.splitBaseName = splitBaseName;
+    public void setOutput(String output) {
+      this.output = output;
     }
 
     public String getFeatureSplitMapping() {
@@ -62,19 +95,45 @@
       inputArchives.add(inputArchive);
     }
 
-    public void addFeatureJar(String featureJar) {
+    protected void addFeatureJar(FeatureJar featureJar) {
       featureJars.add(featureJar);
     }
 
+    public void addFeatureJar(String jar) {
+      featureJars.add(new FeatureJar(jar));
+    }
+
+    public void addFeatureJar(String jar, String outputName) {
+      featureJars.add(new FeatureJar(jar, outputName));
+    }
+
     public ImmutableList<String> getInputArchives() {
       return ImmutableList.copyOf(inputArchives);
     }
 
-    public ImmutableList<String> getFeatureJars() {
+    public ImmutableList<FeatureJar> getFeatureJars() {
       return ImmutableList.copyOf(featureJars);
     }
   }
 
+  /**
+   * Parse a feature jar argument and return the corresponding FeatureJar representation.
+   * Default to use the name of the jar file if the argument contains no ':', if the argument
+   * contains ':', then use the value after the ':' as the name.
+   * @param argument
+   * @return
+   */
+  private static FeatureJar parseFeatureJarArgument(String argument) {
+    if (argument.contains(":")) {
+      String[] parts = argument.split(":");
+      if (parts.length > 2) {
+        throw new RuntimeException("--feature-jar argument contains more than one :");
+      }
+      return new FeatureJar(parts[0], parts[1]);
+    }
+    return new FeatureJar(argument);
+  }
+
   private static Options parseArguments(String[] args) throws IOException {
     Options options = new Options();
     ParseContext context = new ParseContext(args);
@@ -86,12 +145,13 @@
       }
       List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
       if (featureJars != null) {
-        featureJars.stream().forEach(options::addFeatureJar);
+        featureJars.stream().forEach(
+            (feature) -> options.addFeatureJar(parseFeatureJarArgument(feature)));
         continue;
       }
       String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
       if (output != null) {
-        options.setSplitBaseName(output);
+        options.setOutput(output);
         continue;
       }
       String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
@@ -114,8 +174,8 @@
     if (options.getFeatureSplitMapping() != null) {
       return FeatureClassMapping.fromSpecification(Paths.get(options.getFeatureSplitMapping()));
     }
-    assert !options.featureJars.isEmpty();
-    return FeatureClassMapping.fromJarFiles(options.featureJars);
+    assert !options.getFeatureJars().isEmpty();
+    return FeatureClassMapping.fromJarFiles(options.getFeatureJars());
   }
 
   private static void run(String[] args)
@@ -149,7 +209,7 @@
     FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
 
     DexSplitterHelper.run(
-        builder.build(), featureClassMapping, options.getSplitBaseName(), options.getProguardMap());
+        builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap());
   }
 
   public static void main(String[] args) {
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 5b96201..6893eb1 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -58,20 +59,15 @@
     return mapping;
   }
 
-  public static FeatureClassMapping fromJarFiles(List<String> jarFiles)
+  public static FeatureClassMapping fromJarFiles(List<FeatureJar> featureJars)
       throws FeatureMappingException, IOException {
     FeatureClassMapping mapping = new FeatureClassMapping();
-    for (String jar : jarFiles) {
-      Path jarPath = Paths.get(jar);
-      String featureName = jarPath.getFileName().toString();
-      if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
-        featureName = featureName.substring(0, featureName.length() - 4);
-      }
-
+    for (FeatureJar featureJar : featureJars) {
+      Path jarPath = Paths.get(featureJar.getJar());
       ArchiveClassFileProvider provider = new ArchiveClassFileProvider(jarPath);
       for (String javaDescriptor : provider.getClassDescriptors()) {
           String javaType = DescriptorUtils.descriptorToJavaType(javaDescriptor);
-          mapping.addMapping(javaType, featureName);
+          mapping.addMapping(javaType, featureJar.getOutputName());
       }
     }
     return mapping;
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index cf87d46..2db8fe0 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -84,14 +84,15 @@
             .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .build());
 
-    Path output = temp.getRoot().toPath().resolve("output");
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
     Path splitSpec = createSplitSpec();
 
     if (useOptions) {
       Options options = new Options();
       options.addInputArchive(inputZip.toString());
       options.setFeatureSplitMapping(splitSpec.toString());
-      options.setSplitBaseName(output.toString());
+      options.setOutput(output.toString());
       DexSplitter.run(options);
     } else {
       DexSplitter.main(
@@ -102,8 +103,8 @@
           });
     }
 
-    Path base = output.getParent().resolve("output.base.zip");
-    Path feature = output.getParent().resolve("output.feature1.zip");
+    Path base = output.resolve("base").resolve("classes.dex");
+    Path feature = output.resolve("feature1").resolve("classes.dex");
     validateUnobfuscatedOutput(base, feature);
   }
 
@@ -201,7 +202,8 @@
             .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .build());
 
-    Path output = temp.getRoot().toPath().resolve("output");
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
     Path baseJar = temp.getRoot().toPath().resolve("base.jar");
     Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
     ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
@@ -233,14 +235,16 @@
     featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE)));
     featureStream.closeEntry();
     featureStream.close();
+    // Make sure that we can pass in a name for the output.
+    String specificOutputName = "renamed";
     if (useOptions) {
       Options options = new Options();
       options.addInputArchive(inputZip.toString());
-      options.setSplitBaseName(output.toString());
+      options.setOutput(output.toString());
       if (explicitBase) {
         options.addFeatureJar(baseJar.toString());
       }
-      options.addFeatureJar(featureJar.toString());
+      options.addFeatureJar(featureJar.toString(), specificOutputName);
       DexSplitter.run(options);
     } else {
       List<String> args = Lists.newArrayList(
@@ -249,7 +253,7 @@
           "--output",
           output.toString(),
           "--feature-jar",
-          featureJar.toString());
+          featureJar.toString().concat(":").concat(specificOutputName));
       if (explicitBase) {
         args.add("--feature-jar");
         args.add(baseJar.toString());
@@ -257,8 +261,8 @@
 
       DexSplitter.main(args.toArray(new String[0]));
     }
-    Path base = output.getParent().resolve("output.base.zip");
-    Path feature = output.getParent().resolve("output.feature1.zip");
+    Path base = output.resolve("base").resolve("classes.dex");
+    Path feature = output.resolve(specificOutputName).resolve("classes.dex");;
     validateUnobfuscatedOutput(base, feature);
   }
 
@@ -284,7 +288,8 @@
             .addProguardConfiguration(getProguardConf(), null)
             .build());
 
-    Path outputDex = temp.getRoot().toPath().resolve("output");
+    Path outputDex = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(outputDex);
     Path splitSpec = createSplitSpec();
 
     DexSplitter.main(
@@ -295,8 +300,8 @@
           "--proguard-map", proguardMap.toString()
         });
 
-    Path base = outputDex.getParent().resolve("output.base.zip");
-    Path feature = outputDex.getParent().resolve("output.feature1.zip");
+    Path base = outputDex.resolve("base").resolve("classes.dex");
+    Path feature = outputDex.resolve("feature1").resolve("classes.dex");
     String class3 = "dexsplitsample.Class3";
     // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
     // class1 which is in base.