Extend dump to support feature splits and min-api

Bug: 156436523
Change-Id: I152ee4852552c70d1af6a45c5f9e5a984b3f374d
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 8890a4d..ede2193 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -74,11 +74,11 @@
     this.inputApp = inputApp;
   }
 
-  public DexApplication read() throws IOException, ExecutionException {
+  public DexApplication read() throws IOException {
     return read((StringResource) null);
   }
 
-  public DexApplication read(StringResource proguardMap) throws IOException, ExecutionException {
+  public DexApplication read(StringResource proguardMap) throws IOException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
       return read(proguardMap, executor);
@@ -87,14 +87,13 @@
     }
   }
 
-  public final DexApplication read(ExecutorService executorService)
-      throws IOException, ExecutionException {
+  public final DexApplication read(ExecutorService executorService) throws IOException {
     return read(
         null, executorService, ProgramClassCollection.defaultConflictResolver(options.reporter));
   }
 
   public final DexApplication read(StringResource proguardMap, ExecutorService executorService)
-      throws IOException, ExecutionException {
+      throws IOException {
     return read(
         proguardMap,
         executorService,
@@ -105,7 +104,7 @@
       StringResource proguardMap,
       ExecutorService executorService,
       ProgramClassConflictResolver resolver)
-      throws IOException, ExecutionException {
+      throws IOException {
     assert verifyMainDexOptionsCompatible(inputApp, options);
     Path dumpOutput = null;
     boolean cleanDump = false;
@@ -167,7 +166,7 @@
   }
 
   private static void dumpInputToFile(AndroidApp app, Path output, InternalOptions options) {
-    app.dump(output, options.getProguardConfiguration(), options.reporter);
+    app.dump(output, options);
   }
 
   private static boolean verifyMainDexOptionsCompatible(
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index 4a4db11..610104a 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -108,14 +108,12 @@
   }
 
   public boolean inBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
-    FeatureSplit split = javaTypeToFeatureSplitMapping.get(clazz.type.toSourceString());
-    return split == null
-        || split == javaTypeToFeatureSplitMapping.get(context.type.toSourceString());
+    FeatureSplit split = getFeatureSplit(clazz.type);
+    return split == null || split == getFeatureSplit(context.type);
   }
 
   public boolean isInFeature(DexProgramClass clazz) {
-    return javaTypeToFeatureSplitMapping.containsKey(
-        DescriptorUtils.descriptorToJavaType(clazz.type.toDescriptorString()));
+    return getFeatureSplit(clazz.type) != null;
   }
 
   public boolean isInBase(DexProgramClass clazz) {
@@ -132,13 +130,19 @@
       return true;
     }
     // TODO(141451259): Consider doing the mapping from DexType to Feature (with support in mapper)
-    return javaTypeToFeatureSplitMapping.get(
-            DescriptorUtils.descriptorToJavaType(a.toDescriptorString()))
-        == javaTypeToFeatureSplitMapping.get(
-            DescriptorUtils.descriptorToJavaType(b.toDescriptorString()));
+    return getFeatureSplit(a) == getFeatureSplit(b);
   }
 
   public List<FeatureSplit> getFeatureSplits() {
     return featureSplits;
   }
+
+  public FeatureSplit getFeatureSplitFromClassDescriptor(String classDescriptor) {
+    return javaTypeToFeatureSplitMapping.get(DescriptorUtils.descriptorToJavaType(classDescriptor));
+  }
+
+  private FeatureSplit getFeatureSplit(DexType type) {
+    assert type.isClassType();
+    return javaTypeToFeatureSplitMapping.get(type.toSourceString());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index e059db8..c0cdc54 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
@@ -99,29 +98,25 @@
 
   public static List<DexMethod> generateListOfBackportedMethods(
       AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
-    try {
-      List<DexMethod> methods = new ArrayList<>();
-      PrefixRewritingMapper rewritePrefix =
-          options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
-      AppInfo appInfo = null;
-      if (androidApp != null) {
-        DexApplication app =
-            new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
-        appInfo = new AppInfo(app);
-      }
-      AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
-      BackportedMethodRewriter.RewritableMethods rewritableMethods =
-          new BackportedMethodRewriter.RewritableMethods(options, appView);
-      rewritableMethods.visit(methods::add);
-      if (appInfo != null) {
-        DesugaredLibraryRetargeter desugaredLibraryRetargeter =
-            new DesugaredLibraryRetargeter(appView);
-        desugaredLibraryRetargeter.visit(methods::add);
-      }
-      return methods;
-    } catch (ExecutionException e) {
-      throw unwrapExecutionException(e);
+    List<DexMethod> methods = new ArrayList<>();
+    PrefixRewritingMapper rewritePrefix =
+        options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+    AppInfo appInfo = null;
+    if (androidApp != null) {
+      DexApplication app =
+          new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
+      appInfo = new AppInfo(app);
     }
+    AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+    BackportedMethodRewriter.RewritableMethods rewritableMethods =
+        new BackportedMethodRewriter.RewritableMethods(options, appView);
+    rewritableMethods.visit(methods::add);
+    if (appInfo != null) {
+      DesugaredLibraryRetargeter desugaredLibraryRetargeter =
+          new DesugaredLibraryRetargeter(appView);
+      desugaredLibraryRetargeter.visit(methods::add);
+    }
+    return methods;
   }
 
   public static void registerAssumedLibraryTypes(InternalOptions options) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 2c12a44..00853e0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
+import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
@@ -31,11 +32,11 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
@@ -45,6 +46,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -57,11 +59,13 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
@@ -75,11 +79,25 @@
 public class AndroidApp {
 
   private static final String dumpVersionFileName = "r8-version";
+  private static final String dumpBuildPropertiesFileName = "build.properties";
   private static final String dumpProgramFileName = "program.jar";
   private static final String dumpClasspathFileName = "classpath.jar";
   private static final String dumpLibraryFileName = "library.jar";
   private static final String dumpConfigFileName = "proguard.config";
 
+  private static Map<FeatureSplit, String> dumpFeatureSplitFileNames(
+      FeatureSplitConfiguration featureSplitConfiguration) {
+    Map<FeatureSplit, String> featureSplitFileNames = new IdentityHashMap<>();
+    if (featureSplitConfiguration != null) {
+      int i = 1;
+      for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+        featureSplitFileNames.put(featureSplit, "feature-" + i + ".jar");
+        i++;
+      }
+    }
+    return featureSplitFileNames;
+  }
+
   private final ImmutableList<ProgramResourceProvider> programResourceProviders;
   private final ImmutableMap<Resource, String> programResourcesMainDescriptor;
   private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
@@ -422,25 +440,40 @@
     return programResourcesMainDescriptor.get(resource);
   }
 
-  public void dump(Path output, ProguardConfiguration configuration, Reporter reporter) {
+  public void dump(Path output, InternalOptions options) {
     int nextDexIndex = 0;
     OpenOption[] openOptions =
         new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
     try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
       writeToZipStream(
           out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
-      if (configuration != null) {
-        String proguardConfig = configuration.getParsedConfiguration();
+      writeToZipStream(
+          out,
+          dumpBuildPropertiesFileName,
+          getBuildPropertiesContents(options).getBytes(),
+          ZipEntry.DEFLATED);
+      if (options.getProguardConfiguration() != null) {
+        String proguardConfig = options.getProguardConfiguration().getParsedConfiguration();
         writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED);
       }
-      nextDexIndex = dumpProgramResources(dumpProgramFileName, nextDexIndex, out);
+      nextDexIndex =
+          dumpProgramResources(
+              dumpProgramFileName,
+              dumpFeatureSplitFileNames(options.featureSplitConfiguration),
+              nextDexIndex,
+              out,
+              options.featureSplitConfiguration);
       nextDexIndex = dumpClasspathResources(nextDexIndex, out);
       nextDexIndex = dumpLibraryResources(nextDexIndex, out);
     } catch (IOException | ResourceException e) {
-      throw reporter.fatalError(new ExceptionDiagnostic(e));
+      throw options.reporter.fatalError(new ExceptionDiagnostic(e));
     }
   }
 
+  private String getBuildPropertiesContents(InternalOptions options) {
+    return "min-api=" + options.minApiLevel;
+  }
+
   private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out)
       throws IOException, ResourceException {
     nextDexIndex =
@@ -480,31 +513,96 @@
     };
   }
 
-  private int dumpProgramResources(String archiveName, int nextDexIndex, ZipOutputStream out)
+  private int dumpProgramResources(
+      String archiveName,
+      Map<FeatureSplit, String> featureSplitArchiveNames,
+      int nextDexIndex,
+      ZipOutputStream out,
+      FeatureSplitConfiguration featureSplitConfiguration)
       throws IOException, ResourceException {
-    try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
-      try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
-        Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
-        Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
-        for (DataEntryResource dataResource : dataEntries) {
-          String entryName = dataResource.getName();
-          try (InputStream dataStream = dataResource.getByteStream()) {
-            byte[] bytes = ByteStreams.toByteArray(dataStream);
-            writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+    Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams =
+        new IdentityHashMap<>();
+    Map<FeatureSplit, ZipOutputStream> featureSplitArchiveOutputStreams = new IdentityHashMap<>();
+    try {
+      if (featureSplitConfiguration != null) {
+        for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+          ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream();
+          featureSplitArchiveByteStreams.put(featureSplit, archiveByteStream);
+          featureSplitArchiveOutputStreams.put(
+              featureSplit, new ZipOutputStream(archiveByteStream));
+        }
+      }
+      try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+        try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+          Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
+          Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
+          for (DataEntryResource dataResource : dataEntries) {
+            String entryName = dataResource.getName();
+            try (InputStream dataStream = dataResource.getByteStream()) {
+              byte[] bytes = ByteStreams.toByteArray(dataStream);
+              writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+            }
+          }
+          for (ProgramResourceProvider provider : programResourceProviders) {
+            for (ProgramResource programResource : provider.getProgramResources()) {
+              nextDexIndex =
+                  dumpProgramResource(
+                      seen,
+                      nextDexIndex,
+                      classDescriptor -> {
+                        if (featureSplitConfiguration != null) {
+                          FeatureSplit featureSplit =
+                              featureSplitConfiguration.getFeatureSplitFromClassDescriptor(
+                                  classDescriptor);
+                          if (featureSplit != null) {
+                            return featureSplitArchiveOutputStreams.get(featureSplit);
+                          }
+                        }
+                        return archiveOutputStream;
+                      },
+                      archiveOutputStream,
+                      programResource);
+            }
           }
         }
-        for (ProgramResourceProvider provider : programResourceProviders) {
-          for (ProgramResource programResource : provider.getProgramResources()) {
-            nextDexIndex =
-                dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+        writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+        if (featureSplitConfiguration != null) {
+          for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+            featureSplitArchiveOutputStreams.remove(featureSplit).close();
+            writeToZipStream(
+                out,
+                featureSplitArchiveNames.get(featureSplit),
+                featureSplitArchiveByteStreams.get(featureSplit).toByteArray(),
+                ZipEntry.DEFLATED);
           }
         }
       }
-      writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+    } finally {
+      closeOutputStreams(featureSplitArchiveOutputStreams.values());
     }
     return nextDexIndex;
   }
 
+  private void closeOutputStreams(Collection<ZipOutputStream> outputStreams) throws IOException {
+    IOException exception = null;
+    RuntimeException runtimeException = null;
+    for (OutputStream outputStream : outputStreams) {
+      try {
+        outputStream.close();
+      } catch (IOException e) {
+        exception = e;
+      } catch (RuntimeException e) {
+        runtimeException = e;
+      }
+    }
+    if (exception != null) {
+      throw exception;
+    }
+    if (runtimeException != null) {
+      throw runtimeException;
+    }
+  }
+
   private static int dumpClassFileResources(
       String archiveName,
       int nextDexIndex,
@@ -519,7 +617,12 @@
             ProgramResource programResource = provider.getProgramResource(descriptor);
             int oldDexIndex = nextDexIndex;
             nextDexIndex =
-                dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+                dumpProgramResource(
+                    seen,
+                    nextDexIndex,
+                    ignore -> archiveOutputStream,
+                    archiveOutputStream,
+                    programResource);
             assert nextDexIndex == oldDexIndex;
           }
         }
@@ -532,11 +635,11 @@
   private static int dumpProgramResource(
       Object2IntMap<String> seen,
       int nextDexIndex,
-      ZipOutputStream archiveOutputStream,
+      Function<String, ZipOutputStream> cfArchiveOutputStream,
+      ZipOutputStream dexArchiveOutputStream,
       ProgramResource programResource)
       throws ResourceException, IOException {
     byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
-    String entryName;
     if (programResource.getKind() == Kind.CF) {
       Set<String> classDescriptors = programResource.getClassDescriptors();
       String classDescriptor =
@@ -546,12 +649,14 @@
       String classFileName = DescriptorUtils.getClassFileName(classDescriptor);
       int dupCount = seen.getOrDefault(classDescriptor, 0);
       seen.put(classDescriptor, dupCount + 1);
-      entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
+      String entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
+      writeToZipStream(
+          cfArchiveOutputStream.apply(classDescriptor), entryName, bytes, ZipEntry.DEFLATED);
     } else {
       assert programResource.getKind() == Kind.DEX;
-      entryName = "classes" + nextDexIndex++ + ".dex";
+      String entryName = "classes" + nextDexIndex++ + ".dex";
+      writeToZipStream(dexArchiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
     }
-    writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
     return nextDexIndex;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 21a5123..38cd73e 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -3,9 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
 import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command.Builder;
@@ -13,7 +16,9 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
@@ -30,7 +35,7 @@
   private static final List<String> VALID_OPTIONS =
       Arrays.asList("--classfile", "--compat", "--debug", "--release");
 
-  private static final List<String> VALID_OPTIONS_WITH_OPERAND =
+  private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
       Arrays.asList(
           "--output",
           "--lib",
@@ -43,6 +48,9 @@
           "--pg-map-output",
           "--desugared-lib");
 
+  private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
+      Arrays.asList("--feature-jar");
+
   public static void main(String[] args) throws CompilationFailedException {
     boolean isCompatMode = false;
     OutputMode outputMode = OutputMode.DexIndexed;
@@ -50,6 +58,7 @@
     Path pgMapOutput = null;
     CompilationMode compilationMode = CompilationMode.RELEASE;
     List<Path> program = new ArrayList<>();
+    Map<Path, Path> features = new LinkedHashMap<>();
     List<Path> library = new ArrayList<>();
     List<Path> classpath = new ArrayList<>();
     List<Path> config = new ArrayList<>();
@@ -81,7 +90,7 @@
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
-      } else if (VALID_OPTIONS_WITH_OPERAND.contains(option)) {
+      } else if (VALID_OPTIONS_WITH_SINGLE_OPERAND.contains(option)) {
         String operand = args[++i];
         switch (option) {
           case "--output":
@@ -117,11 +126,29 @@
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
+      } else if (VALID_OPTIONS_WITH_TWO_OPERANDS.contains(option)) {
+        String firstOperand = args[++i];
+        String secondOperand = args[++i];
+        switch (option) {
+          case "--feature-jar":
+            {
+              Path featureIn = Paths.get(firstOperand);
+              Path featureOut = Paths.get(secondOperand);
+              if (!isArchive(featureIn)) {
+                throw new IllegalArgumentException(
+                    "Expected an archive, got `" + featureIn.toString() + "`.");
+              }
+              features.put(featureIn, featureOut);
+              break;
+            }
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
       } else {
         program.add(Paths.get(option));
       }
     }
-    Builder builder =
+    Builder commandBuilder =
         new CompatProguardCommandBuilder(isCompatMode)
             .addProgramFiles(program)
             .addLibraryFiles(library)
@@ -130,9 +157,17 @@
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode)
             .setMinApiLevel(minApi);
+    features.forEach(
+        (in, out) ->
+            commandBuilder.addFeatureSplit(
+                featureBuilder ->
+                    featureBuilder
+                        .addProgramResourceProvider(ArchiveResourceProvider.fromArchive(in, true))
+                        .setProgramConsumer(new ArchiveConsumer(out))
+                        .build()));
     if (pgMapOutput != null) {
-      builder.setProguardMapOutputPath(pgMapOutput);
+      commandBuilder.setProguardMapOutputPath(pgMapOutput);
     }
-    R8.run(builder.build());
+    R8.run(commandBuilder.build());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
index 04c4bf2..441be7c 100644
--- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.InternalOptions;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -41,7 +41,7 @@
 
   @Test
   public void test() throws Exception {
-    Reporter reporter = new Reporter();
+    InternalOptions options = new InternalOptions();
 
     String dataResourceName = "my-resource.bin";
     byte[] dataResourceData = new byte[] {1, 2, 3};
@@ -50,7 +50,7 @@
         testForD8().addProgramClasses(B.class).setMinApi(AndroidApiLevel.B).compile().writeToZip();
 
     AndroidApp appIn =
-        AndroidApp.builder(reporter)
+        AndroidApp.builder(options.reporter)
             .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
             .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
             .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
@@ -67,9 +67,9 @@
             .build();
 
     Path dumpFile = temp.newFolder().toPath().resolve("dump.zip");
-    appIn.dump(dumpFile, null, reporter);
+    appIn.dump(dumpFile, options);
 
-    AndroidApp appOut = AndroidApp.builder(reporter).addDump(dumpFile).build();
+    AndroidApp appOut = AndroidApp.builder(options.reporter).addDump(dumpFile).build();
     assertEquals(1, appOut.getClassProgramResourcesForTesting().size());
     assertEquals(
         DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()),
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 5aa5f97..33a7e50 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -85,12 +85,26 @@
   def program_jar(self):
     return self.if_exists('program.jar')
 
+  def feature_jars(self):
+    feature_jars = []
+    i = 1
+    while True:
+      feature_jar = self.if_exists('feature-%s.jar' % i)
+      if feature_jar:
+        feature_jars.append(feature_jar)
+        i = i + 1
+      else:
+        return feature_jars
+
   def library_jar(self):
     return self.if_exists('library.jar')
 
   def classpath_jar(self):
     return self.if_exists('classpath.jar')
 
+  def build_properties_file(self):
+    return self.if_exists('build.properties')
+
   def config_file(self):
     return self.if_exists('proguard.config')
 
@@ -106,11 +120,24 @@
 def read_dump(args, temp):
   if args.dump is None:
     error("A dump file must be specified")
-  dump_file = zipfile.ZipFile(args.dump, 'r')
+  dump_file = zipfile.ZipFile(os.path.abspath(args.dump), 'r')
   with utils.ChangedWorkingDirectory(temp):
     dump_file.extractall()
     return Dump(temp)
 
+def determine_build_properties(args, dump):
+  build_properties = {}
+  build_properties_file = dump.build_properties_file()
+  if build_properties_file:
+    with open(build_properties_file) as f:
+      build_properties_contents = f.readlines()
+      for line in build_properties_contents:
+        stripped = line.strip()
+        if stripped:
+          pair = stripped.split('=')
+          build_properties[pair[0]] = pair[1]
+  return build_properties
+
 def determine_version(args, dump):
   if args.version is None:
     return dump.version()
@@ -126,6 +153,9 @@
 def determine_output(args, temp):
   return os.path.join(temp, 'out.jar')
 
+def determine_feature_output(feature_jar, temp):
+  return os.path.join(temp, os.path.basename(feature_jar)[:-4] + ".out.jar")
+
 def download_distribution(args, version, temp):
   if version == 'master':
     return utils.R8_JAR if args.nolib else utils.R8LIB_JAR
@@ -157,6 +187,7 @@
       if not os.path.exists(temp):
         os.makedirs(temp)
     dump = read_dump(args, temp)
+    build_properties = determine_build_properties(args, dump)
     version = determine_version(args, dump)
     compiler = determine_compiler(args, dump)
     out = determine_output(args, temp)
@@ -181,6 +212,9 @@
       cmd.append('--compat')
     cmd.append(dump.program_jar())
     cmd.extend(['--output', out])
+    for feature_jar in dump.feature_jars():
+      cmd.extend(['--feature-jar', feature_jar,
+                 determine_feature_output(feature_jar, temp)])
     if dump.library_jar():
       cmd.extend(['--lib', dump.library_jar()])
     if dump.classpath_jar():
@@ -189,6 +223,8 @@
       cmd.extend(['--pg-conf', dump.config_file()])
     if compiler != 'd8':
       cmd.extend(['--pg-map-output', '%s.map' % out])
+    if 'min-api' in build_properties:
+      cmd.extend(['--min-api', build_properties.get('min-api')])
     cmd.extend(otherargs)
     utils.PrintCmd(cmd)
     try: