Disallow multiple --min-api and add --no-desugaring / setDisableDesugaring.

R=sgjesse

Change-Id: Ieff5b55d8e2b32722737778bd35c293dd16f732f
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index ea4ee2f..42982d5 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -4,11 +4,13 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
@@ -102,7 +104,7 @@
 
     private CompilationMode mode;
     private int minApiLevel = AndroidApiLevel.getDefault().getLevel();
-    private boolean enableDesugaring = true;
+    private boolean disableDesugaring = false;
 
     Builder() {}
 
@@ -236,23 +238,31 @@
       return self();
     }
 
-    /**
-     * Force enable or disable desugaring.
-     *
-     * <p>There are a few use cases where it makes sense to force disable desugaring, such as:
-     * <li>if all inputs are known to be at most Java 7; or
-     * <li>if a separate desugar tool has been used prior to compiling with D8.
-     *
-     * <p>Note that even for API 27, desugaring is still required for closures support on ART.
-     */
+    @Deprecated
     public B setEnableDesugaring(boolean enableDesugaring) {
-      this.enableDesugaring = enableDesugaring;
+      this.disableDesugaring = !enableDesugaring;
       return self();
     }
 
-    /** Get the enable/disable state of desugaring. */
-    public boolean getEnableDesugaring() {
-      return enableDesugaring;
+    /**
+     * Force disable desugaring.
+     *
+     * <p>There are a few use cases where it makes sense to force disable desugaring, such as:
+     * <ul>
+     * <li>if all inputs are known to be at most Java 7; or
+     * <li>if a separate desugar tool has been used prior to compiling with D8.
+     * </ul>
+     *
+     * <p>Note that even for API 27, desugaring is still required for closures support on ART.
+     */
+    public B setDisableDesugaring(boolean disableDesugaring) {
+      this.disableDesugaring = disableDesugaring;
+      return self();
+    }
+
+    /** Is desugaring forcefully disabled. */
+    public boolean getDisableDesugaring() {
+      return disableDesugaring;
     }
 
     @Override
@@ -290,4 +300,32 @@
       super.validate();
     }
   }
+
+  static <C extends BaseCompilerCommand, B extends BaseCompilerCommand.Builder<C, B>>
+  boolean parseMinApi(
+      BaseCompilerCommand.Builder<C, B> builder,
+      String minApiString,
+      boolean hasDefinedApiLevel,
+      Origin origin) {
+    if (hasDefinedApiLevel) {
+      builder.getReporter().error(new StringDiagnostic(
+          "Cannot set multiple --min-api options", origin));
+      return false;
+    }
+    int minApi;
+    try {
+      minApi = Integer.valueOf(minApiString);
+    } catch (NumberFormatException e) {
+      builder.getReporter().error(new StringDiagnostic(
+          "Invalid argument to --min-api: " + minApiString, origin));
+      return false;
+    }
+    if (minApi < 1) {
+      builder.getReporter().error(new StringDiagnostic(
+          "Invalid argument to --min-api: " + minApiString, origin));
+      return false;
+    }
+    builder.setMinApiLevel(minApi);
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index e950d87..ddb0244 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -36,7 +36,7 @@
   public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
     super(forceProguardCompatibility);
     setIgnoreDexInArchive(true);
-    setEnableDesugaring(false);
+    setDisableDesugaring(true);
     addProguardConfiguration(REFLECTIONS, EmbeddedOrigin.INSTANCE);
   }
 
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 82d02db..d01d8ca 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -136,7 +136,7 @@
           getProgramConsumer(),
           getMinApiLevel(),
           getReporter(),
-          getEnableDesugaring(),
+          !getDisableDesugaring(),
           intermediate);
     }
 
@@ -173,6 +173,7 @@
       "  --intermediate          # Compile an intermediate result intended for later",
       "                          # merging.",
       "  --file-per-class        # Produce a separate dex file per input class",
+      "  --no-desugaring         # Force disable desugaring.",
       "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
       "  --version               # Print the version of d8.",
       "  --help                  # Print this message."));
