diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompiledEvaluator.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompiledEvaluator.kt index 07740419453..103abbce83b 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompiledEvaluator.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompiledEvaluator.kt @@ -23,25 +23,28 @@ import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read import kotlin.concurrent.write -open class GenericReplCompiledEvaluator(baseClasspath: Iterable) : ReplCompiledEvaluator { - - private val classpath = baseClasspath.toMutableList() +open class GenericReplCompiledEvaluator(baseClasspath: Iterable, baseClassloader: ClassLoader?) : ReplCompiledEvaluator { private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader = - org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader)) + org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader)) private val classLoaderLock = ReentrantReadWriteLock() private class ClassWithInstance(val klass: Class<*>, val instance: Any) private val compiledLoadedClassesHistory = arrayListOf>() - override fun eval(codeLine: ReplCodeLine, history: Iterable, compiledClasses: List, hasResult: Boolean): ReplEvalResult { + override fun eval(codeLine: ReplCodeLine, history: Iterable, compiledClasses: List, hasResult: Boolean, newClasspath: List): ReplEvalResult { checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let { return@eval ReplEvalResult.HistoryMismatch(it) } classLoaderLock.read { + if (newClasspath.isNotEmpty()) { + classLoaderLock.write { + classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader)) + } + } compiledClasses.filter { it.path.endsWith(".class") } .forEach { classLoader.addClass(JvmClassName.byInternalName(it.path.replaceFirst("\\.class$".toRegex(), "")), it.bytes) @@ -71,13 +74,6 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable) : ReplCom return if (hasResult) ReplEvalResult.ValueResult(rv) else ReplEvalResult.UnitResult } - fun dependenciesAdded(newClasspath: List) { - classLoaderLock.write { - classpath.addAll(newClasspath) - classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader)) - } - } - companion object { private val SCRIPT_RESULT_FIELD_NAME = "\$\$result" } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/Repl.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/Repl.kt index c6d18ee5e9c..34bb845d988 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/Repl.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/Repl.kt @@ -16,38 +16,62 @@ package org.jetbrains.kotlin.cli.common.repl +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import java.io.File import java.io.Serializable import java.util.* -data class ReplCodeLine(val no: Int, val code: String) +data class ReplCodeLine(val no: Int, val code: String) : Serializable { + companion object { + private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed + } +} data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable { override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes) + companion object { + private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed + } } sealed class ReplCheckResult : Serializable { object Ok : ReplCheckResult() object Incomplete : ReplCheckResult() - class Error(val message: String) : ReplCheckResult() + class Error(val message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCheckResult() { + override fun toString(): String = "Error(message = \"$message\"" + } + companion object { + private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed + } } sealed class ReplCompileResult : Serializable { - class CompiledClasses(val classes: List, val hasResult: Boolean) : ReplCompileResult() + class CompiledClasses(val classes: List, val hasResult: Boolean, val newClasspath: List) : ReplCompileResult() object Incomplete : ReplCompileResult() class HistoryMismatch(val lineNo: Int): ReplCompileResult() - class Error(val message: String) : ReplCompileResult() + class Error(val message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult() { + override fun toString(): String = "Error(message = \"$message\"" + } + companion object { + private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed + } } sealed class ReplEvalResult : Serializable { - class ValueResult(val value: Any?) : ReplEvalResult() + class ValueResult(val value: Any?) : ReplEvalResult() { + override fun toString(): String = "Result: $value" + } object UnitResult : ReplEvalResult() object Incomplete : ReplEvalResult() class HistoryMismatch(val lineNo: Int): ReplEvalResult() sealed class Error(val message: String) : ReplEvalResult() { class Runtime(message: String) : Error(message) - class CompileTime(message: String) : Error(message) + class CompileTime(message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(message) + override fun toString(): String = "${this.javaClass.kotlin.simpleName}Error(message = \"$message\"" + } + companion object { + private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed } } @@ -57,14 +81,11 @@ interface ReplChecker { interface ReplCompiler : ReplChecker { fun compile(codeLine: ReplCodeLine, history: Iterable): ReplCompileResult - - // a callback - fun dependenciesAdded(classpath: List) } interface ReplCompiledEvaluator { - fun eval(codeLine: ReplCodeLine, history: Iterable, compiledClasses: List, hasResult: Boolean): ReplEvalResult + fun eval(codeLine: ReplCodeLine, history: Iterable, compiledClasses: List, hasResult: Boolean, newClasspath: List): ReplEvalResult // override to capture output fun evalWithIO(body: () -> T): T = body() @@ -75,8 +96,6 @@ interface ReplEvaluator : ReplChecker { fun eval(codeLine: ReplCodeLine, history: Iterable): ReplEvalResult - fun dependenciesAdded(classpath: List) - // override to capture output fun evalWithIO(body: () -> T): T = body() } \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt index 16f48bc63bb..b71381fa0e7 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt @@ -77,8 +77,6 @@ open class GenericReplChecker( protected var lineState: LineState? = null - val classpath: MutableList = compilerConfiguration.jvmClasspathRoots.toMutableList() - fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder() override fun check(codeLine: ReplCodeLine, history: Iterable): ReplCheckResult { @@ -108,7 +106,7 @@ open class GenericReplChecker( } -abstract class GenericReplCompiler( +open class GenericReplCompiler( disposable: Disposable, scriptDefinition: KotlinScriptDefinition, compilerConfiguration: CompilerConfiguration, @@ -132,7 +130,7 @@ abstract class GenericReplCompiler( val res = check(codeLine, history) when (res) { ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete - is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message) + is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location) ReplCheckResult.Ok -> {} // continue } } @@ -140,14 +138,6 @@ abstract class GenericReplCompiler( } val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies) - newDependencies?.let { - logger.debug("found dependencies: ${it.classpath}") - val newCp = environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) - if (newCp != null && newCp.isNotEmpty()) { - logger.debug("new dependencies: $newCp") - dependenciesAdded(newCp) - } - } if (lastDependencies != newDependencies) { lastDependencies = newDependencies } @@ -179,7 +169,9 @@ abstract class GenericReplCompiler( descriptorsHistory.add(codeLine to scriptDescriptor) - return ReplCompileResult.CompiledClasses(state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }, state.replSpecific.hasResult) + return ReplCompileResult.CompiledClasses(state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }, + state.replSpecific.hasResult, + newDependencies?.let { environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) } ?: emptyList()) } } @@ -193,28 +185,26 @@ open class GenericRepl( disposable: Disposable, scriptDefinition: KotlinScriptDefinition, compilerConfiguration: CompilerConfiguration, - messageCollector: MessageCollector + messageCollector: MessageCollector, + baseClassloader: ClassLoader? ) : ReplEvaluator, GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) { - private val compiledEvaluator = GenericReplCompiledEvaluator(classpath) + private val compiledEvaluator = GenericReplCompiledEvaluator(compilerConfiguration.jvmClasspathRoots, baseClassloader) - override fun eval(codeLine: ReplCodeLine, history: Iterable): ReplEvalResult { - synchronized(this) { - - val (compiledClasses, hasResult) = compile(codeLine, history).let { - when (it) { - ReplCompileResult.Incomplete -> return@eval ReplEvalResult.Incomplete - is ReplCompileResult.HistoryMismatch -> return@eval ReplEvalResult.HistoryMismatch(it.lineNo) - is ReplCompileResult.Error -> return@eval ReplEvalResult.Error.CompileTime(it.message) - is ReplCompileResult.CompiledClasses -> it.classes to it.hasResult - } - } - - return compiledEvaluator.eval(codeLine, history, compiledClasses, hasResult) - } - } - - override fun dependenciesAdded(classpath: List) { - compiledEvaluator.dependenciesAdded(classpath) + override fun eval(codeLine: ReplCodeLine, history: Iterable): ReplEvalResult = synchronized(this) { + return compileAndEval(this, compiledEvaluator, codeLine, history) } } + + +fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: Iterable): ReplEvalResult = + replCompiler.compile(codeLine, history).let { + when (it) { + ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete + is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(it.lineNo) + is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(it.message, it.location) + is ReplCompileResult.CompiledClasses -> replCompiledEvaluator.eval(codeLine, history, it.classes, it.hasResult, it.newClasspath) + } + } + + diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232ScriptEngine.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232ScriptEngine.kt index 03edacecae4..ed9192161b6 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232ScriptEngine.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232ScriptEngine.kt @@ -32,7 +32,8 @@ class KotlinJsr232ScriptEngine( disposable: Disposable, private val factory: ScriptEngineFactory, private val scriptDefinition: KotlinScriptDefinition, - private val compilerConfiguration: CompilerConfiguration + private val compilerConfiguration: CompilerConfiguration, + baseClassLoader: ClassLoader ) : AbstractScriptEngine(), ScriptEngine { data class MessageCollectorReport(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation) @@ -72,7 +73,7 @@ class KotlinJsr232ScriptEngine( } } - private val repl = object : GenericRepl(disposable, scriptDefinition, compilerConfiguration, messageCollector) {} + private val repl = object : GenericRepl(disposable, scriptDefinition, compilerConfiguration, messageCollector, baseClassLoader) {} private var lineCount = 0 diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232StandardScriptEngineFactory.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232StandardScriptEngineFactory.kt index a66f8ef02bd..9e2cd3e0f70 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232StandardScriptEngineFactory.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/KotlinJsr232StandardScriptEngineFactory.kt @@ -49,7 +49,8 @@ class KotlinJsr232StandardScriptEngineFactory: ScriptEngineFactory { // TODO: addJvmClasspathRoots(config.classpath) put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script") put(JVMConfigurationKeys.INCLUDE_RUNTIME, true) - }) + }, + Thread.currentThread().contextClassLoader) override fun getOutputStatement(toDisplay: String?): String = "print(\"$toDisplay\")" override fun getMethodCallSyntax(obj: String, m: String, vararg args: String): String = "$obj.$m(${args.joinToString()})" diff --git a/compiler/daemon/daemon-client/daemon-client.iml b/compiler/daemon/daemon-client/daemon-client.iml index 5b473410eba..6b9384c6eb3 100644 --- a/compiler/daemon/daemon-client/daemon-client.iml +++ b/compiler/daemon/daemon-client/daemon-client.iml @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt index 1407b8a22f0..c20329a6594 100644 --- a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt +++ b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt @@ -31,7 +31,6 @@ import java.util.concurrent.TimeUnit import kotlin.comparisons.compareByDescending import kotlin.concurrent.thread - class CompilationServices( val incrementalCompilationComponents: IncrementalCompilationComponents? = null, val compilationCanceledStatus: CompilationCanceledStatus? = null @@ -53,18 +52,12 @@ object KotlinCompilerClient { autostart: Boolean = true, checkId: Boolean = true ): CompileService? { - fun newFlagFile(): File { - val flagFile = File.createTempFile("kotlin-compiler-client-", "-is-running") - flagFile.deleteOnExit() - return flagFile - } - val flagFile = System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY) - ?.let { it.trimQuotes() } + ?.let(String::trimQuotes) ?.check { !it.isBlank() } - ?.let { File(it) } - ?.check { it.exists() } - ?: newFlagFile() + ?.let(::File) + ?.check(File::exists) + ?: makeAutodeletingFlagFile() return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart) } @@ -287,7 +280,7 @@ object KotlinCompilerClient { val daemonLauncher = Native.get(ProcessLauncher::class.java) val daemon = daemonLauncher.start(processBuilder) - var isEchoRead = Semaphore(1) + val isEchoRead = Semaphore(1) isEchoRead.acquire() val stdoutThread = @@ -316,7 +309,7 @@ object KotlinCompilerClient { it.toLong() } catch (e: Exception) { - reportingTargets.report(DaemonReportCategory.INFO, "unable to interpret ${COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY} property ('$it'); using default timeout $DAEMON_DEFAULT_STARTUP_TIMEOUT_MS ms") + reportingTargets.report(DaemonReportCategory.INFO, "unable to interpret $COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY property ('$it'); using default timeout $DAEMON_DEFAULT_STARTUP_TIMEOUT_MS ms") null } } ?: DAEMON_DEFAULT_STARTUP_TIMEOUT_MS diff --git a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt new file mode 100644 index 00000000000..c19e7ea3547 --- /dev/null +++ b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt @@ -0,0 +1,131 @@ +/* + * 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.daemon.client + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.daemon.common.CompileService +import org.jetbrains.kotlin.daemon.common.RemoteOperationsTracer +import org.jetbrains.kotlin.daemon.common.SOCKET_ANY_FREE_PORT +import java.io.File +import java.io.InputStream +import java.io.OutputStream + +// TODO: reduce number of ports used then SOCKET_ANY_FREE_PORT is passed (same problem with other calls) + +open class KotlinRemoteReplClientBase( + disposable: Disposable, + protected val compileService: CompileService, + clientAliveFlagFile: File?, + targetPlatform: CompileService.TargetPlatform, + templateClasspath: List, + templateClassName: String, + compilerMessagesOutputStream: OutputStream, + evalOutputStream: OutputStream? = null, + evalErrorStream: OutputStream? = null, + evalInputStream: InputStream? = null, + port: Int = SOCKET_ANY_FREE_PORT, + operationsTracer: RemoteOperationsTracer? = null +) : ReplChecker { + + val sessionId = compileService.leaseReplSession( + clientAliveFlagFile?.absolutePath, + targetPlatform, + CompilerCallbackServicesFacadeServer(port = port), + templateClasspath, + templateClassName, + RemoteOutputStreamServer(compilerMessagesOutputStream, port), + evalOutputStream?.let { RemoteOutputStreamServer(it, port) }, + evalErrorStream?.let { RemoteOutputStreamServer(it, port) }, + evalInputStream?.let { RemoteInputStreamServer(it, port) }, + operationsTracer + ).get() + + init { + Disposer.register(disposable, Disposable { + compileService.releaseReplSession(sessionId) + }) + } + + override fun check(codeLine: ReplCodeLine, history: Iterable): ReplCheckResult { + return compileService.remoteReplLineCheck(sessionId, codeLine, history.toList()).get() + } +} + +class KotlinRemoteReplCompiler( + disposable: Disposable, + compileService: CompileService, + clientAliveFlagFile: File?, + targetPlatform: CompileService.TargetPlatform, + templateClasspath: List, + templateClassName: String, + compilerMessagesOutputStream: OutputStream, + port: Int = SOCKET_ANY_FREE_PORT, + operationsTracer: RemoteOperationsTracer? = null +) : KotlinRemoteReplClientBase( + disposable = disposable, + compileService = compileService, + clientAliveFlagFile = clientAliveFlagFile, + targetPlatform = targetPlatform, + templateClasspath = templateClasspath, + templateClassName = templateClassName, + compilerMessagesOutputStream = compilerMessagesOutputStream, + evalOutputStream = null, + evalErrorStream = null, + evalInputStream = null, + port = port, + operationsTracer = operationsTracer +), ReplCompiler { + + override fun compile(codeLine: ReplCodeLine, history: Iterable): ReplCompileResult { + return compileService.remoteReplLineCompile(sessionId, codeLine, history.toList()).get() + } +} + +class KotlinRemoteReplEvaluator( + disposable: Disposable, + compileService: CompileService, + clientAliveFlagFile: File?, + targetPlatform: CompileService.TargetPlatform, + templateClasspath: List, + templateClassName: String, + compilerMessagesOutputStream: OutputStream, + evalOutputStream: OutputStream?, + evalErrorStream: OutputStream?, + evalInputStream: InputStream?, + port: Int = SOCKET_ANY_FREE_PORT, + operationsTracer: RemoteOperationsTracer? = null +) : KotlinRemoteReplClientBase( + disposable = disposable, + compileService = compileService, + clientAliveFlagFile = clientAliveFlagFile, + targetPlatform = targetPlatform, + templateClasspath = templateClasspath, + templateClassName = templateClassName, + compilerMessagesOutputStream = compilerMessagesOutputStream, + evalOutputStream = evalOutputStream, + evalErrorStream = evalErrorStream, + evalInputStream = evalInputStream, + port = port, + operationsTracer = operationsTracer +), ReplEvaluator { + + override fun eval(codeLine: ReplCodeLine, history: Iterable): ReplEvalResult { + return compileService.remoteReplLineEval(sessionId, codeLine, history.toList()).get() + } +} diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt index 6e91ce9c681..6da0e9fb333 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.daemon.common +import org.jetbrains.kotlin.cli.common.repl.* +import java.io.File import java.io.Serializable import java.rmi.Remote import java.rmi.RemoteException @@ -44,7 +46,7 @@ interface CompileService : Remote { override fun hashCode(): Int = this.javaClass.hashCode() + (result?.hashCode() ?: 1) } class Ok : CallResult() { - override fun get(): Nothing = throw IllegalStateException("Gey is inapplicable to Ok call result") + override fun get(): Nothing = throw IllegalStateException("Get is inapplicable to Ok call result") override fun equals(other: Any?): Boolean = other is Ok override fun hashCode(): Int = this.javaClass.hashCode() + 1 // avoiding clash with the hash of class itself } @@ -122,4 +124,42 @@ interface CompileService : Remote { serviceOutputStream: RemoteOutputStream, operationsTracer: RemoteOperationsTracer? ): CallResult + + @Throws(RemoteException::class) + fun leaseReplSession( + aliveFlagPath: String?, + targetPlatform: CompileService.TargetPlatform, + servicesFacade: CompilerCallbackServicesFacade, + templateClasspath: List, + templateClassName: String, + compilerMessagesOutputStream: RemoteOutputStream, + evalOutputStream: RemoteOutputStream?, + evalErrorStream: RemoteOutputStream?, + evalInputStream: RemoteInputStream?, + operationsTracer: RemoteOperationsTracer? + ): CallResult + + @Throws(RemoteException::class) + fun releaseReplSession(sessionId: Int): CallResult + + @Throws(RemoteException::class) + fun remoteReplLineCheck( + sessionId: Int, + codeLine: ReplCodeLine, + history: List + ): CallResult + + @Throws(RemoteException::class) + fun remoteReplLineCompile( + sessionId: Int, + codeLine: ReplCodeLine, + history: List + ): CallResult + + @Throws(RemoteException::class) + fun remoteReplLineEval( + sessionId: Int, + codeLine: ReplCodeLine, + history: List + ): CallResult } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index 9092fdc1a19..0bfecc50523 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -16,11 +16,17 @@ package org.jetbrains.kotlin.daemon +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.impl.ZipHandler import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY +import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult +import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine +import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult +import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.daemon.common.* @@ -45,6 +51,8 @@ import kotlin.concurrent.read import kotlin.concurrent.schedule import kotlin.concurrent.write +const val REMOTE_STREAM_BUFFER_SIZE = 4096 + fun nowSeconds() = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime()) interface CompilerSelector { @@ -83,10 +91,16 @@ class CompileServiceImpl( } // wrapped in a class to encapsulate alive check logic - private class ClientOrSessionProxy(val aliveFlagPath: String?) { + private class ClientOrSessionProxy(val aliveFlagPath: String?, val data: T? = null, private var disposable: Disposable? = null) { val registered = nowSeconds() val secondsSinceRegistered: Long get() = nowSeconds() - registered val isAlive: Boolean get() = aliveFlagPath?.let { File(it).exists() } ?: true // assuming that if no file was given, the client is alive + fun dispose() { + disposable?.let { + Disposer.dispose(it) + disposable = null + } + } } private val sessionsIdCounter = AtomicInteger(0) @@ -103,8 +117,8 @@ class CompileServiceImpl( // TODO: encapsulate operations on state here private val state = object { - val clientProxies: MutableSet = hashSetOf() - val sessions: MutableMap = hashMapOf() + val clientProxies: MutableSet> = hashSetOf() + val sessions: MutableMap> = hashMapOf() val delayedShutdownQueued = AtomicBoolean(false) @@ -155,16 +169,20 @@ class CompileServiceImpl( // TODO: consider tying a session to a client and use this info to cleanup override fun leaseCompileSession(aliveFlagPath: String?): CompileService.CallResult = ifAlive(minAliveness = Aliveness.Alive) { + leaseSessionImpl(ClientOrSessionProxy(aliveFlagPath)).apply { + log.info("leased a new session $this, client alive file: $aliveFlagPath") + } + } + + private fun leaseSessionImpl(session: ClientOrSessionProxy): Int { // fighting hypothetical integer wrapping var newId = sessionsIdCounter.incrementAndGet() - val session = ClientOrSessionProxy(aliveFlagPath) for (attempt in 1..100) { if (newId != CompileService.NO_SESSION) { synchronized(state.sessions) { if (!state.sessions.containsKey(newId)) { state.sessions.put(newId, session) - log.info("leased a new session $newId, client alive file: $aliveFlagPath") - return@ifAlive newId + return newId } } } @@ -176,6 +194,7 @@ class CompileServiceImpl( override fun releaseCompileSession(sessionId: Int) = ifAlive_Nothing(minAliveness = Aliveness.LastSession) { synchronized(state.sessions) { + state.sessions[sessionId]?.dispose() state.sessions.remove(sessionId) log.info("cleaning after session $sessionId") clearJarCache() @@ -248,6 +267,64 @@ class CompileServiceImpl( } } + override fun leaseReplSession( + aliveFlagPath: String?, + targetPlatform: CompileService.TargetPlatform, + servicesFacade: CompilerCallbackServicesFacade, + templateClasspath: List, + templateClassName: String, + compilerMessagesOutputStream: RemoteOutputStream, + evalOutputStream: RemoteOutputStream?, + evalErrorStream: RemoteOutputStream?, + evalInputStream: RemoteInputStream?, + operationsTracer: RemoteOperationsTracer? + ): CompileService.CallResult = ifAlive_Raw(minAliveness = Aliveness.Alive) { + if (targetPlatform != CompileService.TargetPlatform.JVM) + CompileService.CallResult.Error("Sorry, only JVM target platform is supported now") + else { + val disposable = Disposer.newDisposable() + val repl = KotlinJvmReplService(disposable, templateClasspath, templateClassName, compilerMessagesOutputStream, evalOutputStream, evalErrorStream, evalInputStream, operationsTracer) + val sessionId = leaseSessionImpl(ClientOrSessionProxy(aliveFlagPath, repl, disposable)) + + CompileService.CallResult.Good(sessionId) + } + } + + private fun withValidRepl(sessionId: Int, body: KotlinJvmReplService.() -> R): CompileService.CallResult = + state.sessions[sessionId]?.let { + (it.data as? KotlinJvmReplService?)?.let { + CompileService.CallResult.Good(it.body()) + } + } ?: CompileService.CallResult.Error("Unknown or invalid session $sessionId") + + // TODO: add more checks (e.g. is it a repl session) + override fun releaseReplSession(sessionId: Int): CompileService.CallResult = releaseCompileSession(sessionId) + + override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine, history: List): CompileService.CallResult = + ifAlive_Raw(minAliveness = Aliveness.Alive) { + withValidRepl(sessionId) { + check(codeLine, history) + } + } + + override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List): CompileService.CallResult = + ifAlive_Raw(minAliveness = Aliveness.Alive) { + withValidRepl(sessionId) { + compile(codeLine, history) + } + } + + override fun remoteReplLineEval( + sessionId: Int, + codeLine: ReplCodeLine, + history: List + ): CompileService.CallResult = + ifAlive_Raw(minAliveness = Aliveness.Alive) { + withValidRepl(sessionId) { + eval(codeLine, history) + } + } + // internal implementation stuff // TODO: consider matching compilerId coming from outside with actual one @@ -302,19 +379,31 @@ class CompileServiceImpl( shutdown() } else { + var anyDead = false + var shuttingDown = false + synchronized(state.sessions) { // 2. check if any session hanged - clean - // making copy of the list before calling release - state.sessions.filterValues { !it.isAlive }.keys.toList() - }.forEach { releaseCompileSession(it) } + state.sessions.filterValues { !it.isAlive }.apply { anyDead = true }.forEach { + it.value.dispose() + state.sessions.remove(it.key) + } + } // 3. check if in graceful shutdown state and all sessions are closed if (shutdownCondition({ state.alive.get() == Aliveness.LastSession.ordinal && state.sessions.none()}, "All sessions finished, shutting down")) { shutdown() + shuttingDown = true } // 4. clean dead clients, then check if any left - conditional shutdown (with small delay) - synchronized(state.clientProxies) { state.clientProxies.removeAll(state.clientProxies.filter { !it.isAlive }) } + synchronized(state.clientProxies) { + state.clientProxies.removeAll( + state.clientProxies.filter { !it.isAlive }.apply { anyDead = true }.map { + it.dispose() + it + }) + } if (state.clientProxies.isEmpty() && compilationsCounter.get() > 0 && !state.delayedShutdownQueued.get()) { log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms") shutdownWithDelay() @@ -326,8 +415,14 @@ class CompileServiceImpl( shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") || // 7. compiler changed (seldom check) - shutdown // TODO: could be too expensive anyway, consider removing this check - shutdownCondition({ classpathWatcher.isChanged }, "Compiler changed")) { + shutdownCondition({ classpathWatcher.isChanged }, "Compiler changed")) + { shutdown() + shuttingDown = true + } + + if (anyDead && !shuttingDown) { + clearJarCache() } } } @@ -374,6 +469,7 @@ class CompileServiceImpl( private fun shutdownImpl() { log.info("Shutdown started") state.alive.set(Aliveness.Dying.ordinal) + UnicastRemoteObject.unexportObject(this, true) log.info("Shutdown complete") onShutdown() @@ -414,8 +510,8 @@ class CompileServiceImpl( compilationsCounter.incrementAndGet() val rpcProfiler = if (daemonOptions.reportPerf) WallAndThreadTotalProfiler() else DummyProfiler() val eventManger = EventMangerImpl() - val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesStreamProxy, rpcProfiler), 4096)) - val serviceOutputStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(serviceOutputStreamProxy, rpcProfiler), 4096)) + val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesStreamProxy, rpcProfiler), REMOTE_STREAM_BUFFER_SIZE)) + val serviceOutputStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(serviceOutputStreamProxy, rpcProfiler), REMOTE_STREAM_BUFFER_SIZE)) try { checkedCompile(args, serviceOutputStream, rpcProfiler) { val res = body(compilerMessagesStream, eventManger, rpcProfiler).code @@ -490,17 +586,6 @@ class CompileServiceImpl( (KotlinCoreEnvironment.applicationEnvironment?.jarFileSystem as? CoreJarFileSystem)?.clearHandlersCache() } - // copied (with edit) from gradle plugin - private fun callVoidStaticMethod(classFqName: String, methodName: String) { - // compiler classloader == current classloader for now - // TODO: consider abstracting classloader, for easier changing it for a compiler - val cls = this.javaClass.classLoader.loadClass(classFqName) - - val method = cls.getMethod(methodName) - - method.invoke(null) - } - private fun ifAlive(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult = rwlock.read { ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { CompileService.CallResult.Good(body()) } } @@ -513,6 +598,10 @@ class CompileServiceImpl( } } + private fun ifAlive_Raw(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> CompileService.CallResult): CompileService.CallResult = rwlock.read { + ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { body() } + } + private fun ifAliveExclusive(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult = rwlock.write { ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { CompileService.CallResult.Good(body()) } } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt new file mode 100644 index 00000000000..b1e617314cc --- /dev/null +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt @@ -0,0 +1,177 @@ +/* + * 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.daemon + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.cli.common.messages.* +import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompiler +import org.jetbrains.kotlin.cli.jvm.repl.compileAndEval +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.daemon.common.DummyProfiler +import org.jetbrains.kotlin.daemon.common.RemoteInputStream +import org.jetbrains.kotlin.daemon.common.RemoteOperationsTracer +import org.jetbrains.kotlin.daemon.common.RemoteOutputStream +import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate +import org.jetbrains.kotlin.utils.PathUtil +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.File +import java.io.PrintStream +import java.net.URLClassLoader + +open class KotlinJvmReplService( + disposable: Disposable, + templateClasspath: List, + templateClassName: String, + compilerOutputStreamProxy: RemoteOutputStream, + evalOutputStream: RemoteOutputStream?, + evalErrorStream: RemoteOutputStream?, + evalInputStream: RemoteInputStream?, + val operationsTracer: RemoteOperationsTracer? +) : ReplCompiler, ReplEvaluator { + + protected val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerOutputStreamProxy, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) + + protected class KeepFirstErrorMessageCollector(compilerMessagesStream: PrintStream) : MessageCollector { + + private val innerCollector = PrintingMessageCollector(compilerMessagesStream, MessageRenderer.WITHOUT_PATHS, false) + + internal var firstErrorMessage: String? = null + internal var firstErrorLocation: CompilerMessageLocation? = null + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { + if (firstErrorMessage == null && severity.isError) { + firstErrorMessage = message + firstErrorLocation = location + } + innerCollector.report(severity, message, location) + } + + override fun hasErrors(): Boolean = innerCollector.hasErrors() + override fun clear() { + innerCollector.clear() + } + } + protected val messageCollector = KeepFirstErrorMessageCollector(compilerMessagesStream) + + protected val configuration = CompilerConfiguration().apply { + addJvmClasspathRoots(PathUtil.getJdkClassesRoots()) + addJvmClasspathRoots(PathUtil.getKotlinPathsForCompiler().let { listOf(it.runtimePath, it.reflectPath) }) + addJvmClasspathRoots(templateClasspath) + put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script") + } + + protected fun makeScriptDefinition(templateClasspath: List, templateClassName: String): KotlinScriptDefinition? { + val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader) + + try { + val cls = classloader.loadClass(templateClassName) + val def = KotlinScriptDefinitionFromTemplate(cls.kotlin, null, null, emptyMap()) + messageCollector.report( + CompilerMessageSeverity.INFO, + "New script definition $templateClassName: files pattern = \"${def.scriptFilePattern}\", resolver = ${def.resolver?.javaClass?.name}", + CompilerMessageLocation.NO_LOCATION + ) + return def + } + catch (ex: ClassNotFoundException) { + messageCollector.report( + CompilerMessageSeverity.ERROR, "Cannot find script definition template class $templateClassName", CompilerMessageLocation.NO_LOCATION + ) + } + catch (ex: Exception) { + messageCollector.report( + CompilerMessageSeverity.ERROR, "Error processing script definition template $templateClassName: ${ex.message}", CompilerMessageLocation.NO_LOCATION + ) + } + return null + } + + private val scriptDef = makeScriptDefinition(templateClasspath, templateClassName) + + private val replCompiler : GenericReplCompiler? by lazy { + if (scriptDef == null) null + else GenericReplCompiler(disposable, scriptDef, configuration, messageCollector) + } + + private val compiledEvaluator : GenericReplCompiledEvaluator by lazy { + if (evalOutputStream == null && evalErrorStream == null && evalInputStream == null) + GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null) + else object : GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null) { + val out = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(evalOutputStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) + val err = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(evalErrorStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) + val `in` = BufferedInputStream(RemoteInputStreamClient(evalInputStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE) + override fun evalWithIO(body: () -> T): T { + val prevOut = System.out + System.setOut(out) + val prevErr = System.err + System.setErr(err) + val prevIn = System.`in` + System.setIn(`in`) + try { + return body() + } + finally { + System.setIn(prevIn) + System.setErr(prevErr) + System.setOut(prevOut) + } + } + } + } + + override fun check(codeLine: ReplCodeLine, history: Iterable): ReplCheckResult { + operationsTracer?.before("check") + try { + return replCompiler?.check(codeLine, history) + ?: ReplCheckResult.Error(messageCollector.firstErrorMessage ?: "Unknown error", + messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) + } + finally { + operationsTracer?.after("check") + } + } + + override fun compile(codeLine: ReplCodeLine, history: Iterable): ReplCompileResult { + operationsTracer?.before("compile") + try { + return replCompiler?.compile(codeLine, history) + ?: ReplCompileResult.Error(messageCollector.firstErrorMessage ?: "Unknown error", + messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) + } + finally { + operationsTracer?.after("compile") + } + } + + override fun eval(codeLine: ReplCodeLine, history: Iterable): ReplEvalResult = synchronized(this) { + operationsTracer?.before("eval") + try { + return replCompiler?.let { compileAndEval(it, compiledEvaluator, codeLine, history) } + ?: ReplEvalResult.Error.CompileTime(messageCollector.firstErrorMessage ?: "Unknown error", + messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) + } + finally { + operationsTracer?.after("eval") + } + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 43f7d324fcf..00551e7bda1 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -16,11 +16,11 @@ package org.jetbrains.kotlin.daemon +import com.intellij.openapi.util.Disposer +import junit.framework.TestCase import org.jetbrains.kotlin.cli.AbstractCliTest -import org.jetbrains.kotlin.daemon.client.CompilerCallbackServicesFacadeServer -import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets -import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient -import org.jetbrains.kotlin.daemon.client.RemoteOutputStreamServer +import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.daemon.client.* import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase import org.jetbrains.kotlin.progress.CompilationCanceledStatus @@ -29,8 +29,11 @@ import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.utils.PathUtil import java.io.ByteArrayOutputStream import java.io.File +import java.net.URL +import java.net.URLClassLoader import java.rmi.server.UnicastRemoteObject import java.util.concurrent.CountDownLatch +import java.util.concurrent.Future import java.util.concurrent.TimeUnit import kotlin.concurrent.thread import kotlin.test.fail @@ -81,7 +84,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val logFile = createTempFile("kotlin-daemon-test.", ".log") - val daemonJVMOptions = configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"", + val daemonJVMOptions = configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"", inheritMemoryLimits = false, inheritAdditionalProperties = false) var daemonShotDown = false @@ -159,11 +162,11 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val logFile1 = createTempFile("kotlin-daemon1-test", ".log") val logFile2 = createTempFile("kotlin-daemon2-test", ".log") val daemonJVMOptions1 = - configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile1.loggerCompatiblePath}\"", + configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile1.loggerCompatiblePath}\"", inheritMemoryLimits = false, inheritAdditionalProperties = false) val daemonJVMOptions2 = - configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile2.loggerCompatiblePath}\"", + configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile2.loggerCompatiblePath}\"", inheritMemoryLimits = false, inheritAdditionalProperties = false) assertTrue(logFile1.length() == 0L && logFile2.length() == 0L) @@ -198,7 +201,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val logFile = createTempFile("kotlin-daemon-test", ".log") val daemonJVMOptions = - configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"", + configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"", inheritMemoryLimits = false, inheritAdditionalProperties = false) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) @@ -225,7 +228,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val logFile = createTempFile("kotlin-daemon-test", ".log") val daemonJVMOptions = - configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"", + configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"", inheritMemoryLimits = false, inheritAdditionalProperties = false) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) @@ -257,7 +260,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val logFile = createTempFile("kotlin-daemon-test", ".log") val daemonJVMOptions = - configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"", + configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"", inheritMemoryLimits = false, inheritAdditionalProperties = false) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) @@ -304,7 +307,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val jar = tmpdir.absolutePath + File.separator + "hello.jar" val args = listOf( File(File(System.getProperty("java.home"), "bin"), "java").absolutePath, - "-D${COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY}", + "-D$COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY", "-cp", daemonClientClassPath.joinToString(File.pathSeparator) { it.absolutePath }, KotlinCompilerClient::class.qualifiedName!!) + @@ -430,7 +433,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val logFile = createTempFile("kotlin-daemon-test", ".log") val daemonJVMOptions = - configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"", + configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"", inheritMemoryLimits = false, inheritAdditionalProperties = false) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) @@ -467,8 +470,69 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { logFile.delete() } } + + fun testDaemonReplLocalEval() { + withFlagFile(getTestName(true), ".alive") { flagFile -> + val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) + val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) + val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) + assertNotNull("failed to connect daemon", daemon) + + val disposable = Disposer.newDisposable() + + val repl = KotlinRemoteReplCompiler(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, + classpathFromClassloader(), + ScriptWithNoParam::class.qualifiedName!!, System.err) + + val res1 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) + TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete) + + val codeLine = ReplCodeLine(0, "1 + 2") + val res2 = repl.compile(codeLine, emptyList()) + + val compileRes = res2 as? ReplCompileResult.CompiledClasses + TestCase.assertNotNull("Unexpected compile result: $res2", compileRes) + + val localEvaluator = GenericReplCompiledEvaluator(emptyList(), Thread.currentThread().contextClassLoader) + + val res3 = localEvaluator.eval(codeLine, emptyList(), compileRes!!.classes, compileRes.hasResult, compileRes.newClasspath) + val evalRes = res3 as? ReplEvalResult.ValueResult + TestCase.assertNotNull("Unexpected eval result: $res3", evalRes) + TestCase.assertEquals(3, evalRes!!.value) + + Disposer.dispose(disposable) + } + } + +// fun testDaemonReplRemoteEval() { +// withFlagFile(getTestName(true), ".alive") { flagFile -> +// val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) +// val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) +// val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) +// assertNotNull("failed to connect daemon", daemon) +// +// val evalOut = ByteArrayOutputStream() +// val evalErr = ByteArrayOutputStream() +// val evalIn = ByteArrayInputStream("42\n".toByteArray()) +// +// val disposable = Disposer.newDisposable() +// +// val repl = KotlinRemoteReplEvaluator(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, emptyList(), +// "", System.err, evalOut, evalErr, evalIn) +// +// val res1 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) +// TestCase.assertEquals(ReplCheckResult.Incomplete, res1) +// +// val res2 = repl.eval(ReplCodeLine(0, "1 + 2"), emptyList()) +// TestCase.assertEquals(ReplEvalResult.ValueResult(3), res2) +// +// Disposer.dispose(disposable) +// } +// } } + + // stolen from CompilerFileLimitTest internal fun generateLargeKotlinFile(size: Int): String { return buildString { @@ -537,4 +601,39 @@ internal inline fun withFlagFile(prefix: String, suffix: String? = null, body: ( private val File.loggerCompatiblePath: String get() = if (OSKind.current == OSKind.Windows) absolutePath.replace('\\', '/') - else absolutePath \ No newline at end of file + else absolutePath + +open class TestKotlinScriptDummyDependenciesResolver : ScriptDependenciesResolver { + + private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() } + + override fun resolve(script: ScriptContents, + environment: Map?, + report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit, + previousDependencies: KotlinScriptExternalDependencies? + ): Future + { + return object : KotlinScriptExternalDependencies { + override val classpath: Iterable = classpathFromClassloader() + override val imports: Iterable = listOf("org.jetbrains.kotlin.scripts.DependsOn", "org.jetbrains.kotlin.scripts.DependsOnTwo") + }.asFuture() + } +} + +@ScriptTemplateDefinition(resolver = TestKotlinScriptDummyDependenciesResolver::class) +abstract class ScriptWithNoParam() + +internal fun classpathFromClassloader(): List = + (TestKotlinScriptDummyDependenciesResolver::class.java.classLoader as? URLClassLoader)?.urLs + ?.mapNotNull { it.toFile() } + ?.filter { it.path.contains("out") && it.path.contains("test") } + ?: emptyList() + +internal fun URL.toFile() = + try { + File(toURI().schemeSpecificPart) + } + catch (e: java.net.URISyntaxException) { + if (protocol != "file") null + else File(file) + }