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();
+ }
+}