Merge "Add easier control of whether data resources are processed or not by R8"
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index b57ff61..6ae611c 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -235,11 +235,16 @@
      * @param outputMode Mode in which to write the output.
      */
     public B setOutput(Path outputPath, OutputMode outputMode) {
+      return setOutput(outputPath, outputMode, false);
+    }
+
+    // This is only public in R8Command.
+    protected B setOutput(Path outputPath, OutputMode outputMode, boolean includeDataResources) {
       assert outputPath != null;
       assert outputMode != null;
       this.outputPath = outputPath;
       this.outputMode = outputMode;
-      programConsumer = createProgramOutputConsumer(outputPath, outputMode, false);
+      programConsumer = createProgramOutputConsumer(outputPath, outputMode, includeDataResources);
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5195a82..c53f367 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -239,6 +239,47 @@
       return self();
     }
 
+    /**
+     * Set the output path-and-mode.
+     *
+     * <p>Setting the output path-and-mode will override any previous set consumer or any previous
+     * output path-and-mode, and implicitly sets the appropriate program consumer to write the
+     * output.
+     *
+     * <p>By default data resources from the input will be included in the output. (see {@link
+     * #setOutput(Path, OutputMode, boolean) for details}
+     *
+     * @param outputPath Path to write the output to. Must be an archive or and existing directory.
+     * @param outputMode Mode in which to write the output.
+     */
+    @Override
+    public Builder setOutput(Path outputPath, OutputMode outputMode) {
+      setOutput(outputPath, outputMode, true);
+      return self();
+    }
+
+    /**
+     * Set the output path-and-mode and control if data resources are included.
+     *
+     * <p>In addition to setting the output path-and-mode (see {@link #setOutput(Path, OutputMode)})
+     * this can control if data resources should be included or not.
+     *
+     * <p>Data resources are non Java classfile items in the input.
+     *
+     * <p>If data resources are not included they are ignored in the input and will not produce
+     * anything in the output. If data resources are included they are processed according to the
+     * configuration and written to the output.
+     *
+     * @param outputPath Path to write the output to. Must be an archive or and existing directory.
+     * @param outputMode Mode in which to write the output.
+     * @param includeDataResources If data resources from the input should be included in the
+     *     output.
+     */
+    @Override
+    public Builder setOutput(Path outputPath, OutputMode outputMode, boolean includeDataResources) {
+      return super.setOutput(outputPath, outputMode, includeDataResources);
+    }
+
     @Override
     public Builder addProgramResourceProvider(ProgramResourceProvider programProvider) {
       return super.addProgramResourceProvider(
@@ -250,7 +291,7 @@
         Path path,
         OutputMode mode,
         boolean consumeDataResources) {
-      return super.createProgramOutputConsumer(path, mode, false);
+      return super.createProgramOutputConsumer(path, mode, consumeDataResources);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 3d33037..580b068 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -27,6 +27,7 @@
     OutputMode outputMode = null;
     Path outputPath = null;
     boolean hasDefinedApiLevel = false;
+    private boolean includeDataResources = true;
   }
 
   static final String USAGE_MESSAGE =
@@ -49,6 +50,7 @@
               "  --pg-map-output <file>   # Output the resulting name and line mapping to <file>.",
               "  --no-tree-shaking        # Force disable tree shaking of unreachable classes.",
               "  --no-minification        # Force disable minification of names.",
+              "  --no-data-resources      # Ignore all data resources.",
               "  --no-desugaring          # Force disable desugaring.",
               "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
               "                           # primary dex file.",
@@ -91,7 +93,7 @@
     }
     Path outputPath = state.outputPath != null ? state.outputPath : Paths.get(".");
     OutputMode outputMode = state.outputMode != null ? state.outputMode : OutputMode.DexIndexed;
-    builder.setOutput(outputPath, outputMode);
+    builder.setOutput(outputPath, outputMode, state.includeDataResources);
     return builder;
   }
 