@@ -202,10 +203,28 @@
    * @return D8 command builder with state set up according to parsed command line.
    */
   public static Builder parse(String[] args, Origin origin) {
+    return parse(builder(), args, origin);
+  }
+
+  /**
+   * Parse the D8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return D8 command builder with state set up according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return parse(builder(handler), args, origin);
+  }
+
+  private static Builder parse(Builder builder, String[] args, Origin origin) {
     CompilationMode compilationMode = null;
     Path outputPath = null;
     OutputMode outputMode = null;
-    Builder builder = builder();
+    boolean hasDefinedApiLevel = false;
     try {
       for (int i = 0; i < args.length; i++) {
         String arg = args[i].trim();
@@ -249,9 +268,11 @@
         } else if (arg.equals("--main-dex-list")) {
           builder.addMainDexListFiles(Paths.get(args[++i]));
         } else if (arg.equals("--min-api")) {
-          builder.setMinApiLevel(Integer.valueOf(args[++i]));
+          hasDefinedApiLevel = parseMinApi(builder, args[++i], hasDefinedApiLevel, origin);
         } else if (arg.equals("--intermediate")) {
           builder.setIntermediate(true);
+        } else if (arg.equals("--no-desugaring")) {
+          builder.setDisableDesugaring(true);
         } else {
           if (arg.startsWith("--")) {
             builder.getReporter().error(new StringDiagnostic("Unknown option: " + arg,
@@ -291,7 +312,6 @@
         minApiLevel,
         diagnosticsHandler,
         enableDesugaring);
-    assert programConsumer != null;
     this.intermediate = intermediate;
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4d6ae7b..cb014c7 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -317,7 +317,7 @@
               getMode(),
               getMinApiLevel(),
               reporter,
-              getEnableDesugaring(),
+              !getDisableDesugaring(),
               useTreeShaking,
               useMinification,
               forceProguardCompatibility,
@@ -367,6 +367,7 @@
     CompilationMode mode = null;
     OutputMode outputMode = null;
     Path outputPath = null;
+    boolean hasDefinedApiLevel = false;
   }
 
   static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
@@ -385,6 +386,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-desugaring          # Force disable desugaring.",
       "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
       "                           # primary dex file.",
       "  --main-dex-list <file>   # List of classes to place in the primary dex file.",
@@ -431,7 +433,24 @@
    * @return R8 command builder with state set up according to parsed command line.
    */
   public static Builder parse(String[] args, Origin origin) {
-    Builder builder = builder();
+    return parse(builder(), args, origin);
+  }
+
+  /**
+   * Parse the R8 command-line.
+   *
+   * Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return R8 command builder with state set up according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return parse(builder(handler), args, origin);
+  }
+
+  private static Builder parse(Builder builder, String[] args, Origin origin) {
     ParseState state = new ParseState();
     parse(args, origin, builder, state);
     if (state.mode != null) {
@@ -495,11 +514,14 @@
       } else if (arg.equals("--lib")) {
         builder.addLibraryFiles(Paths.get(args[++i]));
       } else if (arg.equals("--min-api")) {
-        builder.setMinApiLevel(Integer.valueOf(args[++i]));
+        state.hasDefinedApiLevel =
+            parseMinApi(builder, args[++i], state.hasDefinedApiLevel, argsOrigin);
       } else if (arg.equals("--no-tree-shaking")) {
         builder.setDisableTreeShaking(true);
       } else if (arg.equals("--no-minification")) {
         builder.setDisableMinification(true);
+      } else if (arg.equals("--no-desugaring")) {
+        builder.setDisableDesugaring(true);
       } else if (arg.equals("--main-dex-rules")) {
         builder.addMainDexRulesFiles(Paths.get(args[++i]));
       } else if (arg.equals("--main-dex-list")) {
@@ -543,8 +565,7 @@
     return state;
   }
 
-  private
-  R8Command(
+  private R8Command(
       AndroidApp inputApp,
       ProgramConsumer programConsumer,
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
index bf01553..250ca3b 100644
--- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -141,7 +141,7 @@
             .setMode(CompilationMode.DEBUG)
             .addProgramFiles(input)
             .addLibraryFiles(LIB)
-            .setEnableDesugaring(desugar)
+            .setDisableDesugaring(!desugar)
             .setProgramConsumer(consumer)
             .build(),
         executor);
@@ -187,7 +187,7 @@
               .addClasspathResourceProvider(provider)
               .addLibraryFiles(LIB)
               .setProgramConsumer(consumer)
-              .setEnableDesugaring(desugar);
+              .setDisableDesugaring(!desugar);
       for (int j = 0; j < count; j++) {
         builder.addClassProgramData(provider.resources.get(descriptors.get(index + j)),
             Origin.unknown());
@@ -206,7 +206,7 @@
             .setIntermediate(false)
             .setMode(CompilationMode.DEBUG)
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-            .setEnableDesugaring(false);
+            .setDisableDesugaring(true);
     for (ProgramResource input : outputs.values()) {
       try (InputStream inputStream = input.getByteStream()) {
         builder.addDexProgramData(ByteStreams.toByteArray(inputStream), input.getOrigin());
diff --git a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
index df79ae4..899b187 100644
--- a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
@@ -161,7 +161,7 @@
         .setProgramConsumer(consumer)
         .setMode(noLocals ? CompilationMode.RELEASE : CompilationMode.DEBUG)
         .setMinApiLevel(AndroidApiLevel.H_MR2.getLevel())
-        .setEnableDesugaring(false);
+        .setDisableDesugaring(true);
     try (InputStream stream = zipFile.getInputStream(classEntry)) {
       builder.addClassProgramData(
           ByteStreams.toByteArray(stream),
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
index 471c6c2..ad31005 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -333,7 +333,7 @@
               .addClasspathFiles(classpath)
               .addLibraryFiles(libraries)
               .addProgramFiles(inputs)
-              .setEnableDesugaring(true)
+              .setDisableDesugaring(false)
               .build());
     } catch (CompilationFailedException e) {
       throw new RuntimeException("Unexpected compilation exceptions", e);
@@ -367,7 +367,7 @@
         D8Command.builder(handler)
             .setMinApiLevel(minApiLevel)
             .setProgramConsumer(new EnsureOutputConsumer())
-            .setEnableDesugaring(false);
+            .setDisableDesugaring(true);
     for (byte[] intermediate : intermediates) {
       builder.addDexProgramData(intermediate, Origin.unknown());
     }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 7583e97..76856a8 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -23,7 +23,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.zip.ZipFile;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -33,7 +32,6 @@
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
-  @Ignore("Enable when deprecated API is removed")
   @Test(expected = CompilationFailedException.class)
   public void emptyBuilder() throws Throwable {
     verifyEmptyCommand(D8Command.builder().build());
@@ -325,7 +323,41 @@
     D8Command.builder().setProgramConsumer(new MultiTypeConsumer()).build();
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void duplicateApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "multiple --min-api", handler -> parse(handler, "--min-api", "19", "--min-api", "21"));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void invalidApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Invalid argument to --min-api", handler -> parse(handler, "--min-api", "foobar"));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void negativeApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Invalid argument to --min-api", handler -> parse(handler, "--min-api", "-21"));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void zeroApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Invalid argument to --min-api", handler -> parse(handler, "--min-api", "0"));
+  }
+
+  @Test
+  public void disableDesugaring() throws CompilationFailedException {
+    assertFalse(parse("--no-desugaring").getEnableDesugaring());
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
+
+  private D8Command parse(DiagnosticsHandler handler, String... args)
+      throws CompilationFailedException {
+    return D8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
new file mode 100644
index 0000000..7cd32c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -0,0 +1,52 @@
+// 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 static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.utils.ListUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+// Helper to check that a particular error occurred.
+class DiagnosticsChecker implements DiagnosticsHandler {
+  public List<Diagnostic> errors = new ArrayList<>();
+  public List<Diagnostic> warnings = new ArrayList<>();
+  public List<Diagnostic> infos = new ArrayList<>();
+
+  @Override
+  public void error(Diagnostic error) {
+    errors.add(error);
+  }
+
+  @Override
+  public void warning(Diagnostic warning) {
+    warnings.add(warning);
+  }
+
+  @Override
+  public void info(Diagnostic info) {
+    infos.add(info);
+  }
+
+  public interface FailingRunner {
+    void run(DiagnosticsHandler handler) throws CompilationFailedException;
+  }
+
+  public static void checkErrorsContains(String snippet, FailingRunner runner)
+      throws CompilationFailedException {
+    DiagnosticsChecker handler = new DiagnosticsChecker();
+    try {
+      runner.run(handler);
+    } catch (CompilationFailedException e) {
+      assertTrue(
+          "Expected to find snippet '"
+              + snippet
+              + "' in error messages:\n"
+              + String.join("\n", ListUtils.map(handler.errors, Diagnostic::getDiagnosticMessage)),
+          handler.errors.stream().anyMatch(d -> d.getDiagnosticMessage().contains(snippet)));
+      throw e;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index f782a94..6f18657 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -18,7 +18,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -28,44 +27,6 @@
 
 public class R8CommandTest {
 
-  // Helper to check that a particular error occurred.
-  static class DiagnosticsChecker implements DiagnosticsHandler {
-    public List<Diagnostic> errors = new ArrayList<>();
-    public List<Diagnostic> warnings = new ArrayList<>();
-    public List<Diagnostic> infos = new ArrayList<>();
-
-    @Override
-    public void error(Diagnostic error) {
-      errors.add(error);
-    }
-
-    @Override
-    public void warning(Diagnostic warning) {
-      warnings.add(warning);
-    }
-
-    @Override
-    public void info(Diagnostic info) {
-      infos.add(info);
-    }
-
-    public interface FailingRunner {
-      void run(DiagnosticsHandler handler) throws CompilationFailedException;
-    }
-
-    public static void checkErrorsContains(String snippet, FailingRunner runner)
-        throws CompilationFailedException {
-      DiagnosticsChecker handler = new DiagnosticsChecker();
-      try {
-        runner.run(handler);
-      } catch (CompilationFailedException e) {
-        assertTrue(handler.errors.stream()
-            .anyMatch(d -> d.getDiagnosticMessage().contains(snippet)));
-        throw e;
-      }
-    }
-  }
-
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -363,7 +324,41 @@
         .build();
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void duplicateApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "multiple --min-api", handler -> parse(handler, "--min-api", "19", "--min-api", "21"));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void invalidApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Invalid argument to --min-api", handler -> parse(handler, "--min-api", "foobar"));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void negativeApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Invalid argument to --min-api", handler -> parse(handler, "--min-api", "-21"));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void zeroApiLevel() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Invalid argument to --min-api", handler -> parse(handler, "--min-api", "0"));
+  }
+
+  @Test
+  public void disableDesugaring() throws CompilationFailedException {
+    assertFalse(parse("--no-desugaring").getEnableDesugaring());
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
+
+  private R8Command parse(DiagnosticsHandler handler, String... args)
+      throws CompilationFailedException {
+    return R8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
+  }
 }