Implement fallback strategies
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.util.getFileByName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ExecutionStrategyIT(): BaseGradleIT() {
|
||||||
|
companion object {
|
||||||
|
private const val GRADLE_VERSION = "2.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDaemon() {
|
||||||
|
doTestExecutionStrategy("daemon")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testInProcess() {
|
||||||
|
doTestExecutionStrategy("in-process")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOutOfProcess() {
|
||||||
|
doTestExecutionStrategy("out-of-process")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doTestExecutionStrategy(executionStrategy: String) {
|
||||||
|
val project = Project("kotlinBuiltins", GRADLE_VERSION)
|
||||||
|
setupProject(project)
|
||||||
|
|
||||||
|
val strategyCLIArg = "-Dkotlin.compiler.execution.strategy=$executionStrategy"
|
||||||
|
val finishMessage = "Finished executing kotlin compiler using $executionStrategy strategy"
|
||||||
|
|
||||||
|
project.build("build", strategyCLIArg) {
|
||||||
|
assertSuccessful()
|
||||||
|
assertContains(finishMessage)
|
||||||
|
checkOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
val fKt = project.projectDir.getFileByName("f.kt")
|
||||||
|
fKt.delete()
|
||||||
|
project.build("build", strategyCLIArg) {
|
||||||
|
assertFailed()
|
||||||
|
assertContains(finishMessage)
|
||||||
|
assert(output.contains("Unresolved reference: f", ignoreCase = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun setupProject(project: Project) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun CompiledProject.checkOutput() {
|
||||||
|
assertFileExists("app/build/classes/main/foo/MainKt.class")
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -17,6 +17,7 @@ class SimpleKotlinGradleIT : BaseGradleIT() {
|
|||||||
|
|
||||||
project.build("compileDeployKotlin", "build") {
|
project.build("compileDeployKotlin", "build") {
|
||||||
assertSuccessful()
|
assertSuccessful()
|
||||||
|
assertContains("Finished executing kotlin compiler using daemon strategy")
|
||||||
assertReportExists("build/reports/tests/classes/demo.TestSource.html")
|
assertReportExists("build/reports/tests/classes/demo.TestSource.html")
|
||||||
assertContains(":compileKotlin", ":compileTestKotlin", ":compileDeployKotlin")
|
assertContains(":compileKotlin", ":compileTestKotlin", ":compileDeployKotlin")
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
fun f(fn: (x: Int)->Unit) {}
|
||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
fun f(fn: (x: Int)->Unit) {}
|
fun main() {
|
||||||
|
f { it * 2 }
|
||||||
|
}
|
||||||
+101
-3
@@ -33,8 +33,12 @@ import java.net.URL
|
|||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
internal const val KOTLIN_COMPILER_EXECUTION_STRATEGY_PROPERTY = "kotlin.compiler.execution.strategy"
|
||||||
|
internal const val DAEMON_EXECUTION_STRATEGY = "daemon"
|
||||||
|
internal const val IN_PROCESS_EXECUTION_STRATEGY = "in-process"
|
||||||
|
internal const val OUT_OF_PROCESS_EXECUTION_STRATEGY = "out-of-process"
|
||||||
|
|
||||||
const val KOTLIN_COMPILER_JAR_PATH_PROPERTY = "kotlin.compiler.jar.path"
|
internal const val KOTLIN_COMPILER_JAR_PATH_PROPERTY = "kotlin.compiler.jar.path"
|
||||||
|
|
||||||
internal class GradleCompilerRunner(private val project: Project) : KotlinCompilerRunner<GradleCompilerEnvironment>() {
|
internal class GradleCompilerRunner(private val project: Project) : KotlinCompilerRunner<GradleCompilerEnvironment>() {
|
||||||
override val log = GradleKotlinLogger(project.logger)
|
override val log = GradleKotlinLogger(project.logger)
|
||||||
@@ -80,6 +84,95 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun doRunCompiler(compilerClassName: String, argsArray: Array<String>, environment: GradleCompilerEnvironment, messageCollector: MessageCollector, collector: OutputItemsCollector): ExitCode {
|
||||||
|
val executionStrategy = System.getProperty(KOTLIN_COMPILER_EXECUTION_STRATEGY_PROPERTY) ?: DAEMON_EXECUTION_STRATEGY
|
||||||
|
if (executionStrategy == DAEMON_EXECUTION_STRATEGY) {
|
||||||
|
val daemonExitCode = compileWithDaemon(compilerClassName, argsArray, environment, messageCollector, collector)
|
||||||
|
|
||||||
|
if (daemonExitCode != null) {
|
||||||
|
return daemonExitCode
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.warn("Could not connect to kotlin daemon. Using fallback strategy.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isGradleDaemonUsed = System.getProperty("org.gradle.daemon")?.let(String::toBoolean)
|
||||||
|
return if (executionStrategy == IN_PROCESS_EXECUTION_STRATEGY || isGradleDaemonUsed == false) {
|
||||||
|
compileInProcess(argsArray, collector, compilerClassName, environment, messageCollector)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
compileOutOfProcess(argsArray, compilerClassName, environment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compileWithDaemon(compilerClassName: String, argsArray: Array<String>, environment: GradleCompilerEnvironment, messageCollector: MessageCollector, collector: OutputItemsCollector, retryOnConnectionError: Boolean): ExitCode? {
|
||||||
|
val exitCode = super.compileWithDaemon(compilerClassName, argsArray, environment, messageCollector, collector, retryOnConnectionError)
|
||||||
|
exitCode?.let {
|
||||||
|
logFinish(DAEMON_EXECUTION_STRATEGY)
|
||||||
|
}
|
||||||
|
return exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileOutOfProcess(
|
||||||
|
argsArray: Array<String>,
|
||||||
|
compilerClassName: String,
|
||||||
|
environment: GradleCompilerEnvironment
|
||||||
|
): ExitCode {
|
||||||
|
val javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"
|
||||||
|
val classpathString = environment.compilerClasspath.map {it.absolutePath}.joinToString(separator = File.pathSeparator)
|
||||||
|
val builder = ProcessBuilder(javaBin, "-cp", classpathString, compilerClassName, *argsArray)
|
||||||
|
val process = builder.start()
|
||||||
|
|
||||||
|
// important to read inputStream, otherwise the process may hang on some systems
|
||||||
|
val readErrThread = thread {
|
||||||
|
process.errorStream!!.bufferedReader().forEachLine {
|
||||||
|
System.err.println(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.inputStream!!.bufferedReader().forEachLine {
|
||||||
|
System.out.println(it)
|
||||||
|
}
|
||||||
|
readErrThread.join()
|
||||||
|
|
||||||
|
val exitCode = process.waitFor()
|
||||||
|
logFinish(OUT_OF_PROCESS_EXECUTION_STRATEGY)
|
||||||
|
return exitCodeFromProcessExitCode(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileInProcess(
|
||||||
|
argsArray: Array<String>,
|
||||||
|
collector: OutputItemsCollector,
|
||||||
|
compilerClassName: String,
|
||||||
|
environment: GradleCompilerEnvironment,
|
||||||
|
messageCollector: MessageCollector
|
||||||
|
): ExitCode {
|
||||||
|
val stream = ByteArrayOutputStream()
|
||||||
|
val out = PrintStream(stream)
|
||||||
|
// todo: cache classloader?
|
||||||
|
val classLoader = ParentLastURLClassLoader(environment.compilerClasspathURLs, this.javaClass.classLoader)
|
||||||
|
val servicesClass = Class.forName(Services::class.java.canonicalName, true, classLoader)
|
||||||
|
val emptyServices = servicesClass.getField("EMPTY").get(servicesClass)
|
||||||
|
val compiler = Class.forName(compilerClassName, true, classLoader)
|
||||||
|
|
||||||
|
val exec = compiler.getMethod(
|
||||||
|
"execAndOutputXml",
|
||||||
|
PrintStream::class.java,
|
||||||
|
servicesClass,
|
||||||
|
Array<String>::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
val res = exec.invoke(compiler.newInstance(), out, emptyServices, argsArray)
|
||||||
|
val exitCode = ExitCode.valueOf(res.toString())
|
||||||
|
processCompilerOutput(messageCollector, collector, stream, exitCode)
|
||||||
|
logFinish(IN_PROCESS_EXECUTION_STRATEGY)
|
||||||
|
return exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logFinish(strategy: String) {
|
||||||
|
log.debug("Finished executing kotlin compiler using $strategy strategy")
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun getDaemonConnection(environment: GradleCompilerEnvironment, messageCollector: MessageCollector): DaemonConnection {
|
override fun getDaemonConnection(environment: GradleCompilerEnvironment, messageCollector: MessageCollector): DaemonConnection {
|
||||||
return newDaemonConnection(environment.compilerJar, messageCollector, flagFile)
|
return newDaemonConnection(environment.compilerJar, messageCollector, flagFile)
|
||||||
@@ -93,13 +186,18 @@ internal class GradleCompilerEnvironment(
|
|||||||
val compilerJar: File by lazy {
|
val compilerJar: File by lazy {
|
||||||
val file = findKotlinCompilerJar(project, compilerClassName)
|
val file = findKotlinCompilerJar(project, compilerClassName)
|
||||||
?: throw IllegalStateException("Could not found Kotlin compiler jar. " +
|
?: throw IllegalStateException("Could not found Kotlin compiler jar. " +
|
||||||
"As a workaround you may specify path to compiler jar using " +
|
"As a workaround you may specify path to compiler jar using " +
|
||||||
"\"$KOTLIN_COMPILER_JAR_PATH_PROPERTY\" system property")
|
"\"$KOTLIN_COMPILER_JAR_PATH_PROPERTY\" system property")
|
||||||
|
|
||||||
project.logger.kotlinInfo("Using kotlin compiler jar: $file")
|
project.logger.kotlinInfo("Using kotlin compiler jar: $file")
|
||||||
file
|
file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val compilerClasspath: List<File>
|
||||||
|
get() = listOf(compilerJar).filterNotNull()
|
||||||
|
|
||||||
|
val compilerClasspathURLs: List<URL>
|
||||||
|
get() = compilerClasspath.map { it.toURI().toURL() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findKotlinCompilerJar(project: Project, compilerClassName: String): File? {
|
fun findKotlinCompilerJar(project: Project, compilerClassName: String): File? {
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.gradle.plugin;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parent-last classloader that will try the child classloader first and then the parent.
|
||||||
|
* This takes a fair bit of doing because java really prefers parent-first.
|
||||||
|
* <p/>
|
||||||
|
* For those not familiar with class loading trickery, be wary
|
||||||
|
*
|
||||||
|
* http://stackoverflow.com/questions/5445511/how-do-i-create-a-parent-last-child-first-classloader-in-java-or-how-to-overr
|
||||||
|
*/
|
||||||
|
public class ParentLastURLClassLoader extends ClassLoader {
|
||||||
|
private final ChildURLClassLoader childClassLoader;
|
||||||
|
|
||||||
|
public ParentLastURLClassLoader(@NotNull List<URL> classpath, @Nullable ClassLoader parent) {
|
||||||
|
super(Thread.currentThread().getContextClassLoader());
|
||||||
|
|
||||||
|
URL[] urls = classpath.toArray(new URL[classpath.size()]);
|
||||||
|
|
||||||
|
childClassLoader = new ChildURLClassLoader(urls, new FindClassClassLoader(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized Class<?> loadClass(@NotNull String name, boolean resolve) throws ClassNotFoundException {
|
||||||
|
try {
|
||||||
|
// first we try to find a class inside the child classloader
|
||||||
|
return childClassLoader.findClass(name);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
// didn't find it, try the parent
|
||||||
|
return super.loadClass(name, resolve);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class allows me to call findClass on a classloader
|
||||||
|
*/
|
||||||
|
private static class FindClassClassLoader extends ClassLoader {
|
||||||
|
public FindClassClassLoader(@Nullable ClassLoader parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
|
||||||
|
return super.findClass(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class delegates (child then parent) for the findClass method for a URLClassLoader.
|
||||||
|
* We need this because findClass is protected in URLClassLoader
|
||||||
|
*/
|
||||||
|
static class ChildURLClassLoader extends URLClassLoader {
|
||||||
|
private final FindClassClassLoader realParent;
|
||||||
|
|
||||||
|
public ChildURLClassLoader(@NotNull URL[] urls, @NotNull FindClassClassLoader realParent) {
|
||||||
|
super(urls, null);
|
||||||
|
|
||||||
|
this.realParent = realParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
|
||||||
|
Class<?> loaded = findLoadedClass(name);
|
||||||
|
if (loaded != null) {
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return super.findClass(name);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
// if that fails, we ask our real parent classloader to load the class (we give up)
|
||||||
|
return realParent.loadClass(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user