@@ -175,6 +177,8 @@
         builder.addProguardConfigurationFiles(Paths.get(expandedArgs[++i]));
       } else if (arg.equals("--pg-map-output")) {
         builder.setProguardMapOutputPath(Paths.get(expandedArgs[++i]));
+      } else if (arg.equals("--no-data-resources")) {
+        state.includeDataResources = false;
       } else {
         if (arg.startsWith("--")) {
           builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index dad3120..ec15120 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -34,6 +34,7 @@
     public final String output;
     public final int minApi;
     public final boolean forceProguardCompatibility;
+    public final boolean includeDataResources;
     public final boolean multiDex;
     public final String mainDexList;
     public final List<String> proguardConfig;
@@ -48,12 +49,14 @@
         int minApi,
         boolean multiDex,
         boolean forceProguardCompatibility,
+        boolean includeDataResources,
         String mainDexList,
         boolean printHelpAndExit,
         boolean verticalClassMerging) {
       this.output = output;
       this.minApi = minApi;
       this.forceProguardCompatibility = forceProguardCompatibility;
+      this.includeDataResources = includeDataResources;
       this.multiDex = multiDex;
       this.mainDexList = mainDexList;
       this.proguardConfig = proguardConfig;
@@ -65,6 +68,7 @@
       String output = null;
       int minApi = 1;
       boolean forceProguardCompatibility = false;
+      boolean includeDataResources = true;
       boolean multiDex = false;
       String mainDexList = null;
       boolean printHelpAndExit = false;
@@ -87,6 +91,8 @@
               minApi = Integer.valueOf(args[++i]);
             } else if (arg.equals("--force-proguard-compatibility")) {
               forceProguardCompatibility = true;
+            } else if (arg.equals("--no-data-resources")) {
+              includeDataResources = false;
             } else if (arg.equals("--output")) {
               output = args[++i];
             } else if (arg.equals("--multi-dex")) {
@@ -129,6 +135,7 @@
           minApi,
           multiDex,
           forceProguardCompatibility,
+          includeDataResources,
           mainDexList,
           printHelpAndExit,
           verticalClassMerging);
@@ -142,6 +149,8 @@
       System.out.println("--multi-dex          : ignored (provided for compatibility)");
       System.out.println("--no-locals          : ignored (provided for compatibility)");
       System.out.println("--core-library       : ignored (provided for compatibility)");
+      System.out.println("--force-proguard-compatibility : Proguard compatibility mode");
+      System.out.println("--no-data-resources  : ignore all data resources");
     }
   }
 
@@ -170,7 +179,7 @@
         new CompatProguardCommandBuilder(
             options.forceProguardCompatibility, options.enableVerticalClassMerging);
     builder
-        .setOutput(Paths.get(options.output), OutputMode.DexIndexed)
+        .setOutput(Paths.get(options.output), OutputMode.DexIndexed, options.includeDataResources)
         .addProguardConfiguration(options.proguardConfig, CommandLineOrigin.INSTANCE)
         .setMinApiLevel(options.minApi);
     if (options.mainDexList != null) {
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 16bbae9..0f0bc2f 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.reflect.Method;
@@ -22,10 +23,13 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -463,6 +467,116 @@
     assertEquals(0, new ZipFile(emptyZip.toFile(), StandardCharsets.UTF_8).size());
   }
 
