Support "$JDK_HOME" as argument to --lib for command line tools

When running d8 and r8 through the CLI allow the argument to --lib to
be a directory of a JDK (the JAVA_HOME location for that JDK). In that
case add the JDK runtime classes as library classes.

This is the only way to get the runtime classes for JDK 9+, as they do
not ship with a rt.jar anymore.

Bug: 143335486
Change-Id: Idacdfc9ba85cef70a50255d064a4bdb9ff115288
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 054db58..2915114 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -4,14 +4,19 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 public class BaseCompilerCommandParser {
 
   static void parseMinApi(BaseCompilerCommand.Builder builder, String minApiString, Origin origin) {
     int minApi;
     try {
-      minApi = Integer.valueOf(minApiString);
+      minApi = Integer.parseInt(minApiString);
     } catch (NumberFormatException e) {
       builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin));
       return;
@@ -22,4 +27,40 @@
     }
     builder.setMinApiLevel(minApi);
   }
+
+  /**
+   * This method must match the lookup in
+   * {@link com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
+   */
+  private static boolean isJdkHome(Path home) {
+    Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+    if (Files.exists(jrtFsJar)) {
+      return true;
+    }
+    // JDK has rt.jar in jre/lib/rt.jar.
+    Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return true;
+    }
+    // JRE has rt.jar in lib/rt.jar.
+    rtJar = home.resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return true;
+    }
+    return false;
+  }
+
+  static void addLibraryArgument(BaseCommand.Builder builder, Origin origin, String arg) {
+    Path path = Paths.get(arg);
+    if (isJdkHome(path)) {
+      try {
+        builder
+            .addLibraryResourceProvider(JdkClassFileProvider.fromJdkHome(path));
+      } catch (IOException e) {
+        builder.error(new ExceptionDiagnostic(e, origin));
+      }
+    } else {
+      builder.addLibraryFiles(path);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 3985cd7..792f8b9 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -115,7 +115,7 @@
               "  --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.",
+              "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
               "  --classpath <file>      # Add <file> as a classpath resource.",
               "  --min-api <number>      # Minimum Android API level compatibility, default: "
                   + AndroidApiLevel.getDefault().getLevel()
@@ -209,7 +209,7 @@
         }
         outputPath = Paths.get(nextArg);
       } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(nextArg));
+        addLibraryArgument(builder, origin, nextArg);
       } else if (arg.equals("--classpath")) {
         Path file = Paths.get(nextArg);
         try {
diff --git a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
index 5e35cf0..9fcbd00 100644
--- a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
@@ -30,7 +30,9 @@
  * <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 JDK content.
  *
- * <p>Currently only JDK's of version 9 or higher with a lib/jrt-fs.jar file present is supported.
+ * <p>This supports all JDK versions. For JDK's of version 8 or lower classes in
+ * <code>lib/rt.jar</code> will be loaded. JDK's of version 9 or higher system module classes will
+ * be loaded using <code>lib/jrt-fs.jar/<code>.
  */
 @Keep
 public class JdkClassFileProvider implements ClassFileResourceProvider, Closeable {
@@ -65,11 +67,11 @@
   }
 
   /**
-   * Creates a lazy class-file program-resource provider for the runtime of a JDK.
+   * Creates a lazy class-file program-resource provider for a JDK.
    *
-   * <p>This will load the program-resources form the system modules for JDK of version 9 or higher.
+   * <p>This will load the program resources form the system modules for JDK of version 9 or higher.
    *
-   * <p>This will load <code>rt.jar</code> for JDK of version 8 and lower.
+   * <p>This will load <code>lib/rt.jar</code> for JDK of version 8 and lower.
    *
    * @param home Location of the JDK to read the program-resources from.
    */
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index fd4f570..b0acdb6 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -58,7 +58,7 @@
               "  --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.",
+              "  --lib <file|jdk-home>    # Add <file|jdk-home> as a library resource.",
               "  --classpath <file>       # Add <file> as a classpath resource.",
               "  --min-api <number>       # Minimum Android API level compatibility, default: "
                   + AndroidApiLevel.getDefault().getLevel()
@@ -178,7 +178,7 @@
         }
         state.outputPath = Paths.get(nextArg);
       } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(nextArg));
+        addLibraryArgument(builder, argsOrigin, nextArg);
       } else if (arg.equals("--classpath")) {
         builder.addClasspathFiles(Paths.get(nextArg));
       } else if (arg.equals("--min-api")) {
diff --git a/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java b/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java
new file mode 100644
index 0000000..553452e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, 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.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class D8CommandTestJdkLib extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public D8CommandTestJdkLib(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void jdkLib() throws Throwable {
+    CfRuntime runtime = parameters.getRuntime().asCf();
+    D8Command command = parse("--lib", runtime.getJavaHome().toString());
+    AndroidApp inputApp = ToolHelper.getApp(command);
+    assertEquals(1, inputApp.getLibraryResourceProviders().size());
+    if (runtime.isNewerThan(CfVm.JDK8)) {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof JdkClassFileProvider);
+    } else {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof ArchiveClassFileProvider);
+    }
+  }
+
+  private D8Command parse(String... args) throws CompilationFailedException {
+    return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java b/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java
new file mode 100644
index 0000000..f5cd550
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, 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.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class R8CommandTestJdkLib extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public R8CommandTestJdkLib(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void jdkLib() throws Throwable {
+    CfRuntime runtime = parameters.getRuntime().asCf();
+    R8Command command = parse("--lib", runtime.getJavaHome().toString());
+    AndroidApp inputApp = ToolHelper.getApp(command);
+    assertEquals(1, inputApp.getLibraryResourceProviders().size());
+    if (runtime.isNewerThan(CfVm.JDK8)) {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof JdkClassFileProvider);
+    } else {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof ArchiveClassFileProvider);
+    }
+  }
+
+  private R8Command parse(String... args) throws CompilationFailedException {
+    return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+  }
+}