| // 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 static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MAX_SUPPORTED_VERSION; | |
| import static com.android.tools.r8.ToolHelper.isWindows; | |
| import static org.junit.Assert.assertEquals; | |
| import static org.junit.Assert.assertNotEquals; | |
| import static org.junit.Assert.assertNotNull; | |
| import static org.junit.Assert.assertTrue; | |
| import com.android.tools.r8.TestRuntime.CfRuntime; | |
| import com.android.tools.r8.ToolHelper.ProcessResult; | |
| import com.android.tools.r8.errors.Unimplemented; | |
| import com.android.tools.r8.utils.ArrayUtils; | |
| import com.android.tools.r8.utils.FileUtils; | |
| import com.android.tools.r8.utils.structural.Ordered; | |
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.function.Consumer; | |
| import java.util.stream.Collectors; | |
| import org.junit.rules.TemporaryFolder; | |
| public class KotlinCompilerTool { | |
| public enum KotlinTargetVersion { | |
| NONE(""), | |
| JAVA_6("JAVA_6"), | |
| JAVA_8("JAVA_8"); | |
| private final String folderName; | |
| KotlinTargetVersion(String folderName) { | |
| this.folderName = folderName; | |
| } | |
| public String getFolderName() { | |
| return folderName; | |
| } | |
| public String getJvmTargetString() { | |
| switch (this) { | |
| case JAVA_6: | |
| return "1.6"; | |
| case JAVA_8: | |
| return "1.8"; | |
| default: | |
| throw new Unimplemented("JvmTarget not specified for " + this); | |
| } | |
| } | |
| } | |
| public enum KotlinCompilerVersion implements Ordered<KotlinCompilerVersion> { | |
| KOTLINC_1_3_72("kotlin-compiler-1.3.72"), | |
| KOTLINC_1_4_20("kotlin-compiler-1.4.20"), | |
| KOTLINC_1_5_0("kotlin-compiler-1.5.0"), | |
| KOTLINC_1_6_0("kotlin-compiler-1.6.0"), | |
| KOTLINC_1_7_0("kotlin-compiler-1.7.0"), | |
| KOTLIN_DEV("kotlin-compiler-dev"); | |
| public static final KotlinCompilerVersion MIN_SUPPORTED_VERSION = KOTLINC_1_5_0; | |
| public static final KotlinCompilerVersion MAX_SUPPORTED_VERSION = KOTLINC_1_7_0; | |
| private final String folder; | |
| KotlinCompilerVersion(String folder) { | |
| this.folder = folder; | |
| } | |
| public static KotlinCompilerVersion latest() { | |
| return ArrayUtils.last(values()); | |
| } | |
| public KotlinCompiler getCompiler() { | |
| return new KotlinCompiler(this); | |
| } | |
| public static List<KotlinCompilerVersion> getSupported() { | |
| return Arrays.stream(KotlinCompilerVersion.values()) | |
| .filter( | |
| compiler -> | |
| compiler.isGreaterThanOrEqualTo(MIN_SUPPORTED_VERSION) | |
| && compiler.isLessThanOrEqualTo(MAX_SUPPORTED_VERSION)) | |
| .collect(Collectors.toList()); | |
| } | |
| } | |
| public static final class KotlinCompiler { | |
| private final String name; | |
| private final Path lib; | |
| private final Path compiler; | |
| private final KotlinCompilerVersion compilerVersion; | |
| public KotlinCompiler(KotlinCompilerVersion compilerVersion) { | |
| this.lib = | |
| Paths.get(ToolHelper.THIRD_PARTY_DIR, "kotlin", compilerVersion.folder, "kotlinc", "lib"); | |
| this.compiler = lib.resolve("kotlin-compiler.jar"); | |
| this.compilerVersion = compilerVersion; | |
| this.name = compilerVersion.name(); | |
| } | |
| public KotlinCompiler(String name, Path compiler, KotlinCompilerVersion compilerVersion) { | |
| this.compiler = compiler; | |
| this.lib = null; | |
| this.compilerVersion = compilerVersion; | |
| this.name = name; | |
| } | |
| public static KotlinCompiler latest() { | |
| return MAX_SUPPORTED_VERSION.getCompiler(); | |
| } | |
| public Path getCompiler() { | |
| return compiler; | |
| } | |
| public Path getFolder() { | |
| return lib; | |
| } | |
| public boolean is(KotlinCompilerVersion version) { | |
| return compilerVersion == version; | |
| } | |
| public boolean isOneOf(KotlinCompilerVersion... versions) { | |
| return Arrays.stream(versions).anyMatch(this::is); | |
| } | |
| public boolean isNot(KotlinCompilerVersion version) { | |
| return !is(version); | |
| } | |
| public KotlinCompilerVersion getCompilerVersion() { | |
| return compilerVersion; | |
| } | |
| public Path getKotlinStdlibJar() { | |
| Path stdLib = getFolder().resolve("kotlin-stdlib.jar"); | |
| assert Files.exists(stdLib) : "Expected kotlin stdlib jar"; | |
| return stdLib; | |
| } | |
| public Path getKotlinReflectJar() { | |
| Path reflectJar = getFolder().resolve("kotlin-reflect.jar"); | |
| assert Files.exists(reflectJar) : "Expected kotlin reflect jar"; | |
| return reflectJar; | |
| } | |
| public Path getKotlinScriptRuntime() { | |
| Path reflectJar = getFolder().resolve("kotlin-script-runtime.jar"); | |
| assert Files.exists(reflectJar) : "Expected kotlin script runtime jar"; | |
| return reflectJar; | |
| } | |
| public Path getKotlinAnnotationJar() { | |
| Path annotationJar = getFolder().resolve("annotations-13.0.jar"); | |
| assert Files.exists(annotationJar) : "Expected annotation jar"; | |
| return annotationJar; | |
| } | |
| @Override | |
| public String toString() { | |
| return name; | |
| } | |
| } | |
| private final CfRuntime jdk; | |
| private final TestState state; | |
| private final KotlinCompiler compiler; | |
| private final KotlinTargetVersion targetVersion; | |
| private final List<Path> sources = new ArrayList<>(); | |
| private final List<Path> classpath = new ArrayList<>(); | |
| private final List<String> additionalArguments = new ArrayList<>(); | |
| private boolean useJvmAssertions; | |
| // TODO(b/211590675): We should enable assertions by default. | |
| private boolean enableAssertions = true; | |
| private Path output = null; | |
| private KotlinCompilerTool( | |
| CfRuntime jdk, TestState state, KotlinCompiler compiler, KotlinTargetVersion targetVersion) { | |
| this.jdk = jdk; | |
| this.state = state; | |
| this.compiler = compiler; | |
| this.targetVersion = targetVersion; | |
| } | |
| public KotlinCompiler getCompiler() { | |
| return compiler; | |
| } | |
| public KotlinTargetVersion getTargetVersion() { | |
| return targetVersion; | |
| } | |
| public static KotlinCompilerTool create( | |
| CfRuntime jdk, | |
| TemporaryFolder temp, | |
| KotlinCompiler kotlinCompiler, | |
| KotlinTargetVersion kotlinTargetVersion) { | |
| return create(jdk, new TestState(temp), kotlinCompiler, kotlinTargetVersion); | |
| } | |
| public static KotlinCompilerTool create( | |
| CfRuntime jdk, | |
| TestState state, | |
| KotlinCompiler kotlinCompiler, | |
| KotlinTargetVersion kotlinTargetVersion) { | |
| return new KotlinCompilerTool(jdk, state, kotlinCompiler, kotlinTargetVersion); | |
| } | |
| public KotlinCompilerTool addArguments(String... arguments) { | |
| Collections.addAll(additionalArguments, arguments); | |
| return this; | |
| } | |
| public KotlinCompilerTool addSourceFiles(Path... files) { | |
| return addSourceFiles(Arrays.asList(files)); | |
| } | |
| public KotlinCompilerTool addSourceFiles(Collection<Path> files) { | |
| sources.addAll(files); | |
| return this; | |
| } | |
| public KotlinCompilerTool addSourceFilesWithNonKtExtension(TemporaryFolder temp, Path... files) { | |
| return addSourceFilesWithNonKtExtension(temp, Arrays.asList(files)); | |
| } | |
| public KotlinCompilerTool includeRuntime() { | |
| assert !additionalArguments.contains("-include-runtime"); | |
| addArguments("-include-runtime"); | |
| return this; | |
| } | |
| public KotlinCompilerTool noReflect() { | |
| assert !additionalArguments.contains("-no-reflect"); | |
| addArguments("-no-reflect"); | |
| return this; | |
| } | |
| public KotlinCompilerTool noStdLib() { | |
| assert !additionalArguments.contains("-no-stdlib"); | |
| addArguments("-no-stdlib"); | |
| return this; | |
| } | |
| public KotlinCompilerTool disableAssertions() { | |
| this.enableAssertions = false; | |
| return this; | |
| } | |
| public KotlinCompilerTool addSourceFilesWithNonKtExtension( | |
| TemporaryFolder temp, Collection<Path> files) { | |
| return addSourceFiles( | |
| files.stream() | |
| .map( | |
| fileNotNamedKt -> { | |
| try { | |
| // The Kotlin compiler does not require particular naming of files except for | |
| // the extension, so just create a file called source.kt in a new directory. | |
| Path fileNamedKt = temp.newFolder().toPath().resolve("source.kt"); | |
| Files.copy(fileNotNamedKt, fileNamedKt); | |
| return fileNamedKt; | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| }) | |
| .collect(Collectors.toList())); | |
| } | |
| public KotlinCompilerTool addClasspathFiles(Path... files) { | |
| return addClasspathFiles(Arrays.asList(files)); | |
| } | |
| public KotlinCompilerTool addClasspathFiles(Collection<Path> files) { | |
| classpath.addAll(files); | |
| return this; | |
| } | |
| public KotlinCompilerTool setOutputPath(Path file) { | |
| assertTrue("Output path must be an existing directory or a non-existing jar file", | |
| (!Files.exists(file) && FileUtils.isJarFile(file) && Files.exists(file.getParent())) | |
| || (Files.exists(file) && Files.isDirectory(file))); | |
| this.output = file; | |
| return this; | |
| } | |
| public KotlinCompilerTool setUseJvmAssertions(boolean useJvmAssertions) { | |
| this.useJvmAssertions = useJvmAssertions; | |
| return this; | |
| } | |
| public KotlinCompilerTool apply(Consumer<KotlinCompilerTool> consumer) { | |
| consumer.accept(this); | |
| return this; | |
| } | |
| private Path getOrCreateOutputPath() throws IOException { | |
| return output != null ? output : state.getNewTempFolder().resolve("out.jar"); | |
| } | |
| /** Compile and return the compilations process result object. */ | |
| public ProcessResult compileRaw() throws IOException { | |
| assertNotNull("An output path must be specified prior to compilation.", output); | |
| return compileInternal(output); | |
| } | |
| /** Compile asserting success and return the output path. */ | |
| public Path compile() throws IOException { | |
| return compile(false); | |
| } | |
| public Path compile(boolean expectingFailure) throws IOException { | |
| Path output = getOrCreateOutputPath(); | |
| ProcessResult result = compileInternal(output); | |
| if (expectingFailure) { | |
| assertNotEquals(result.toString(), result.exitCode, 0); | |
| } else { | |
| assertEquals(result.toString(), result.exitCode, 0); | |
| } | |
| return output; | |
| } | |
| private ProcessResult compileInternal(Path output) throws IOException { | |
| List<String> cmdline = new ArrayList<>(); | |
| cmdline.add(jdk.getJavaExecutable().toString()); | |
| if (enableAssertions) { | |
| cmdline.add("-ea"); | |
| } | |
| cmdline.add("-cp"); | |
| cmdline.add(compiler.getCompiler().toString()); | |
| cmdline.add(ToolHelper.K2JVMCompiler); | |
| if (useJvmAssertions) { | |
| cmdline.add("-Xassertions=jvm"); | |
| } | |
| cmdline.add("-jdk-home"); | |
| cmdline.add(jdk.getJavaHome().toString()); | |
| cmdline.add("-jvm-target"); | |
| cmdline.add(targetVersion.getJvmTargetString()); | |
| for (Path source : sources) { | |
| cmdline.add(source.toString()); | |
| } | |
| cmdline.add("-d"); | |
| cmdline.add(output.toString()); | |
| if (!classpath.isEmpty()) { | |
| cmdline.add("-cp"); | |
| cmdline.add(classpath | |
| .stream() | |
| .map(Path::toString) | |
| .collect(Collectors.joining(isWindows() ? ";" : ":"))); | |
| } | |
| cmdline.addAll(additionalArguments); | |
| ProcessBuilder builder = new ProcessBuilder(cmdline); | |
| return ToolHelper.runProcess(builder); | |
| } | |
| } |