+  private Path writeZipWithDataResource(String name) throws Exception {
+    Path dataResourceZip = temp.newFolder().toPath().resolve(name);
+    try (ZipOutputStream out =
+        new ZipOutputStream(
+            Files.newOutputStream(
+                dataResourceZip,
+                StandardOpenOption.CREATE,
+                StandardOpenOption.TRUNCATE_EXISTING))) {
+      // Write a directory entry and a normal entry.
+      ZipUtils.writeToZipStream(out, "org/", new byte[] {}, ZipEntry.STORED);
+      ZipUtils.writeToZipStream(
+          out, "org/resource.txt", "Hello world!".getBytes(), ZipEntry.STORED);
+    }
+    return dataResourceZip;
+  }
+
+  @Test
+  public void defaultResourceProcessing() throws Exception {
+    Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
+    Path outputZip = temp.getRoot().toPath().resolve("output.zip");
+    R8.run(
+        R8Command.builder()
+            .addProgramFiles(dataResourceZip)
+            .setOutput(outputZip, OutputMode.ClassFile)
+            .build());
+    assertTrue(Files.exists(outputZip));
+    assertEquals(2, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
+  }
+
+  public void runCustomResourceProcessing(boolean includeDataResources, int expectedZipEntries)
+      throws Exception {
+    Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
+    Path outputZip = temp.newFolder().toPath().resolve("output.zip");
+    R8.run(
+        R8Command.builder()
+            .addProgramFiles(dataResourceZip)
+            .setOutput(outputZip, OutputMode.ClassFile, includeDataResources)
+            .build());
+    assertTrue(Files.exists(outputZip));
+    assertEquals(
+        expectedZipEntries, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
+  }
+
+  private Path simpleProguardConfiguration() throws Exception {
+    Path proguardConfiguration = temp.newFile("printseedsandprintusage.txt").toPath();
+    FileUtils.writeTextFile(proguardConfiguration, ImmutableList.of("-keep class A { *; }"));
+    return proguardConfiguration;
+  }
+
+  @Test
+  public void noTreeShakingOption() throws Throwable {
+    // Default "keep all" rule implies no tree shaking.
+    assertFalse(parse().getEnableTreeShaking());
+    assertFalse(parse("--no-tree-shaking").getEnableTreeShaking());
+
+    // With a Proguard configuration --no-tree-shaking takes effect.
+    String proguardConfiguration = simpleProguardConfiguration().toAbsolutePath().toString();
+    assertTrue(parse("--pg-conf", proguardConfiguration).getEnableTreeShaking());
+    assertFalse(
+        parse("--no-tree-shaking", "--pg-conf", proguardConfiguration).getEnableTreeShaking());
+  }
+
+  @Test
+  public void noMinificationOption() throws Throwable {
+    // Default "keep all" rule implies no tree minification.
+    assertFalse(parse().getEnableMinification());
+    assertFalse(parse("--no-minification").getEnableMinification());
+
+    // With a Proguard configuration --no-tree-shaking takes effect.
+    String proguardConfiguration = simpleProguardConfiguration().toAbsolutePath().toString();
+    assertTrue(parse("--pg-conf", proguardConfiguration).getEnableMinification());
+    assertFalse(
+        parse("--no-minification", "--pg-conf", proguardConfiguration).getEnableMinification());
+  }
+
+  @Test
+  public void defaultDataResourcesOption() throws Throwable {
+    Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
+    Path outputZip = temp.newFolder().toPath().resolve("output.zip");
+
+    R8.run(
+        parse(
+            dataResourceZip.toAbsolutePath().toString(),
+            "--output",
+            outputZip.toAbsolutePath().toString()));
+    assertTrue(Files.exists(outputZip));
+    assertEquals(2, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
+  }
+
+  @Test
+  public void noDataResourcesOption() throws Throwable {
+    Path dataResourceZip = writeZipWithDataResource("dataResource.zip");
+    Path outputZip = temp.newFolder().toPath().resolve("output.zip");
+
+    R8.run(
+        parse(
+            "--no-data-resources",
+            dataResourceZip.toAbsolutePath().toString(),
+            "--output",
+            outputZip.toAbsolutePath().toString()));
+    assertTrue(Files.exists(outputZip));
+    assertEquals(0, new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8).size());
+  }
+
+  @Test
+  public void customResourceProcessing() throws Exception {
+    runCustomResourceProcessing(true, 2);
+    runCustomResourceProcessing(false, 0);
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
index 8da93a5..cf95846 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.compatproguard;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.compatproguard.CompatProguard.CompatProguardOptions;
 import org.junit.Test;
@@ -16,9 +19,20 @@
   }
 
   @Test
+  public void testDefaultDataResources() throws Exception {
+    CompatProguardOptions options = parseArgs();
+    assertNull(options.output);
+    assertEquals(1, options.minApi);
+    assertFalse(options.forceProguardCompatibility);
+    assertTrue(options.includeDataResources);
+    assertFalse(options.multiDex);
+    assertNull(options.mainDexList);
+    assertEquals(0, options.proguardConfig.size());
+  }
+
+  @Test
   public void testShortLine() throws Exception {
-    CompatProguardOptions options;
-    options = parseArgs("-");
+    CompatProguardOptions options = parseArgs("-");
     assertEquals(1, options.proguardConfig.size());
   }
 
@@ -65,18 +79,20 @@
 
   @Test
   public void testInclude() throws Exception {
-    CompatProguardOptions options;
-
-    options = parseArgs("-include --my-include-file.txt");
+    CompatProguardOptions options = parseArgs("-include --my-include-file.txt");
     assertEquals(1, options.proguardConfig.size());
     assertEquals("-include --my-include-file.txt", options.proguardConfig.get(0));
   }
 
   @Test
   public void testNoLocalsOption() throws Exception {
-    CompatProguardOptions options;
-
-    options = parseArgs("--no-locals");
+    CompatProguardOptions options = parseArgs("--no-locals");
     assertEquals(0, options.proguardConfig.size());
   }
+
+  @Test
+  public void testNoDataResources() throws Exception {
+    CompatProguardOptions options = parseArgs("--no-data-resources");
+    assertFalse(options.includeDataResources);
+  }
 }