Add remote repl support to the daemon, refactor repl classes accordingly, simple tests
This commit is contained in:
+8
-12
@@ -23,25 +23,28 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>) : ReplCompiledEvaluator {
|
||||
|
||||
private val classpath = baseClasspath.toMutableList()
|
||||
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, 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<Pair<ReplCodeLine, ClassWithInstance>>()
|
||||
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult {
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean, newClasspath: List<File>): 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<File>) : ReplCom
|
||||
return if (hasResult) ReplEvalResult.ValueResult(rv) else ReplEvalResult.UnitResult
|
||||
}
|
||||
|
||||
fun dependenciesAdded(newClasspath: List<File>) {
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -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<CompiledClassData>, val hasResult: Boolean) : ReplCompileResult()
|
||||
class CompiledClasses(val classes: List<CompiledClassData>, val hasResult: Boolean, val newClasspath: List<File>) : 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<ReplCodeLine>): ReplCompileResult
|
||||
|
||||
// a callback
|
||||
fun dependenciesAdded(classpath: List<File>)
|
||||
}
|
||||
|
||||
interface ReplCompiledEvaluator {
|
||||
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean, newClasspath: List<File>): ReplEvalResult
|
||||
|
||||
// override to capture output
|
||||
fun<T> evalWithIO(body: () -> T): T = body()
|
||||
@@ -75,8 +96,6 @@ interface ReplEvaluator : ReplChecker {
|
||||
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult
|
||||
|
||||
fun dependenciesAdded(classpath: List<File>)
|
||||
|
||||
// override to capture output
|
||||
fun<T> evalWithIO(body: () -> T): T = body()
|
||||
}
|
||||
@@ -77,8 +77,6 @@ open class GenericReplChecker(
|
||||
|
||||
protected var lineState: LineState? = null
|
||||
|
||||
val classpath: MutableList<File> = compilerConfiguration.jvmClasspathRoots.toMutableList()
|
||||
|
||||
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
||||
|
||||
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): 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<ReplCodeLine>): 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<File>) {
|
||||
compiledEvaluator.dependenciesAdded(classpath)
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult = synchronized(this) {
|
||||
return compileAndEval(this, compiledEvaluator, codeLine, history)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+2
-1
@@ -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()})"
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
<orderEntry type="library" name="native-platform-uberjar" level="project" />
|
||||
<orderEntry type="module" module-name="cli-common" />
|
||||
</component>
|
||||
</module>
|
||||
+6
-13
@@ -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
|
||||
|
||||
+131
@@ -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<File>,
|
||||
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<ReplCodeLine>): ReplCheckResult {
|
||||
return compileService.remoteReplLineCheck(sessionId, codeLine, history.toList()).get()
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinRemoteReplCompiler(
|
||||
disposable: Disposable,
|
||||
compileService: CompileService,
|
||||
clientAliveFlagFile: File?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
templateClasspath: List<File>,
|
||||
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<ReplCodeLine>): ReplCompileResult {
|
||||
return compileService.remoteReplLineCompile(sessionId, codeLine, history.toList()).get()
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinRemoteReplEvaluator(
|
||||
disposable: Disposable,
|
||||
compileService: CompileService,
|
||||
clientAliveFlagFile: File?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
templateClasspath: List<File>,
|
||||
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<ReplCodeLine>): ReplEvalResult {
|
||||
return compileService.remoteReplLineEval(sessionId, codeLine, history.toList()).get()
|
||||
}
|
||||
}
|
||||
+41
-1
@@ -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<Nothing>() {
|
||||
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<Int>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun leaseReplSession(
|
||||
aliveFlagPath: String?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
servicesFacade: CompilerCallbackServicesFacade,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
compilerMessagesOutputStream: RemoteOutputStream,
|
||||
evalOutputStream: RemoteOutputStream?,
|
||||
evalErrorStream: RemoteOutputStream?,
|
||||
evalInputStream: RemoteInputStream?,
|
||||
operationsTracer: RemoteOperationsTracer?
|
||||
): CallResult<Int>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun releaseReplSession(sessionId: Int): CallResult<Nothing>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineCheck(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CallResult<ReplCheckResult>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineCompile(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CallResult<ReplCompileResult>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineEval(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CallResult<ReplEvalResult>
|
||||
}
|
||||
|
||||
@@ -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<out T: Any>(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<ClientOrSessionProxy> = hashSetOf()
|
||||
val sessions: MutableMap<Int, ClientOrSessionProxy> = hashMapOf()
|
||||
val clientProxies: MutableSet<ClientOrSessionProxy<Any>> = hashSetOf()
|
||||
val sessions: MutableMap<Int, ClientOrSessionProxy<Any>> = 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<Int> = ifAlive(minAliveness = Aliveness.Alive) {
|
||||
leaseSessionImpl(ClientOrSessionProxy<Any>(aliveFlagPath)).apply {
|
||||
log.info("leased a new session $this, client alive file: $aliveFlagPath")
|
||||
}
|
||||
}
|
||||
|
||||
private fun<T: Any> leaseSessionImpl(session: ClientOrSessionProxy<T>): 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<File>,
|
||||
templateClassName: String,
|
||||
compilerMessagesOutputStream: RemoteOutputStream,
|
||||
evalOutputStream: RemoteOutputStream?,
|
||||
evalErrorStream: RemoteOutputStream?,
|
||||
evalInputStream: RemoteInputStream?,
|
||||
operationsTracer: RemoteOperationsTracer?
|
||||
): CompileService.CallResult<Int> = 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<R> withValidRepl(sessionId: Int, body: KotlinJvmReplService.() -> R): CompileService.CallResult<R> =
|
||||
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<Nothing> = releaseCompileSession(sessionId)
|
||||
|
||||
override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>): CompileService.CallResult<ReplCheckResult> =
|
||||
ifAlive_Raw(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
check(codeLine, history)
|
||||
}
|
||||
}
|
||||
|
||||
override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>): CompileService.CallResult<ReplCompileResult> =
|
||||
ifAlive_Raw(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
compile(codeLine, history)
|
||||
}
|
||||
}
|
||||
|
||||
override fun remoteReplLineEval(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CompileService.CallResult<ReplEvalResult> =
|
||||
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<R> ifAlive(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult<R> = rwlock.read {
|
||||
ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { CompileService.CallResult.Good(body()) }
|
||||
}
|
||||
@@ -513,6 +598,10 @@ class CompileServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private fun<R> ifAlive_Raw(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> CompileService.CallResult<R>): CompileService.CallResult<R> = rwlock.read {
|
||||
ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { body() }
|
||||
}
|
||||
|
||||
private fun<R> ifAliveExclusive(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult<R> = rwlock.write {
|
||||
ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { CompileService.CallResult.Good(body()) }
|
||||
}
|
||||
|
||||
@@ -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<File>,
|
||||
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<File>, 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<T> 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<ReplCodeLine>): 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<ReplCodeLine>): 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<ReplCodeLine>): 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
// "<StandardScriptTemplate>", 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
|
||||
else absolutePath
|
||||
|
||||
open class TestKotlinScriptDummyDependenciesResolver : ScriptDependenciesResolver {
|
||||
|
||||
private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() }
|
||||
|
||||
override fun resolve(script: ScriptContents,
|
||||
environment: Map<String, Any?>?,
|
||||
report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit,
|
||||
previousDependencies: KotlinScriptExternalDependencies?
|
||||
): Future<KotlinScriptExternalDependencies?>
|
||||
{
|
||||
return object : KotlinScriptExternalDependencies {
|
||||
override val classpath: Iterable<File> = classpathFromClassloader()
|
||||
override val imports: Iterable<String> = listOf("org.jetbrains.kotlin.scripts.DependsOn", "org.jetbrains.kotlin.scripts.DependsOnTwo")
|
||||
}.asFuture()
|
||||
}
|
||||
}
|
||||
|
||||
@ScriptTemplateDefinition(resolver = TestKotlinScriptDummyDependenciesResolver::class)
|
||||
abstract class ScriptWithNoParam()
|
||||
|
||||
internal fun classpathFromClassloader(): List<File> =
|
||||
(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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user