Merge changes I2342b395,Ie0918957,I0a9a2a17

* changes:
  Use command-line parsing in apiUsageSample
  Add @Keep and @KeepForSubclassing annotations for public R8 API
  Make R8 and D8 command-line parsing use only public API
diff --git a/build.gradle b/build.gradle
index cd831cc..f379bbf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -111,7 +111,12 @@
     }
     apiUsageSample {
         java {
-            srcDirs = ['src/test/apiUsageSample']
+            srcDirs = ['src/test/apiUsageSample', 'src/main/java']
+            include 'com/android/tools/apiusagesample/*.java'
+            include 'com/android/tools/r8/BaseCompilerCommandParser.java'
+            include 'com/android/tools/r8/D8CommandParser.java'
+            include 'com/android/tools/r8/R8CommandParser.java'
+            include 'com/android/tools/r8/utils/FlagFile.java'
         }
     }
     debugTestResources {
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
index e17acd0..b3d59fb 100644
--- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -35,6 +35,7 @@
  * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for
  * resources in the descriptor set will then force the read of zip entry contents.
  */
+@Keep
 public class ArchiveClassFileProvider implements ClassFileResourceProvider, Closeable {
   private final Origin origin;
   private final ZipFile zipFile;
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index b3a4bfb..baac9bc 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -29,8 +29,10 @@
 import java.util.zip.ZipFile;
 
 /** Provider for archives of program resources. */
+@KeepForSubclassing
 public class ArchiveProgramResourceProvider implements ProgramResourceProvider {
 
+  @KeepForSubclassing
   public interface ZipFileSupplier {
     ZipFile open() throws IOException;
   }
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index fbc6ca3..8828248 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -28,6 +28,7 @@
  * <p>For concrete builders, see for example {@link D8Command.Builder} and {@link
  * R8Command.Builder}.
  */
+@Keep
 public abstract class BaseCommand {
 
   private final boolean printHelp;
@@ -101,6 +102,7 @@
    * @param <B> Concrete builder extending this base, e.g., {@link R8Command.Builder} or {@link
    *     D8Command.Builder}.
    */
+  @Keep
   public abstract static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
 
     private final Reporter reporter;
@@ -304,6 +306,20 @@
       return self();
     }
 
+    /** Signal an error. */
+    public void error(Diagnostic diagnostic) {
+      reporter.error(diagnostic);
+    }
+
+    /**
+     * Signal an error and throw {@link AbortException}.
+     *
+     * @throws AbortException always.
+     */
+    public RuntimeException fatalError(Diagnostic diagnostic) {
+      return reporter.fatalError(diagnostic);
+    }
+
     // Internal helper for compat tools to make them ignore DEX code in input archives.
     void setIgnoreDexInArchive(boolean value) {
       guard(() -> app.setIgnoreDexInArchive(value));
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 799d834..5980a0c 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -4,13 +4,11 @@
 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;
@@ -23,6 +21,7 @@
  * <p>For concrete builders, see for example {@link D8Command.Builder} and {@link
  * R8Command.Builder}.
  */
+@Keep
 public abstract class BaseCompilerCommand extends BaseCommand {
 
   private final CompilationMode mode;
@@ -106,6 +105,7 @@
    * @param <B> Concrete builder extending this base, e.g., {@link R8Command.Builder} or {@link
    *     D8Command.Builder}.
    */
+  @Keep
   public abstract static class Builder<C extends BaseCompilerCommand, B extends Builder<C, B>>
       extends BaseCommand.Builder<C, B> {
 
@@ -341,31 +341,4 @@
     }
   }
 
-  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/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
new file mode 100644
index 0000000..054db58
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -0,0 +1,25 @@
+// 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 com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.StringDiagnostic;
+
+public class BaseCompilerCommandParser {
+
+  static void parseMinApi(BaseCompilerCommand.Builder builder, String minApiString, Origin origin) {
+    int minApi;
+    try {
+      minApi = Integer.valueOf(minApiString);
+    } catch (NumberFormatException e) {
+      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin));
+      return;
+    }
+    if (minApi < 1) {
+      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin));
+      return;
+    }
+    builder.setMinApiLevel(minApi);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 6c9a286..3bab0ab 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -26,6 +26,7 @@
  *
  * <p>This consumer can only be provided to R8.
  */
+@KeepForSubclassing
 public interface ClassFileConsumer extends ProgramConsumer {
 
   /**
@@ -49,6 +50,7 @@
   }
 
   /** Forwarding consumer to delegate to an optional existing consumer. */
+  @Keep
   class ForwardingConsumer implements ClassFileConsumer {
 
     private static final ClassFileConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
@@ -80,6 +82,7 @@
   }
 
   /** Consumer to write program resources to an output. */
+  @Keep
   class ArchiveConsumer extends ForwardingConsumer
       implements DataResourceConsumer, InternalProgramOutputPathConsumer {
     private final OutputBuilder outputBuilder;
@@ -163,6 +166,7 @@
   }
 
   /** Directory consumer to write program resources to a directory. */
+  @Keep
   class DirectoryConsumer extends ForwardingConsumer implements InternalProgramOutputPathConsumer {
     private final OutputBuilder outputBuilder;
     protected final boolean consumeDataResouces;
diff --git a/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java b/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
index 4c71d88..db0a9f8 100644
--- a/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
@@ -12,6 +12,7 @@
  * only created on-demand when they are needed by the compiler. If never needed, the resource will
  * never be loaded.
  */
+@KeepForSubclassing
 public interface ClassFileResourceProvider {
 
   /**
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index a6923ba..89a33c1 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -6,6 +6,8 @@
 
 import java.nio.file.Path;
 
+// This class is used by the Android Studio Gradle plugin and is thus part of the R8 API.
+@Keep
 public class CompatProguardCommandBuilder extends R8Command.Builder {
   public CompatProguardCommandBuilder() {
     this(true);
diff --git a/src/main/java/com/android/tools/r8/CompilationFailedException.java b/src/main/java/com/android/tools/r8/CompilationFailedException.java
index 561ad0c..bcc4ad1 100644
--- a/src/main/java/com/android/tools/r8/CompilationFailedException.java
+++ b/src/main/java/com/android/tools/r8/CompilationFailedException.java
@@ -7,6 +7,7 @@
  * Exception thrown when compilation failed to complete because of errors previously reported
  * through {@link com.android.tools.r8.DiagnosticsHandler}.
  */
+@Keep
 public class CompilationFailedException extends Exception {
 
   public CompilationFailedException() {
diff --git a/src/main/java/com/android/tools/r8/CompilationMode.java b/src/main/java/com/android/tools/r8/CompilationMode.java
index f7aa4f0..fb02504 100644
--- a/src/main/java/com/android/tools/r8/CompilationMode.java
+++ b/src/main/java/com/android/tools/r8/CompilationMode.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 /** Compilation mode. */
+@Keep
 public enum CompilationMode {
   /** Preserves debugging information during compilation, eg, line-numbers and locals. */
   DEBUG,
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 06cf0bb..f4d378a 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -52,6 +52,7 @@
  * them to DEX bytecode (compiling from Java bytecode for such inputs and merging for DEX inputs),
  * and then writes the result to the directory or zip archive specified by {@code outputPath}.
  */
+@Keep
 public final class D8 {
 
   private D8() {}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 6f956db..8c6c02b 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -3,18 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collection;
 
@@ -31,7 +26,8 @@
  *     .build();
  * </pre>
  */
-public class D8Command extends BaseCompilerCommand {
+@Keep
+public final class D8Command extends BaseCompilerCommand {
 
   private static class ClasspathInputOrigin extends InputFileOrigin {
 
@@ -45,6 +41,7 @@
    *
    * <p>A builder is obtained by calling {@link D8Command#builder}.
    */
+  @Keep
   public static class Builder extends BaseCompilerCommand.Builder<D8Command, Builder> {
 
     private boolean intermediate = false;
@@ -150,24 +147,7 @@
     }
   }
 
-  static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-      "Usage: d8 [options] <input-files>",
-      " where <input-files> are any combination of dex, class, zip, jar, or apk files",
-      " and options are:",
-      "  --debug                 # Compile with debugging information (default).",
-      "  --release               # Compile without debugging information.",
-      "  --output <file>         # Output result in <outfile>.",
-      "                          # <file> must be an existing directory or a zip file.",
-      "  --lib <file>            # Add <file> as a library resource.",
-      "  --classpath <file>      # Add <file> as a classpath resource.",
-      "  --min-api               # Minimum Android API level compatibility",
-      "  --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."));
+  static final String USAGE_MESSAGE = D8CommandParser.USAGE_MESSAGE;
 
   private boolean intermediate = false;
 
@@ -194,7 +174,7 @@
    * @return D8 command builder with state set up according to parsed command line.
    */
   public static Builder parse(String[] args, Origin origin) {
-    return parse(args, origin, builder());
+    return D8CommandParser.parse(args, origin);
   }
 
   /**
@@ -208,87 +188,7 @@
    * @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(args, origin, builder(handler));
-  }
-
-  private static Builder parse(String[] args, Origin origin, Builder builder) {
-    CompilationMode compilationMode = null;
-    Path outputPath = null;
-    OutputMode outputMode = null;
-    boolean hasDefinedApiLevel = false;
-    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder.getReporter());
-    try {
-      for (int i = 0; i < expandedArgs.length; i++) {
-        String arg = expandedArgs[i].trim();
-        if (arg.length() == 0) {
-          continue;
-        } else if (arg.equals("--help")) {
-          builder.setPrintHelp(true);
-        } else if (arg.equals("--version")) {
-          builder.setPrintVersion(true);
-        } else if (arg.equals("--debug")) {
-          if (compilationMode == CompilationMode.RELEASE) {
-            builder.getReporter().error(new StringDiagnostic(
-                "Cannot compile in both --debug and --release mode.",
-                origin));
-            continue;
-          }
-          compilationMode = CompilationMode.DEBUG;
-        } else if (arg.equals("--release")) {
-          if (compilationMode == CompilationMode.DEBUG) {
-            builder.getReporter().error(new StringDiagnostic(
-                "Cannot compile in both --debug and --release mode.",
-                origin));
-            continue;
-          }
-          compilationMode = CompilationMode.RELEASE;
-        } else if (arg.equals("--file-per-class")) {
-          outputMode = OutputMode.DexFilePerClassFile;
-        } else if (arg.equals("--output")) {
-          String output = expandedArgs[++i];
-          if (outputPath != null) {
-            builder.getReporter().error(new StringDiagnostic(
-                "Cannot output both to '" + outputPath.toString() + "' and '" + output + "'",
-                origin));
-            continue;
-          }
-          outputPath = Paths.get(output);
-        } else if (arg.equals("--lib")) {
-          builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
-        } else if (arg.equals("--classpath")) {
-          builder.addClasspathFiles(Paths.get(expandedArgs[++i]));
-        } else if (arg.equals("--main-dex-list")) {
-          builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
-        } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
-          builder.setOptimizeMultidexForLinearAlloc(true);
-        } else if (arg.equals("--min-api")) {
-          hasDefinedApiLevel = parseMinApi(builder, expandedArgs[++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,
-                origin));
-            continue;
-          }
-          builder.addProgramFiles(Paths.get(arg));
-        }
-      }
-      if (compilationMode != null) {
-        builder.setMode(compilationMode);
-      }
-      if (outputMode == null) {
-        outputMode = OutputMode.DexIndexed;
-      }
-      if (outputPath == null) {
-        outputPath = Paths.get(".");
-      }
-      return builder.setOutput(outputPath, outputMode);
-    } catch (CompilationError e) {
-      throw builder.getReporter().fatalError(e);
-    }
+    return D8CommandParser.parse(args, origin, handler);
   }
 
   private D8Command(
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
new file mode 100644
index 0000000..9e5d36a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -0,0 +1,157 @@
+// 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 com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class D8CommandParser extends BaseCompilerCommandParser {
+
+  public static void main(String[] args) throws CompilationFailedException {
+    D8Command command = parse(args, Origin.root()).build();
+    if (command.isPrintHelp()) {
+      System.out.println(USAGE_MESSAGE);
+      System.exit(1);
+    }
+    D8.run(command);
+  }
+
+  static final String USAGE_MESSAGE =
+      String.join(
+          "\n",
+          Arrays.asList(
+              "Usage: d8 [options] <input-files>",
+              " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+              " and options are:",
+              "  --debug                 # Compile with debugging information (default).",
+              "  --release               # Compile without debugging information.",
+              "  --output <file>         # Output result in <outfile>.",
+              "                          # <file> must be an existing directory or a zip file.",
+              "  --lib <file>            # Add <file> as a library resource.",
+              "  --classpath <file>      # Add <file> as a classpath resource.",
+              "  --min-api               # Minimum Android API level compatibility",
+              "  --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."));
+
+  /**
+   * 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.
+   * @return D8 command builder with state set up according to parsed command line.
+   */
+  public static D8Command.Builder parse(String[] args, Origin origin) {
+    return parse(args, origin, D8Command.builder());
+  }
+
+  /**
+   * 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 D8Command.Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return parse(args, origin, D8Command.builder(handler));
+  }
+
+  private static D8Command.Builder parse(String[] args, Origin origin, D8Command.Builder builder) {
+    CompilationMode compilationMode = null;
+    Path outputPath = null;
+    OutputMode outputMode = null;
+    boolean hasDefinedApiLevel = false;
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
+    try {
+      for (int i = 0; i < expandedArgs.length; i++) {
+        String arg = expandedArgs[i].trim();
+        if (arg.length() == 0) {
+          continue;
+        } else if (arg.equals("--help")) {
+          builder.setPrintHelp(true);
+        } else if (arg.equals("--version")) {
+          builder.setPrintVersion(true);
+        } else if (arg.equals("--debug")) {
+          if (compilationMode == CompilationMode.RELEASE) {
+            builder.error(
+                new StringDiagnostic("Cannot compile in both --debug and --release mode.", origin));
+            continue;
+          }
+          compilationMode = CompilationMode.DEBUG;
+        } else if (arg.equals("--release")) {
+          if (compilationMode == CompilationMode.DEBUG) {
+            builder.error(
+                new StringDiagnostic("Cannot compile in both --debug and --release mode.", origin));
+            continue;
+          }
+          compilationMode = CompilationMode.RELEASE;
+        } else if (arg.equals("--file-per-class")) {
+          outputMode = OutputMode.DexFilePerClassFile;
+        } else if (arg.equals("--output")) {
+          String output = expandedArgs[++i];
+          if (outputPath != null) {
+            builder.error(
+                new StringDiagnostic(
+                    "Cannot output both to '" + outputPath.toString() + "' and '" + output + "'",
+                    origin));
+            continue;
+          }
+          outputPath = Paths.get(output);
+        } else if (arg.equals("--lib")) {
+          builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
+        } else if (arg.equals("--classpath")) {
+          builder.addClasspathFiles(Paths.get(expandedArgs[++i]));
+        } else if (arg.equals("--main-dex-list")) {
+          builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+        } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
+          builder.setOptimizeMultidexForLinearAlloc(true);
+        } else if (arg.equals("--min-api")) {
+          String minApiString = expandedArgs[++i];
+          if (hasDefinedApiLevel) {
+            builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin));
+          } else {
+            parseMinApi(builder, minApiString, origin);
+            hasDefinedApiLevel = true;
+          }
+        } else if (arg.equals("--intermediate")) {
+          builder.setIntermediate(true);
+        } else if (arg.equals("--no-desugaring")) {
+          builder.setDisableDesugaring(true);
+        } else {
+          if (arg.startsWith("--")) {
+            builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
+            continue;
+          }
+          builder.addProgramFiles(Paths.get(arg));
+        }
+      }
+      if (compilationMode != null) {
+        builder.setMode(compilationMode);
+      }
+      if (outputMode == null) {
+        outputMode = OutputMode.DexIndexed;
+      }
+      if (outputPath == null) {
+        outputPath = Paths.get(".");
+      }
+      return builder.setOutput(outputPath, outputMode);
+    } catch (CompilationError e) {
+      throw builder.fatalError(e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/DataDirectoryResource.java b/src/main/java/com/android/tools/r8/DataDirectoryResource.java
index 2f421f7..3e860e3 100644
--- a/src/main/java/com/android/tools/r8/DataDirectoryResource.java
+++ b/src/main/java/com/android/tools/r8/DataDirectoryResource.java
@@ -12,6 +12,7 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+@Keep
 public interface DataDirectoryResource extends DataResource {
 
   static DataDirectoryResource fromFile(Path dir, Path file) {
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java
index 189d5eb..aa9c73f 100644
--- a/src/main/java/com/android/tools/r8/DataEntryResource.java
+++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -15,6 +15,7 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+@Keep
 public interface DataEntryResource extends DataResource {
 
   /** Get the bytes of the data entry resource. */
diff --git a/src/main/java/com/android/tools/r8/DataResource.java b/src/main/java/com/android/tools/r8/DataResource.java
index 4609c97..eac8b6c 100644
--- a/src/main/java/com/android/tools/r8/DataResource.java
+++ b/src/main/java/com/android/tools/r8/DataResource.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+@Keep
 public interface DataResource extends Resource {
   char SEPARATOR = '/';
 
diff --git a/src/main/java/com/android/tools/r8/DataResourceConsumer.java b/src/main/java/com/android/tools/r8/DataResourceConsumer.java
index 215ca59..e306b9b 100644
--- a/src/main/java/com/android/tools/r8/DataResourceConsumer.java
+++ b/src/main/java/com/android/tools/r8/DataResourceConsumer.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+@KeepForSubclassing
 public interface DataResourceConsumer {
 
   void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler);
diff --git a/src/main/java/com/android/tools/r8/DataResourceProvider.java b/src/main/java/com/android/tools/r8/DataResourceProvider.java
index e851f0f..5e0a24f 100644
--- a/src/main/java/com/android/tools/r8/DataResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/DataResourceProvider.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+@KeepForSubclassing
 public interface DataResourceProvider {
 
+  @KeepForSubclassing
   interface Visitor {
     void visit(DataDirectoryResource directory);
     void visit(DataEntryResource file);
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
index f1c0fac..311a3dd 100644
--- a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
@@ -29,6 +29,7 @@
  *
  * <p>This consumer receives DEX file content for each Java class-file input.
  */
+@KeepForSubclassing
 public interface DexFilePerClassFileConsumer extends ProgramConsumer {
 
   /**
@@ -57,6 +58,7 @@
   }
 
   /** Forwarding consumer to delegate to an optional existing consumer. */
+  @Keep
   class ForwardingConsumer implements DexFilePerClassFileConsumer {
 
     private static final DexFilePerClassFileConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
@@ -92,6 +94,7 @@
   }
 
   /** Consumer to write program resources to an output. */
+  @Keep
   class ArchiveConsumer extends ForwardingConsumer
       implements DataResourceConsumer, InternalProgramOutputPathConsumer {
     private final OutputBuilder outputBuilder;
@@ -181,6 +184,7 @@
   }
 
   /** Directory consumer to write program resources to a directory. */
+  @Keep
   class DirectoryConsumer extends ForwardingConsumer
       implements DataResourceConsumer, InternalProgramOutputPathConsumer {
     private final OutputBuilder outputBuilder;
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 3bbab64..e802103 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -31,6 +31,7 @@
  * <p>This consumer receives DEX file content using standard indexed-multidex for programs larger
  * than a single DEX file. This is the default consumer for DEX programs.
  */
+@KeepForSubclassing
 public interface DexIndexedConsumer extends ProgramConsumer {
 
   /**
@@ -58,6 +59,7 @@
   }
 
   /** Forwarding consumer to delegate to an optional existing consumer. */
+  @Keep
   class ForwardingConsumer implements DexIndexedConsumer {
 
     private static final DexIndexedConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
@@ -100,6 +102,7 @@
   }
 
   /** Consumer to write program resources to an output. */
+  @Keep
   class ArchiveConsumer extends ForwardingConsumer
       implements DataResourceConsumer, InternalProgramOutputPathConsumer {
     protected final OutputBuilder outputBuilder;
@@ -181,6 +184,7 @@
     }
   }
 
+  @Keep
   class DirectoryConsumer extends ForwardingConsumer
       implements DataResourceConsumer, InternalProgramOutputPathConsumer {
     private final Path directory;
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 2d52ec9..3e299f2 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -31,7 +31,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-public class DexSplitterHelper {
+@Keep
+public final class DexSplitterHelper {
 
   public static void run(
       D8Command command, FeatureClassMapping featureClassMapping, String output, String proguardMap)
diff --git a/src/main/java/com/android/tools/r8/Diagnostic.java b/src/main/java/com/android/tools/r8/Diagnostic.java
index 7896251..3184d54 100644
--- a/src/main/java/com/android/tools/r8/Diagnostic.java
+++ b/src/main/java/com/android/tools/r8/Diagnostic.java
@@ -9,6 +9,7 @@
 /**
  * Interface for all diagnostic message produced by D8 and R8.
  */
+@Keep
 public interface Diagnostic {
 
   /**
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 8212835..74a9a96 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -10,6 +10,7 @@
  *
  * <p>During compilation the warning and info methods will be called.
  */
+@Keep
 public interface DiagnosticsHandler {
 
   /**
diff --git a/src/main/java/com/android/tools/r8/DirectoryClassFileProvider.java b/src/main/java/com/android/tools/r8/DirectoryClassFileProvider.java
index 5d5f751..50aff3f 100644
--- a/src/main/java/com/android/tools/r8/DirectoryClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/DirectoryClassFileProvider.java
@@ -19,6 +19,7 @@
  * Lazy resource provider returning class file resources based
  * on filesystem directory content.
  */
+@Keep
 public final class DirectoryClassFileProvider implements ClassFileResourceProvider {
   private final Path root;
 
diff --git a/src/main/java/com/android/tools/r8/Keep.java b/src/main/java/com/android/tools/r8/Keep.java
new file mode 100644
index 0000000..13ed8c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/Keep.java
@@ -0,0 +1,9 @@
+// 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;
+
+@Keep
+public @interface Keep {
+
+}
diff --git a/src/main/java/com/android/tools/r8/KeepForSubclassing.java b/src/main/java/com/android/tools/r8/KeepForSubclassing.java
new file mode 100644
index 0000000..6e57114
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/KeepForSubclassing.java
@@ -0,0 +1,9 @@
+// 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;
+
+@Keep
+public @interface KeepForSubclassing {
+
+}
diff --git a/src/main/java/com/android/tools/r8/OutputMode.java b/src/main/java/com/android/tools/r8/OutputMode.java
index 0da34b7..bc8a8d8 100644
--- a/src/main/java/com/android/tools/r8/OutputMode.java
+++ b/src/main/java/com/android/tools/r8/OutputMode.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 /** Enumeration of the possible output formats of compilation. */
+@Keep
 public enum OutputMode {
 
   /** Produce DEX files using standard indexed-multidex for programs larger that a single file. */
diff --git a/src/main/java/com/android/tools/r8/ProgramConsumer.java b/src/main/java/com/android/tools/r8/ProgramConsumer.java
index 08d7d7c..b3143a0 100644
--- a/src/main/java/com/android/tools/r8/ProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/ProgramConsumer.java
@@ -6,6 +6,7 @@
 /**
  * Base for all program consumers to allow abstracting which concrete consumer is provided to D8/R8.
  */
+@KeepForSubclassing
 public interface ProgramConsumer {
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ProgramResource.java b/src/main/java/com/android/tools/r8/ProgramResource.java
index cabeabc..ffeb558 100644
--- a/src/main/java/com/android/tools/r8/ProgramResource.java
+++ b/src/main/java/com/android/tools/r8/ProgramResource.java
@@ -20,9 +20,11 @@
  * A resource may optionally include a set describing the class descriptors for each type that is
  * defined by the resource.
  */
+@KeepForSubclassing
 public interface ProgramResource extends Resource {
 
   /** Type of program-format kinds. */
+  @Keep
   enum Kind {
     /** Format-kind for Java class-file resources. */
     CF,
@@ -66,6 +68,7 @@
   Set<String> getClassDescriptors();
 
   /** File-based program resource. */
+  @Keep
   class FileResource implements ProgramResource {
     private final Origin origin;
     private final Kind kind;
@@ -105,6 +108,7 @@
   }
 
   /** Byte-content based program resource. */
+  @Keep
   class ByteResource implements ProgramResource {
     private final Origin origin;
     private final Kind kind;
diff --git a/src/main/java/com/android/tools/r8/ProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
index 76d340c..d4b8a87 100644
--- a/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
@@ -6,6 +6,7 @@
 import java.util.Collection;
 
 /** Program resource provider. */
+@KeepForSubclassing
 public interface ProgramResourceProvider {
 
   Collection<ProgramResource> getProgramResources() throws ResourceException;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5d29d90..077e220 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -103,6 +103,7 @@
  * them to DEX bytecode, using {@code androidJar} as the reference of the system runtime library,
  * and then writes the result to the directory or zip archive specified by {@code outputPath}.
  */
+@Keep
 public class R8 {
 
   private final Timing timing = new Timing("R8");
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 573f6df..86ff5a5 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.Reporter;
@@ -46,13 +45,15 @@
  *     .build();
  * </pre>
  */
-public class R8Command extends BaseCompilerCommand {
+@Keep
+public final class R8Command extends BaseCompilerCommand {
 
   /**
    * Builder for constructing a R8Command.
    *
    * <p>A builder is obtained by calling {@link R8Command#builder}.
    */
+  @Keep
   public static class Builder extends BaseCompilerCommand.Builder<R8Command, Builder> {
 
     private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
@@ -421,38 +422,7 @@
     }
   }
 
-  // Internal state to verify parsing properties not enforced by the builder.
-  private static class ParseState {
-    CompilationMode mode = null;
-    OutputMode outputMode = null;
-    Path outputPath = null;
-    boolean hasDefinedApiLevel = false;
-  }
-
-  static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-      "Usage: r8 [options] <input-files>",
-      " where <input-files> are any combination of dex, class, zip, jar, or apk files",
-      " and options are:",
-      "  --release                # Compile without debugging information (default).",
-      "  --debug                  # Compile with debugging information.",
-      // TODO(b/65390962): Add help for output-mode flags once the CF backend is complete.
-      //"  --dex                    # Compile program to DEX file format (default).",
-      //"  --classfile              # Compile program to Java classfile format.",
-      "  --output <file>          # Output result in <file>.",
-      "                           # <file> must be an existing directory or a zip file.",
-      "  --lib <file>             # Add <file> as a library resource.",
-      "  --min-api                # Minimum Android API level compatibility.",
-      "  --pg-conf <file>         # Proguard configuration <file>.",
-      "  --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.",
-      "  --main-dex-list-output <file>  # Output the full main-dex list in <file>.",
-      "  --version                # Print the version of r8.",
-      "  --help                   # Print this message."));
+  static final String USAGE_MESSAGE = R8CommandParser.USAGE_MESSAGE;
 
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
   private final StringConsumer mainDexListConsumer;
@@ -488,7 +458,7 @@
    * @return R8 command builder with state set up according to parsed command line.
    */
   public static Builder parse(String[] args, Origin origin) {
-    return parse(args, origin, builder());
+    return R8CommandParser.parse(args, origin);
   }
 
   /**
@@ -502,102 +472,7 @@
    * @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(args, origin, builder(handler));
-  }
-
-  private static Builder parse(String[] args, Origin origin, Builder builder) {
-    ParseState state = new ParseState();
-    parse(args, origin, builder, state);
-    if (state.mode != null) {
-      builder.setMode(state.mode);
-    }
-    Path outputPath = state.outputPath != null ? state.outputPath : Paths.get(".");
-    OutputMode outputMode = state.outputMode != null ? state.outputMode : OutputMode.DexIndexed;
-    builder.setOutput(outputPath, outputMode);
-    return builder;
-  }
-
-  private static ParseState parse(
-      String[] args,
-      Origin argsOrigin,
-      Builder builder,
-      ParseState state) {
-    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder.getReporter());
-    for (int i = 0; i < expandedArgs.length; i++) {
-      String arg = expandedArgs[i].trim();
-      if (arg.length() == 0) {
-        continue;
-      } else if (arg.equals("--help")) {
-        builder.setPrintHelp(true);
-      } else if (arg.equals("--version")) {
-        builder.setPrintVersion(true);
-      } else if (arg.equals("--debug")) {
-        if (state.mode == CompilationMode.RELEASE) {
-          builder.getReporter().error(new StringDiagnostic(
-              "Cannot compile in both --debug and --release mode.", argsOrigin));
-        }
-        state.mode = CompilationMode.DEBUG;
-      } else if (arg.equals("--release")) {
-        if (state.mode == CompilationMode.DEBUG) {
-          builder.getReporter().error(new StringDiagnostic(
-              "Cannot compile in both --debug and --release mode.", argsOrigin));
-        }
-        state.mode = CompilationMode.RELEASE;
-      } else if (arg.equals("--dex")) {
-        if (state.outputMode == OutputMode.ClassFile) {
-          builder.getReporter().error(new StringDiagnostic(
-              "Cannot compile in both --dex and --classfile output mode.", argsOrigin));
-        }
-        state.outputMode = OutputMode.DexIndexed;
-      } else if (arg.equals("--classfile")) {
-        if (state.outputMode == OutputMode.DexIndexed) {
-          builder.getReporter().error(new StringDiagnostic(
-              "Cannot compile in both --dex and --classfile output mode.", argsOrigin));
-        }
-        state.outputMode = OutputMode.ClassFile;
-      } else if (arg.equals("--output")) {
-        String outputPath = expandedArgs[++i];
-        if (state.outputPath != null) {
-          builder.getReporter().error(new StringDiagnostic(
-              "Cannot output both to '"
-                  + state.outputPath.toString()
-                  + "' and '"
-                  + outputPath
-                  + "'",
-              argsOrigin));
-        }
-        state.outputPath = Paths.get(outputPath);
-      } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
-      } else if (arg.equals("--min-api")) {
-        state.hasDefinedApiLevel =
-            parseMinApi(builder, expandedArgs[++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(expandedArgs[++i]));
-      } else if (arg.equals("--main-dex-list")) {
-        builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
-      } else if (arg.equals("--main-dex-list-output")) {
-        builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
-      } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
-        builder.setOptimizeMultidexForLinearAlloc(true);
-      } else if (arg.equals("--pg-conf")) {
-        builder.addProguardConfigurationFiles(Paths.get(expandedArgs[++i]));
-      } else if (arg.equals("--pg-map-output")) {
-        builder.setProguardMapOutputPath(Paths.get(expandedArgs[++i]));
-      } else {
-        if (arg.startsWith("--")) {
-          builder.getReporter().error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
-        }
-        builder.addProgramFiles(Paths.get(arg));
-      }
-    }
-    return state;
+    return R8CommandParser.parse(args, origin, handler);
   }
 
   private R8Command(
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
new file mode 100644
index 0000000..3d33037
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -0,0 +1,186 @@
+// 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 com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class R8CommandParser extends BaseCompilerCommandParser {
+
+  public static void main(String[] args) throws CompilationFailedException {
+    R8Command command = parse(args, Origin.root()).build();
+    if (command.isPrintHelp()) {
+      System.out.println(USAGE_MESSAGE);
+      System.exit(1);
+    }
+    R8.run(command);
+  }
+
+  // Internal state to verify parsing properties not enforced by the builder.
+  private static class ParseState {
+    CompilationMode mode = null;
+    OutputMode outputMode = null;
+    Path outputPath = null;
+    boolean hasDefinedApiLevel = false;
+  }
+
+  static final String USAGE_MESSAGE =
+      String.join(
+          "\n",
+          Arrays.asList(
+              "Usage: r8 [options] <input-files>",
+              " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+              " and options are:",
+              "  --release                # Compile without debugging information (default).",
+              "  --debug                  # Compile with debugging information.",
+              // TODO(b/65390962): Add help for output-mode flags once the CF backend is complete.
+              // "  --dex                    # Compile program to DEX file format (default).",
+              // "  --classfile              # Compile program to Java classfile format.",
+              "  --output <file>          # Output result in <file>.",
+              "                           # <file> must be an existing directory or a zip file.",
+              "  --lib <file>             # Add <file> as a library resource.",
+              "  --min-api                # Minimum Android API level compatibility.",
+              "  --pg-conf <file>         # Proguard configuration <file>.",
+              "  --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.",
+              "  --main-dex-list-output <file>  # Output the full main-dex list in <file>.",
+              "  --version                # Print the version of r8.",
+              "  --help                   # Print this message."));
+  /**
+   * Parse the R8 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.
+   * @return R8 command builder with state set up according to parsed command line.
+   */
+  public static R8Command.Builder parse(String[] args, Origin origin) {
+    return new R8CommandParser().parse(args, origin, R8Command.builder());
+  }
+
+  /**
+   * Parse the R8 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 R8 command builder with state set up according to parsed command line.
+   */
+  public static R8Command.Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return new R8CommandParser().parse(args, origin, R8Command.builder(handler));
+  }
+
+  private R8Command.Builder parse(String[] args, Origin origin, R8Command.Builder builder) {
+    ParseState state = new ParseState();
+    parse(args, origin, builder, state);
+    if (state.mode != null) {
+      builder.setMode(state.mode);
+    }
+    Path outputPath = state.outputPath != null ? state.outputPath : Paths.get(".");
+    OutputMode outputMode = state.outputMode != null ? state.outputMode : OutputMode.DexIndexed;
+    builder.setOutput(outputPath, outputMode);
+    return builder;
+  }
+
+  private void parse(
+      String[] args, Origin argsOrigin, R8Command.Builder builder, ParseState state) {
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
+    for (int i = 0; i < expandedArgs.length; i++) {
+      String arg = expandedArgs[i].trim();
+      if (arg.length() == 0) {
+        continue;
+      } else if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--debug")) {
+        if (state.mode == CompilationMode.RELEASE) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot compile in both --debug and --release mode.", argsOrigin));
+        }
+        state.mode = CompilationMode.DEBUG;
+      } else if (arg.equals("--release")) {
+        if (state.mode == CompilationMode.DEBUG) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot compile in both --debug and --release mode.", argsOrigin));
+        }
+        state.mode = CompilationMode.RELEASE;
+      } else if (arg.equals("--dex")) {
+        if (state.outputMode == OutputMode.ClassFile) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot compile in both --dex and --classfile output mode.", argsOrigin));
+        }
+        state.outputMode = OutputMode.DexIndexed;
+      } else if (arg.equals("--classfile")) {
+        if (state.outputMode == OutputMode.DexIndexed) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot compile in both --dex and --classfile output mode.", argsOrigin));
+        }
+        state.outputMode = OutputMode.ClassFile;
+      } else if (arg.equals("--output")) {
+        String outputPath = expandedArgs[++i];
+        if (state.outputPath != null) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot output both to '"
+                      + state.outputPath.toString()
+                      + "' and '"
+                      + outputPath
+                      + "'",
+                  argsOrigin));
+        }
+        state.outputPath = Paths.get(outputPath);
+      } else if (arg.equals("--lib")) {
+        builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
+      } else if (arg.equals("--min-api")) {
+        String minApiString = expandedArgs[++i];
+        if (state.hasDefinedApiLevel) {
+          builder.error(new StringDiagnostic("Cannot set multiple --min-api options", argsOrigin));
+        } else {
+          parseMinApi(builder, minApiString, argsOrigin);
+          state.hasDefinedApiLevel = true;
+        }
+      } 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(expandedArgs[++i]));
+      } else if (arg.equals("--main-dex-list")) {
+        builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+      } else if (arg.equals("--main-dex-list-output")) {
+        builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
+      } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
+        builder.setOptimizeMultidexForLinearAlloc(true);
+      } else if (arg.equals("--pg-conf")) {
+        builder.addProguardConfigurationFiles(Paths.get(expandedArgs[++i]));
+      } else if (arg.equals("--pg-map-output")) {
+        builder.setProguardMapOutputPath(Paths.get(expandedArgs[++i]));
+      } else {
+        if (arg.startsWith("--")) {
+          builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
+        }
+        builder.addProgramFiles(Paths.get(arg));
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/Resource.java b/src/main/java/com/android/tools/r8/Resource.java
index b64fe01..50c50cc 100644
--- a/src/main/java/com/android/tools/r8/Resource.java
+++ b/src/main/java/com/android/tools/r8/Resource.java
@@ -19,6 +19,7 @@
  * The D8/R8 compilers uses default implementations for various file-system resources, but the
  * client is free to provide their own.
  */
+@KeepForSubclassing
 public interface Resource {
   /**
    * Get the origin of the resource.
diff --git a/src/main/java/com/android/tools/r8/ResourceException.java b/src/main/java/com/android/tools/r8/ResourceException.java
index 3af7efa..2726b92 100644
--- a/src/main/java/com/android/tools/r8/ResourceException.java
+++ b/src/main/java/com/android/tools/r8/ResourceException.java
@@ -11,6 +11,7 @@
  * For example, this is the expected exception that must be thrown if a resource fails to produce
  * its content. See {@link ProgramResource#getByteStream()} and {@link StringResource#getString()}.
  */
+@Keep
 public class ResourceException extends Exception {
 
   private final Origin origin;
diff --git a/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java b/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
index 0a6c84a..37ae662 100644
--- a/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
+++ b/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.origin;
 
+import com.android.tools.r8.Keep;
+
 /**
  * Origin representing an entry in an archive.
  */
+@Keep
 public class ArchiveEntryOrigin extends Origin {
 
   final String entryName;
diff --git a/src/main/java/com/android/tools/r8/origin/Origin.java b/src/main/java/com/android/tools/r8/origin/Origin.java
index c0bc2ec..3a8aa97 100644
--- a/src/main/java/com/android/tools/r8/origin/Origin.java
+++ b/src/main/java/com/android/tools/r8/origin/Origin.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.origin;
 
+import com.android.tools.r8.KeepForSubclassing;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -22,6 +23,7 @@
  * Origin.unknown()}. The former is the parent of any file path, while the latter is an unknown
  * origin (e.g., for generated resources of raw bytes).
  */
+@KeepForSubclassing
 public abstract class Origin implements Comparable<Origin> {
 
   private static final Origin ROOT =
diff --git a/src/main/java/com/android/tools/r8/origin/PathOrigin.java b/src/main/java/com/android/tools/r8/origin/PathOrigin.java
index 95ee46f..0ff8080 100644
--- a/src/main/java/com/android/tools/r8/origin/PathOrigin.java
+++ b/src/main/java/com/android/tools/r8/origin/PathOrigin.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.origin;
 
+import com.android.tools.r8.Keep;
 import java.nio.file.Path;
 
 /**
  * Path component in an origin description.
  */
+@Keep
 public class PathOrigin extends Origin {
 
   private final Path path;
diff --git a/src/main/java/com/android/tools/r8/position/Position.java b/src/main/java/com/android/tools/r8/position/Position.java
index 902fad5..618fd51 100644
--- a/src/main/java/com/android/tools/r8/position/Position.java
+++ b/src/main/java/com/android/tools/r8/position/Position.java
@@ -4,10 +4,13 @@
 
 package com.android.tools.r8.position;
 
+import com.android.tools.r8.Keep;
+
 /**
  * Represent a position in a resource, it can for example be line in a text file of the byte offset
  * in a binary stream.
  */
+@Keep
 public interface Position {
 
   /**
diff --git a/src/main/java/com/android/tools/r8/position/TextPosition.java b/src/main/java/com/android/tools/r8/position/TextPosition.java
index 9136fa7..0dd4090 100644
--- a/src/main/java/com/android/tools/r8/position/TextPosition.java
+++ b/src/main/java/com/android/tools/r8/position/TextPosition.java
@@ -3,10 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.position;
 
+import com.android.tools.r8.Keep;
+
 /**
  * A {@link Position} in a text file determined by line and column.
  * Line and column numbers start at 1.
  */
+@Keep
 public class TextPosition implements Position {
 
   /**
diff --git a/src/main/java/com/android/tools/r8/position/TextRange.java b/src/main/java/com/android/tools/r8/position/TextRange.java
index 5f35aad..0e642d8 100644
--- a/src/main/java/com/android/tools/r8/position/TextRange.java
+++ b/src/main/java/com/android/tools/r8/position/TextRange.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.position;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public class TextRange implements Position {
   private final TextPosition start;
   private final TextPosition end;
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 873013e..8e84935 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.Keep;
 import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
 import com.android.tools.r8.origin.PathOrigin;
@@ -38,6 +39,7 @@
  * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
  * placement tool.
  */
+@Keep
 public class FeatureClassMapping {
 
   HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
@@ -198,6 +200,7 @@
         "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line);
   }
 
+  @Keep
   public static class FeatureMappingException extends Exception {
     FeatureMappingException(String message) {
       super(message);
diff --git a/src/main/java/com/android/tools/r8/utils/FlagFile.java b/src/main/java/com/android/tools/r8/utils/FlagFile.java
index 4791217..5f5003f 100644
--- a/src/main/java/com/android/tools/r8/utils/FlagFile.java
+++ b/src/main/java/com/android/tools/r8/utils/FlagFile.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.BaseCommand;
 import com.android.tools.r8.origin.Origin;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -28,7 +29,7 @@
     }
   }
 
-  public static String[] expandFlagFiles(String[] args, Reporter reporter) {
+  public static String[] expandFlagFiles(String[] args, BaseCommand.Builder builder) {
     List<String> flags = new ArrayList<>(args.length);
     for (String arg : args) {
       if (arg.startsWith("@")) {
@@ -37,7 +38,7 @@
           flags.addAll(Files.readAllLines(flagFilePath));
         } catch (IOException e) {
           Origin origin = new FlagFileOrigin(flagFilePath);
-          reporter.error(new ExceptionDiagnostic(e, origin));
+          builder.error(new ExceptionDiagnostic(e, origin));
         }
       } else {
         flags.add(arg);
diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
index b7cd855..06ab8a9 100644
--- a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
@@ -4,9 +4,11 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 
+@Keep
 public class StringDiagnostic implements Diagnostic {
 
   private final Origin origin;
diff --git a/src/main/keep.txt b/src/main/keep.txt
new file mode 100644
index 0000000..2d74e47
--- /dev/null
+++ b/src/main/keep.txt
@@ -0,0 +1,6 @@
+# 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.
+
+-keep @com.android.tools.r8.Keep class * { public *; }
+-keep @com.android.tools.r8.KeepForSubclassing class * { public *; protected *; }
diff --git a/tests/d8_api_usage_sample.jar b/tests/d8_api_usage_sample.jar
index f901b9d..7d503a5 100644
--- a/tests/d8_api_usage_sample.jar
+++ b/tests/d8_api_usage_sample.jar
Binary files differ
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
index f901b9d..7d503a5 100644
--- a/tests/r8_api_usage_sample.jar
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/tools/build_r8lib.py b/tools/build_r8lib.py
new file mode 100755
index 0000000..abaa619
--- /dev/null
+++ b/tools/build_r8lib.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# 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.
+
+'''
+Build r8lib.jar using src/main/keep.txt and test that d8_api_usage_sample.jar
+works with the minified R8.
+'''
+
+import argparse
+import os
+import subprocess
+import toolhelper
+import utils
+
+parser = argparse.ArgumentParser(description=__doc__.strip(),
+                                 formatter_class=argparse.RawTextHelpFormatter)
+
+SAMPLE_JAR = os.path.join(utils.REPO_ROOT, 'tests/d8_api_usage_sample.jar')
+KEEP_RULES = os.path.join(utils.REPO_ROOT, 'src/main/keep.txt')
+R8LIB_JAR = os.path.join(utils.LIBS, 'r8lib.jar')
+R8LIB_MAP_FILE = os.path.join(utils.LIBS, 'r8lib-map.txt')
+
+API_LEVEL = 26
+ANDROID_JAR = 'third_party/android_jar/lib-v%s/android.jar' % API_LEVEL
+
+
+def build_r8lib():
+  toolhelper.run(
+      'r8',
+      ('--release',
+       '--classfile',
+       '--lib', utils.RT_JAR,
+       utils.R8_JAR,
+       '--output', R8LIB_JAR,
+       '--pg-conf', KEEP_RULES,
+       '--pg-map-output', R8LIB_MAP_FILE))
+
+
+def test_d8sample():
+  with utils.TempDir() as path:
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+            'com.android.tools.apiusagesample.D8ApiUsageSample',
+            '--output', path,
+            '--min-api', str(API_LEVEL),
+            '--lib', ANDROID_JAR,
+            '--classpath', utils.R8_JAR,
+            '--main-dex-list', '/dev/null',
+            os.path.join(utils.BUILD, 'test/examples/hello.jar')]
+    utils.PrintCmd(args)
+    subprocess.check_call(args)
+
+
+def test_r8command():
+  with utils.TempDir() as path:
+    # SAMPLE_JAR and R8LIB_JAR should not have any classes in common, since e.g.
+    # R8CommandParser should have been minified in R8LIB_JAR.
+    # Just in case R8CommandParser is also present in R8LIB_JAR, we put
+    # SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+            'com.android.tools.r8.R8CommandParser',
+            '--output', path + "/output.zip",
+            '--min-api', str(API_LEVEL),
+            '--lib', ANDROID_JAR,
+            '--main-dex-list', '/dev/null',
+            os.path.join(utils.BUILD, 'test/examples/hello.jar')]
+    utils.PrintCmd(args)
+    subprocess.check_call(args)
+
+
+def test_r8cfcommand():
+  with utils.TempDir() as path:
+    # SAMPLE_JAR and R8LIB_JAR should not have any classes in common, since e.g.
+    # R8CommandParser should have been minified in R8LIB_JAR.
+    # Just in case R8CommandParser is also present in R8LIB_JAR, we put
+    # SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+            'com.android.tools.r8.R8CommandParser',
+            '--classfile',
+            '--output', path + "/output.jar",
+            '--lib', utils.RT_JAR,
+            os.path.join(utils.BUILD, 'test/examples/hello.jar')]
+    utils.PrintCmd(args)
+    subprocess.check_call(args)
+
+
+def main():
+  # Handle --help
+  parser.parse_args()
+
+  build_r8lib()
+  test_d8sample()
+  test_r8command()
+  test_r8cfcommand()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
index 787c1e1..8f440dd 100755
--- a/tools/minify_tool.py
+++ b/tools/minify_tool.py
@@ -27,7 +27,6 @@
 MANIFEST_PATH = 'META-INF/MANIFEST.MF'
 MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n'
 MANIFEST_PATTERN = r'Main-Class:\s*(\S+)'
-RT = os.path.join(utils.REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
 
 parser = argparse.ArgumentParser(description=__doc__.strip(),
                                  formatter_class=argparse.RawTextHelpFormatter)
@@ -38,7 +37,7 @@
     '-o', '--output-jar',
     help='Path to output JAR (default: build/libs/<MainClass>-min.jar)')
 parser.add_argument(
-    '-l', '--lib', default=RT,
+    '-l', '--lib', default=utils.RT_JAR,
     help='Path to rt.jar to use instead of OpenJDK 1.8')
 parser.add_argument(
     '-m', '--mainclass',
@@ -85,8 +84,8 @@
           'No --mainclass specified and no Main-Class in input JAR manifest.')
     return mo.group(1)
 
-def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None, lib=RT,
-                debug=True, build=True, benchmark_name=None):
+def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None,
+                lib=utils.RT_JAR, debug=True, build=True, benchmark_name=None):
   if output_jar is None:
     output_jar = generate_output_name(input_jar, mainclass)
   with utils.TempDir() as path:
diff --git a/tools/utils.py b/tools/utils.py
index 9f6959f..3aab572 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -35,6 +35,7 @@
 COMPATPROGUARD_JAR = os.path.join(LIBS, 'compatproguard.jar')
 MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
 GENERATED_LICENSE = os.path.join(GENERATED_LICENSE_DIR, 'LICENSE')
+RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
 
 def PrintCmd(s):
   if type(s) is list: