|  | // Copyright (c) 2019, 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.TestBase.Backend; | 
|  | import com.android.tools.r8.ToolHelper.DexVm; | 
|  | import com.android.tools.r8.errors.Unimplemented; | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.utils.AndroidApiLevel; | 
|  | import com.android.tools.r8.utils.ListUtils; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableList.Builder; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import java.nio.file.Path; | 
|  | import java.nio.file.Paths; | 
|  | import java.util.Arrays; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Objects; | 
|  |  | 
|  | // Base class for the runtime structure in the test parameters. | 
|  | public abstract class TestRuntime { | 
|  |  | 
|  | // Enum describing the possible/supported CF runtimes. | 
|  | public enum CfVm { | 
|  | JDK8("jdk8", 52), | 
|  | JDK9("jdk9", 53), | 
|  | JDK10("jdk10", 54), | 
|  | JDK11("jdk11", 55), | 
|  | JDK17("jdk17", 61), | 
|  | ; | 
|  |  | 
|  | private final String name; | 
|  | private final int classfileVersion; | 
|  |  | 
|  | CfVm(String name, int classfileVersion) { | 
|  | this.name = name; | 
|  | this.classfileVersion = classfileVersion; | 
|  | } | 
|  |  | 
|  | public int getClassfileVersion() { | 
|  | return classfileVersion; | 
|  | } | 
|  |  | 
|  | public static CfVm first() { | 
|  | return JDK8; | 
|  | } | 
|  |  | 
|  | public static CfVm last() { | 
|  | return JDK11; | 
|  | } | 
|  |  | 
|  | public boolean lessThan(CfVm other) { | 
|  | return this.ordinal() < other.ordinal(); | 
|  | } | 
|  |  | 
|  | public boolean lessThanOrEqual(CfVm other) { | 
|  | return this.ordinal() <= other.ordinal(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return name; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final Path JDK8_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk8"); | 
|  | private static final Path JDK9_PATH = | 
|  | Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "openjdk-9.0.4"); | 
|  | private static final Path JDK11_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-11"); | 
|  | private static final Path JDK17_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-17"); | 
|  | private static final Map<CfVm, Path> jdkPaths = | 
|  | ImmutableMap.of( | 
|  | CfVm.JDK8, JDK8_PATH, | 
|  | CfVm.JDK9, JDK9_PATH, | 
|  | CfVm.JDK11, JDK11_PATH, | 
|  | CfVm.JDK17, JDK17_PATH); | 
|  |  | 
|  | public static CfRuntime getCheckedInJdk(CfVm vm) { | 
|  | if (vm == CfVm.JDK8) { | 
|  | return getCheckedInJdk8(); | 
|  | } | 
|  | return new CfRuntime(vm, getCheckedInJdkHome(vm)); | 
|  | } | 
|  |  | 
|  | public static CfRuntime getCheckedInJdk8() { | 
|  | Path home; | 
|  | if (ToolHelper.isLinux()) { | 
|  | home = JDK8_PATH.resolve("linux-x86"); | 
|  | } else if (ToolHelper.isMac()) { | 
|  | home = JDK8_PATH.resolve("darwin-x86"); | 
|  | } else { | 
|  | assert ToolHelper.isWindows(); | 
|  | return null; | 
|  | } | 
|  | return new CfRuntime(CfVm.JDK8, home); | 
|  | } | 
|  |  | 
|  | private static Path getCheckedInJdkHome(CfVm vm) { | 
|  | Path path = jdkPaths.get(vm); | 
|  | assert path != null : "No JDK path defined for " + vm; | 
|  | if (ToolHelper.isLinux()) { | 
|  | return path.resolve("linux"); | 
|  | } else if (ToolHelper.isMac()) { | 
|  | return vm.lessThanOrEqual(CfVm.JDK9) | 
|  | ? path.resolve("osx") | 
|  | : path.resolve("osx/Contents/Home"); | 
|  | } else { | 
|  | assert ToolHelper.isWindows(); | 
|  | return path.resolve("windows"); | 
|  | } | 
|  | } | 
|  |  | 
|  | public static CfRuntime getCheckedInJdk9() { | 
|  | return new CfRuntime(CfVm.JDK9, getCheckedInJdkHome(CfVm.JDK9)); | 
|  | } | 
|  |  | 
|  | public static CfRuntime getCheckedInJdk11() { | 
|  | return new CfRuntime(CfVm.JDK11, getCheckedInJdkHome(CfVm.JDK11)); | 
|  | } | 
|  |  | 
|  | // TODO(b/169692487): Add this to 'getCheckedInCfRuntimes' when we start having support for JDK17. | 
|  | public static CfRuntime getCheckedInJdk17() { | 
|  | return new CfRuntime(CfVm.JDK17, getCheckedInJdkHome(CfVm.JDK17)); | 
|  | } | 
|  |  | 
|  | public static List<CfRuntime> getCheckedInCfRuntimes() { | 
|  | CfRuntime[] jdks = | 
|  | new CfRuntime[] {getCheckedInJdk8(), getCheckedInJdk9(), getCheckedInJdk11()}; | 
|  | Builder<CfRuntime> builder = ImmutableList.builder(); | 
|  | for (CfRuntime jdk : jdks) { | 
|  | if (jdk != null) { | 
|  | builder.add(jdk); | 
|  | } | 
|  | } | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | private static List<DexRuntime> getCheckedInDexRuntimes() { | 
|  | if (ToolHelper.isLinux()) { | 
|  | return ListUtils.map(Arrays.asList(DexVm.Version.values()), DexRuntime::new); | 
|  | } | 
|  | assert ToolHelper.isMac() || ToolHelper.isWindows(); | 
|  | return ImmutableList.of(); | 
|  | } | 
|  |  | 
|  | // For compatibility with old tests not specifying a Java runtime | 
|  | @Deprecated | 
|  | public static TestRuntime getDefaultJavaRuntime() { | 
|  | return getCheckedInJdk9(); | 
|  | } | 
|  |  | 
|  | public static List<TestRuntime> getCheckedInRuntimes() { | 
|  | return ImmutableList.<TestRuntime>builder() | 
|  | .addAll(getCheckedInCfRuntimes()) | 
|  | .addAll(getCheckedInDexRuntimes()) | 
|  | .build(); | 
|  | } | 
|  |  | 
|  | public static CfRuntime getSystemRuntime() { | 
|  | String version = System.getProperty("java.version"); | 
|  | String home = System.getProperty("java.home"); | 
|  | if (version == null || version.isEmpty() || home == null || home.isEmpty()) { | 
|  | throw new Unimplemented("Unable to create a system runtime"); | 
|  | } | 
|  | if (version.startsWith("1.8.")) { | 
|  | return new CfRuntime(CfVm.JDK8, Paths.get(home)); | 
|  | } | 
|  | if (version.startsWith("9.")) { | 
|  | return new CfRuntime(CfVm.JDK9, Paths.get(home)); | 
|  | } | 
|  | if (version.startsWith("11.")) { | 
|  | return new CfRuntime(CfVm.JDK11, Paths.get(home)); | 
|  | } | 
|  | throw new Unimplemented("No support for JDK version: " + version); | 
|  | } | 
|  |  | 
|  | public static class NoneRuntime extends TestRuntime { | 
|  |  | 
|  | private static final String NAME = "none"; | 
|  | private static final NoneRuntime INSTANCE = new NoneRuntime(); | 
|  |  | 
|  | private NoneRuntime() {} | 
|  |  | 
|  | public static NoneRuntime getInstance() { | 
|  | return INSTANCE; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String name() { | 
|  | return NAME; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public AndroidApiLevel maxSupportedApiLevel() { | 
|  | // The "none" runtime trivally supports all api levels as nothing is run. | 
|  | return AndroidApiLevel.LATEST; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return NAME; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object other) { | 
|  | return this == other; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return System.identityHashCode(this); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Wrapper for the DEX runtimes. | 
|  | public static class DexRuntime extends TestRuntime { | 
|  |  | 
|  | private final DexVm vm; | 
|  |  | 
|  | public DexRuntime(DexVm.Version version) { | 
|  | this(DexVm.fromVersion(version)); | 
|  | } | 
|  |  | 
|  | public DexRuntime(DexVm vm) { | 
|  | assert vm != null; | 
|  | this.vm = vm; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String name() { | 
|  | return "dex-" + vm.getVersion().toString(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isDex() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexRuntime asDex() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public DexVm getVm() { | 
|  | return vm; | 
|  | } | 
|  |  | 
|  | public DexVm.Version getVersion() { | 
|  | return vm.getVersion(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public AndroidApiLevel maxSupportedApiLevel() { | 
|  | return getMinApiLevel(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object other) { | 
|  | if (!(other instanceof DexRuntime)) { | 
|  | return false; | 
|  | } | 
|  | DexRuntime dexRuntime = (DexRuntime) other; | 
|  | return vm == dexRuntime.vm; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return vm.hashCode(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return "dex-" + vm.getVersion().toString(); | 
|  | } | 
|  |  | 
|  | public AndroidApiLevel getMinApiLevel() { | 
|  | return ToolHelper.getMinApiLevelForDexVm(vm); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Wrapper for the CF runtimes. | 
|  | public static class CfRuntime extends TestRuntime { | 
|  | private final CfVm vm; | 
|  | private final Path home; | 
|  |  | 
|  | public CfRuntime(CfVm vm, Path home) { | 
|  | assert vm != null; | 
|  | this.vm = vm; | 
|  | this.home = home.toAbsolutePath(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String name() { | 
|  | return vm.name().toLowerCase(); | 
|  | } | 
|  |  | 
|  | public Path getJavaHome() { | 
|  | return home; | 
|  | } | 
|  |  | 
|  | public Path getJavaExecutable() { | 
|  | return home.resolve("bin").resolve("java"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isCf() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public CfRuntime asCf() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public CfVm getVm() { | 
|  | return vm; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public AndroidApiLevel maxSupportedApiLevel() { | 
|  | // TODO: define the mapping from android API levels back to JDKs. | 
|  | return AndroidApiLevel.LATEST; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return vm.toString(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object obj) { | 
|  | if (!(obj instanceof CfRuntime)) { | 
|  | return false; | 
|  | } | 
|  | CfRuntime cfRuntime = (CfRuntime) obj; | 
|  | return vm == cfRuntime.vm && home.equals(cfRuntime.home); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return Objects.hash(vm, home); | 
|  | } | 
|  |  | 
|  | public boolean isOlderThan(CfVm version) { | 
|  | return vm.lessThan(version); | 
|  | } | 
|  |  | 
|  | public boolean isNewerThan(CfVm version) { | 
|  | return !vm.lessThanOrEqual(version); | 
|  | } | 
|  |  | 
|  | public boolean isNewerThanOrEqual(CfVm version) { | 
|  | return vm == version || !vm.lessThanOrEqual(version); | 
|  | } | 
|  | } | 
|  |  | 
|  | public boolean isDex() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | public boolean isCf() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | public DexRuntime asDex() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | public CfRuntime asCf() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | public Backend getBackend() { | 
|  | if (isDex()) { | 
|  | return Backend.DEX; | 
|  | } | 
|  | if (isCf()) { | 
|  | return Backend.CF; | 
|  | } | 
|  | throw new Unreachable("Unexpected runtime without backend: " + this); | 
|  | } | 
|  |  | 
|  | public abstract AndroidApiLevel maxSupportedApiLevel(); | 
|  |  | 
|  | @Override | 
|  | public abstract boolean equals(Object other); | 
|  |  | 
|  | @Override | 
|  | public abstract int hashCode(); | 
|  |  | 
|  | public abstract String name(); | 
|  | } |