blob: 180b04377723b9539dd57f672a94e6c3d0eb884a [file] [log] [blame]
// 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"),
KOTLIN_DEV("kotlin-compiler-dev");
public static final KotlinCompilerVersion MIN_SUPPORTED_VERSION = KOTLINC_1_4_20;
public static final KotlinCompilerVersion MAX_SUPPORTED_VERSION = KOTLINC_1_6_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);
}
}