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.read
|
||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>) : ReplCompiledEvaluator {
|
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, baseClassloader: ClassLoader?) : ReplCompiledEvaluator {
|
||||||
|
|
||||||
private val classpath = baseClasspath.toMutableList()
|
|
||||||
|
|
||||||
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader =
|
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 val classLoaderLock = ReentrantReadWriteLock()
|
||||||
|
|
||||||
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
|
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
|
||||||
|
|
||||||
private val compiledLoadedClassesHistory = arrayListOf<Pair<ReplCodeLine, ClassWithInstance>>()
|
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 {
|
checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
|
||||||
return@eval ReplEvalResult.HistoryMismatch(it)
|
return@eval ReplEvalResult.HistoryMismatch(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
classLoaderLock.read {
|
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") }
|
compiledClasses.filter { it.path.endsWith(".class") }
|
||||||
.forEach {
|
.forEach {
|
||||||
classLoader.addClass(JvmClassName.byInternalName(it.path.replaceFirst("\\.class$".toRegex(), "")), it.bytes)
|
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
|
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 {
|
companion object {
|
||||||
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,38 +16,62 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.cli.common.repl
|
package org.jetbrains.kotlin.cli.common.repl
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.util.*
|
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 {
|
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 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)
|
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 {
|
sealed class ReplCheckResult : Serializable {
|
||||||
object Ok : ReplCheckResult()
|
object Ok : ReplCheckResult()
|
||||||
object Incomplete : 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 {
|
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()
|
object Incomplete : ReplCompileResult()
|
||||||
class HistoryMismatch(val lineNo: Int): 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 {
|
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 UnitResult : ReplEvalResult()
|
||||||
object Incomplete : ReplEvalResult()
|
object Incomplete : ReplEvalResult()
|
||||||
class HistoryMismatch(val lineNo: Int): ReplEvalResult()
|
class HistoryMismatch(val lineNo: Int): ReplEvalResult()
|
||||||
sealed class Error(val message: String) : ReplEvalResult() {
|
sealed class Error(val message: String) : ReplEvalResult() {
|
||||||
class Runtime(message: String) : Error(message)
|
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 {
|
interface ReplCompiler : ReplChecker {
|
||||||
fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult
|
fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult
|
||||||
|
|
||||||
// a callback
|
|
||||||
fun dependenciesAdded(classpath: List<File>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReplCompiledEvaluator {
|
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
|
// override to capture output
|
||||||
fun<T> evalWithIO(body: () -> T): T = body()
|
fun<T> evalWithIO(body: () -> T): T = body()
|
||||||
@@ -75,8 +96,6 @@ interface ReplEvaluator : ReplChecker {
|
|||||||
|
|
||||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult
|
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult
|
||||||
|
|
||||||
fun dependenciesAdded(classpath: List<File>)
|
|
||||||
|
|
||||||
// override to capture output
|
// override to capture output
|
||||||
fun<T> evalWithIO(body: () -> T): T = body()
|
fun<T> evalWithIO(body: () -> T): T = body()
|
||||||
}
|
}
|
||||||
@@ -77,8 +77,6 @@ open class GenericReplChecker(
|
|||||||
|
|
||||||
protected var lineState: LineState? = null
|
protected var lineState: LineState? = null
|
||||||
|
|
||||||
val classpath: MutableList<File> = compilerConfiguration.jvmClasspathRoots.toMutableList()
|
|
||||||
|
|
||||||
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
||||||
|
|
||||||
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
||||||
@@ -108,7 +106,7 @@ open class GenericReplChecker(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class GenericReplCompiler(
|
open class GenericReplCompiler(
|
||||||
disposable: Disposable,
|
disposable: Disposable,
|
||||||
scriptDefinition: KotlinScriptDefinition,
|
scriptDefinition: KotlinScriptDefinition,
|
||||||
compilerConfiguration: CompilerConfiguration,
|
compilerConfiguration: CompilerConfiguration,
|
||||||
@@ -132,7 +130,7 @@ abstract class GenericReplCompiler(
|
|||||||
val res = check(codeLine, history)
|
val res = check(codeLine, history)
|
||||||
when (res) {
|
when (res) {
|
||||||
ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete
|
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
|
ReplCheckResult.Ok -> {} // continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,14 +138,6 @@ abstract class GenericReplCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies)
|
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) {
|
if (lastDependencies != newDependencies) {
|
||||||
lastDependencies = newDependencies
|
lastDependencies = newDependencies
|
||||||
}
|
}
|
||||||
@@ -179,7 +169,9 @@ abstract class GenericReplCompiler(
|
|||||||
|
|
||||||
descriptorsHistory.add(codeLine to scriptDescriptor)
|
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,
|
disposable: Disposable,
|
||||||
scriptDefinition: KotlinScriptDefinition,
|
scriptDefinition: KotlinScriptDefinition,
|
||||||
compilerConfiguration: CompilerConfiguration,
|
compilerConfiguration: CompilerConfiguration,
|
||||||
messageCollector: MessageCollector
|
messageCollector: MessageCollector,
|
||||||
|
baseClassloader: ClassLoader?
|
||||||
) : ReplEvaluator, GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
|
) : 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 {
|
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult = synchronized(this) {
|
||||||
synchronized(this) {
|
return compileAndEval(this, compiledEvaluator, codeLine, history)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
disposable: Disposable,
|
||||||
private val factory: ScriptEngineFactory,
|
private val factory: ScriptEngineFactory,
|
||||||
private val scriptDefinition: KotlinScriptDefinition,
|
private val scriptDefinition: KotlinScriptDefinition,
|
||||||
private val compilerConfiguration: CompilerConfiguration
|
private val compilerConfiguration: CompilerConfiguration,
|
||||||
|
baseClassLoader: ClassLoader
|
||||||
) : AbstractScriptEngine(), ScriptEngine {
|
) : AbstractScriptEngine(), ScriptEngine {
|
||||||
|
|
||||||
data class MessageCollectorReport(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation)
|
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
|
private var lineCount = 0
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -49,7 +49,8 @@ class KotlinJsr232StandardScriptEngineFactory: ScriptEngineFactory {
|
|||||||
// TODO: addJvmClasspathRoots(config.classpath)
|
// TODO: addJvmClasspathRoots(config.classpath)
|
||||||
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script")
|
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script")
|
||||||
put(JVMConfigurationKeys.INCLUDE_RUNTIME, true)
|
put(JVMConfigurationKeys.INCLUDE_RUNTIME, true)
|
||||||
})
|
},
|
||||||
|
Thread.currentThread().contextClassLoader)
|
||||||
|
|
||||||
override fun getOutputStatement(toDisplay: String?): String = "print(\"$toDisplay\")"
|
override fun getOutputStatement(toDisplay: String?): String = "print(\"$toDisplay\")"
|
||||||
override fun getMethodCallSyntax(obj: String, m: String, vararg args: String): String = "$obj.$m(${args.joinToString()})"
|
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="frontend.java" />
|
||||||
<orderEntry type="module" module-name="util" />
|
<orderEntry type="module" module-name="util" />
|
||||||
<orderEntry type="library" name="native-platform-uberjar" level="project" />
|
<orderEntry type="library" name="native-platform-uberjar" level="project" />
|
||||||
|
<orderEntry type="module" module-name="cli-common" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
+6
-13
@@ -31,7 +31,6 @@ import java.util.concurrent.TimeUnit
|
|||||||
import kotlin.comparisons.compareByDescending
|
import kotlin.comparisons.compareByDescending
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
class CompilationServices(
|
class CompilationServices(
|
||||||
val incrementalCompilationComponents: IncrementalCompilationComponents? = null,
|
val incrementalCompilationComponents: IncrementalCompilationComponents? = null,
|
||||||
val compilationCanceledStatus: CompilationCanceledStatus? = null
|
val compilationCanceledStatus: CompilationCanceledStatus? = null
|
||||||
@@ -53,18 +52,12 @@ object KotlinCompilerClient {
|
|||||||
autostart: Boolean = true,
|
autostart: Boolean = true,
|
||||||
checkId: Boolean = true
|
checkId: Boolean = true
|
||||||
): CompileService? {
|
): 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)
|
val flagFile = System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)
|
||||||
?.let { it.trimQuotes() }
|
?.let(String::trimQuotes)
|
||||||
?.check { !it.isBlank() }
|
?.check { !it.isBlank() }
|
||||||
?.let { File(it) }
|
?.let(::File)
|
||||||
?.check { it.exists() }
|
?.check(File::exists)
|
||||||
?: newFlagFile()
|
?: makeAutodeletingFlagFile()
|
||||||
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart)
|
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +280,7 @@ object KotlinCompilerClient {
|
|||||||
val daemonLauncher = Native.get(ProcessLauncher::class.java)
|
val daemonLauncher = Native.get(ProcessLauncher::class.java)
|
||||||
val daemon = daemonLauncher.start(processBuilder)
|
val daemon = daemonLauncher.start(processBuilder)
|
||||||
|
|
||||||
var isEchoRead = Semaphore(1)
|
val isEchoRead = Semaphore(1)
|
||||||
isEchoRead.acquire()
|
isEchoRead.acquire()
|
||||||
|
|
||||||
val stdoutThread =
|
val stdoutThread =
|
||||||
@@ -316,7 +309,7 @@ object KotlinCompilerClient {
|
|||||||
it.toLong()
|
it.toLong()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
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
|
null
|
||||||
}
|
}
|
||||||
} ?: DAEMON_DEFAULT_STARTUP_TIMEOUT_MS
|
} ?: 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
|
package org.jetbrains.kotlin.daemon.common
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
|
import java.io.File
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.rmi.Remote
|
import java.rmi.Remote
|
||||||
import java.rmi.RemoteException
|
import java.rmi.RemoteException
|
||||||
@@ -44,7 +46,7 @@ interface CompileService : Remote {
|
|||||||
override fun hashCode(): Int = this.javaClass.hashCode() + (result?.hashCode() ?: 1)
|
override fun hashCode(): Int = this.javaClass.hashCode() + (result?.hashCode() ?: 1)
|
||||||
}
|
}
|
||||||
class Ok : CallResult<Nothing>() {
|
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 equals(other: Any?): Boolean = other is Ok
|
||||||
override fun hashCode(): Int = this.javaClass.hashCode() + 1 // avoiding clash with the hash of class itself
|
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,
|
serviceOutputStream: RemoteOutputStream,
|
||||||
operationsTracer: RemoteOperationsTracer?
|
operationsTracer: RemoteOperationsTracer?
|
||||||
): CallResult<Int>
|
): 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
|
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.ZipHandler
|
||||||
import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
|
import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
|
||||||
import org.jetbrains.kotlin.cli.common.CLICompiler
|
import org.jetbrains.kotlin.cli.common.CLICompiler
|
||||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||||
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
|
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.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.config.Services
|
import org.jetbrains.kotlin.config.Services
|
||||||
import org.jetbrains.kotlin.daemon.common.*
|
import org.jetbrains.kotlin.daemon.common.*
|
||||||
@@ -45,6 +51,8 @@ import kotlin.concurrent.read
|
|||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
|
const val REMOTE_STREAM_BUFFER_SIZE = 4096
|
||||||
|
|
||||||
fun nowSeconds() = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime())
|
fun nowSeconds() = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime())
|
||||||
|
|
||||||
interface CompilerSelector {
|
interface CompilerSelector {
|
||||||
@@ -83,10 +91,16 @@ class CompileServiceImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wrapped in a class to encapsulate alive check logic
|
// 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 registered = nowSeconds()
|
||||||
val secondsSinceRegistered: Long get() = nowSeconds() - registered
|
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
|
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)
|
private val sessionsIdCounter = AtomicInteger(0)
|
||||||
@@ -103,8 +117,8 @@ class CompileServiceImpl(
|
|||||||
// TODO: encapsulate operations on state here
|
// TODO: encapsulate operations on state here
|
||||||
private val state = object {
|
private val state = object {
|
||||||
|
|
||||||
val clientProxies: MutableSet<ClientOrSessionProxy> = hashSetOf()
|
val clientProxies: MutableSet<ClientOrSessionProxy<Any>> = hashSetOf()
|
||||||
val sessions: MutableMap<Int, ClientOrSessionProxy> = hashMapOf()
|
val sessions: MutableMap<Int, ClientOrSessionProxy<Any>> = hashMapOf()
|
||||||
|
|
||||||
val delayedShutdownQueued = AtomicBoolean(false)
|
val delayedShutdownQueued = AtomicBoolean(false)
|
||||||
|
|
||||||
@@ -155,16 +169,20 @@ class CompileServiceImpl(
|
|||||||
|
|
||||||
// TODO: consider tying a session to a client and use this info to cleanup
|
// 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) {
|
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
|
// fighting hypothetical integer wrapping
|
||||||
var newId = sessionsIdCounter.incrementAndGet()
|
var newId = sessionsIdCounter.incrementAndGet()
|
||||||
val session = ClientOrSessionProxy(aliveFlagPath)
|
|
||||||
for (attempt in 1..100) {
|
for (attempt in 1..100) {
|
||||||
if (newId != CompileService.NO_SESSION) {
|
if (newId != CompileService.NO_SESSION) {
|
||||||
synchronized(state.sessions) {
|
synchronized(state.sessions) {
|
||||||
if (!state.sessions.containsKey(newId)) {
|
if (!state.sessions.containsKey(newId)) {
|
||||||
state.sessions.put(newId, session)
|
state.sessions.put(newId, session)
|
||||||
log.info("leased a new session $newId, client alive file: $aliveFlagPath")
|
return newId
|
||||||
return@ifAlive newId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +194,7 @@ class CompileServiceImpl(
|
|||||||
|
|
||||||
override fun releaseCompileSession(sessionId: Int) = ifAlive_Nothing(minAliveness = Aliveness.LastSession) {
|
override fun releaseCompileSession(sessionId: Int) = ifAlive_Nothing(minAliveness = Aliveness.LastSession) {
|
||||||
synchronized(state.sessions) {
|
synchronized(state.sessions) {
|
||||||
|
state.sessions[sessionId]?.dispose()
|
||||||
state.sessions.remove(sessionId)
|
state.sessions.remove(sessionId)
|
||||||
log.info("cleaning after session $sessionId")
|
log.info("cleaning after session $sessionId")
|
||||||
clearJarCache()
|
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
|
// internal implementation stuff
|
||||||
|
|
||||||
// TODO: consider matching compilerId coming from outside with actual one
|
// TODO: consider matching compilerId coming from outside with actual one
|
||||||
@@ -302,19 +379,31 @@ class CompileServiceImpl(
|
|||||||
shutdown()
|
shutdown()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
var anyDead = false
|
||||||
|
var shuttingDown = false
|
||||||
|
|
||||||
synchronized(state.sessions) {
|
synchronized(state.sessions) {
|
||||||
// 2. check if any session hanged - clean
|
// 2. check if any session hanged - clean
|
||||||
// making copy of the list before calling release
|
state.sessions.filterValues { !it.isAlive }.apply { anyDead = true }.forEach {
|
||||||
state.sessions.filterValues { !it.isAlive }.keys.toList()
|
it.value.dispose()
|
||||||
}.forEach { releaseCompileSession(it) }
|
state.sessions.remove(it.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. check if in graceful shutdown state and all sessions are closed
|
// 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")) {
|
if (shutdownCondition({ state.alive.get() == Aliveness.LastSession.ordinal && state.sessions.none()}, "All sessions finished, shutting down")) {
|
||||||
shutdown()
|
shutdown()
|
||||||
|
shuttingDown = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. clean dead clients, then check if any left - conditional shutdown (with small delay)
|
// 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()) {
|
if (state.clientProxies.isEmpty() && compilationsCounter.get() > 0 && !state.delayedShutdownQueued.get()) {
|
||||||
log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
|
log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
|
||||||
shutdownWithDelay()
|
shutdownWithDelay()
|
||||||
@@ -326,8 +415,14 @@ class CompileServiceImpl(
|
|||||||
shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") ||
|
shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") ||
|
||||||
// 7. compiler changed (seldom check) - shutdown
|
// 7. compiler changed (seldom check) - shutdown
|
||||||
// TODO: could be too expensive anyway, consider removing this check
|
// TODO: could be too expensive anyway, consider removing this check
|
||||||
shutdownCondition({ classpathWatcher.isChanged }, "Compiler changed")) {
|
shutdownCondition({ classpathWatcher.isChanged }, "Compiler changed"))
|
||||||
|
{
|
||||||
shutdown()
|
shutdown()
|
||||||
|
shuttingDown = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyDead && !shuttingDown) {
|
||||||
|
clearJarCache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,6 +469,7 @@ class CompileServiceImpl(
|
|||||||
private fun shutdownImpl() {
|
private fun shutdownImpl() {
|
||||||
log.info("Shutdown started")
|
log.info("Shutdown started")
|
||||||
state.alive.set(Aliveness.Dying.ordinal)
|
state.alive.set(Aliveness.Dying.ordinal)
|
||||||
|
|
||||||
UnicastRemoteObject.unexportObject(this, true)
|
UnicastRemoteObject.unexportObject(this, true)
|
||||||
log.info("Shutdown complete")
|
log.info("Shutdown complete")
|
||||||
onShutdown()
|
onShutdown()
|
||||||
@@ -414,8 +510,8 @@ class CompileServiceImpl(
|
|||||||
compilationsCounter.incrementAndGet()
|
compilationsCounter.incrementAndGet()
|
||||||
val rpcProfiler = if (daemonOptions.reportPerf) WallAndThreadTotalProfiler() else DummyProfiler()
|
val rpcProfiler = if (daemonOptions.reportPerf) WallAndThreadTotalProfiler() else DummyProfiler()
|
||||||
val eventManger = EventMangerImpl()
|
val eventManger = EventMangerImpl()
|
||||||
val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesStreamProxy, rpcProfiler), 4096))
|
val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesStreamProxy, rpcProfiler), REMOTE_STREAM_BUFFER_SIZE))
|
||||||
val serviceOutputStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(serviceOutputStreamProxy, rpcProfiler), 4096))
|
val serviceOutputStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(serviceOutputStreamProxy, rpcProfiler), REMOTE_STREAM_BUFFER_SIZE))
|
||||||
try {
|
try {
|
||||||
checkedCompile(args, serviceOutputStream, rpcProfiler) {
|
checkedCompile(args, serviceOutputStream, rpcProfiler) {
|
||||||
val res = body(compilerMessagesStream, eventManger, rpcProfiler).code
|
val res = body(compilerMessagesStream, eventManger, rpcProfiler).code
|
||||||
@@ -490,17 +586,6 @@ class CompileServiceImpl(
|
|||||||
(KotlinCoreEnvironment.applicationEnvironment?.jarFileSystem as? CoreJarFileSystem)?.clearHandlersCache()
|
(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 {
|
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()) }
|
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 {
|
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()) }
|
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
|
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.cli.AbstractCliTest
|
||||||
import org.jetbrains.kotlin.daemon.client.CompilerCallbackServicesFacadeServer
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
|
import org.jetbrains.kotlin.daemon.client.*
|
||||||
import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient
|
|
||||||
import org.jetbrains.kotlin.daemon.client.RemoteOutputStreamServer
|
|
||||||
import org.jetbrains.kotlin.daemon.common.*
|
import org.jetbrains.kotlin.daemon.common.*
|
||||||
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
||||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||||
@@ -29,8 +29,11 @@ import org.jetbrains.kotlin.test.KotlinTestUtils
|
|||||||
import org.jetbrains.kotlin.utils.PathUtil
|
import org.jetbrains.kotlin.utils.PathUtil
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
import java.net.URLClassLoader
|
||||||
import java.rmi.server.UnicastRemoteObject
|
import java.rmi.server.UnicastRemoteObject
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.Future
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
@@ -81,7 +84,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
|||||||
|
|
||||||
val logFile = createTempFile("kotlin-daemon-test.", ".log")
|
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)
|
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||||
var daemonShotDown = false
|
var daemonShotDown = false
|
||||||
|
|
||||||
@@ -159,11 +162,11 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
|||||||
val logFile1 = createTempFile("kotlin-daemon1-test", ".log")
|
val logFile1 = createTempFile("kotlin-daemon1-test", ".log")
|
||||||
val logFile2 = createTempFile("kotlin-daemon2-test", ".log")
|
val logFile2 = createTempFile("kotlin-daemon2-test", ".log")
|
||||||
val daemonJVMOptions1 =
|
val daemonJVMOptions1 =
|
||||||
configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile1.loggerCompatiblePath}\"",
|
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile1.loggerCompatiblePath}\"",
|
||||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||||
|
|
||||||
val daemonJVMOptions2 =
|
val daemonJVMOptions2 =
|
||||||
configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile2.loggerCompatiblePath}\"",
|
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile2.loggerCompatiblePath}\"",
|
||||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||||
|
|
||||||
assertTrue(logFile1.length() == 0L && logFile2.length() == 0L)
|
assertTrue(logFile1.length() == 0L && logFile2.length() == 0L)
|
||||||
@@ -198,7 +201,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
|||||||
|
|
||||||
val logFile = createTempFile("kotlin-daemon-test", ".log")
|
val logFile = createTempFile("kotlin-daemon-test", ".log")
|
||||||
val daemonJVMOptions =
|
val daemonJVMOptions =
|
||||||
configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"",
|
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"",
|
||||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||||
|
|
||||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
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 logFile = createTempFile("kotlin-daemon-test", ".log")
|
||||||
val daemonJVMOptions =
|
val daemonJVMOptions =
|
||||||
configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"",
|
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"",
|
||||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||||
|
|
||||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
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 logFile = createTempFile("kotlin-daemon-test", ".log")
|
||||||
val daemonJVMOptions =
|
val daemonJVMOptions =
|
||||||
configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"",
|
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"",
|
||||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||||
|
|
||||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
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 jar = tmpdir.absolutePath + File.separator + "hello.jar"
|
||||||
val args = listOf(
|
val args = listOf(
|
||||||
File(File(System.getProperty("java.home"), "bin"), "java").absolutePath,
|
File(File(System.getProperty("java.home"), "bin"), "java").absolutePath,
|
||||||
"-D${COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY}",
|
"-D$COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY",
|
||||||
"-cp",
|
"-cp",
|
||||||
daemonClientClassPath.joinToString(File.pathSeparator) { it.absolutePath },
|
daemonClientClassPath.joinToString(File.pathSeparator) { it.absolutePath },
|
||||||
KotlinCompilerClient::class.qualifiedName!!) +
|
KotlinCompilerClient::class.qualifiedName!!) +
|
||||||
@@ -430,7 +433,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
|||||||
|
|
||||||
val logFile = createTempFile("kotlin-daemon-test", ".log")
|
val logFile = createTempFile("kotlin-daemon-test", ".log")
|
||||||
val daemonJVMOptions =
|
val daemonJVMOptions =
|
||||||
configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"",
|
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"",
|
||||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||||
|
|
||||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||||
@@ -467,8 +470,69 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
|||||||
logFile.delete()
|
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
|
// stolen from CompilerFileLimitTest
|
||||||
internal fun generateLargeKotlinFile(size: Int): String {
|
internal fun generateLargeKotlinFile(size: Int): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
@@ -537,4 +601,39 @@ internal inline fun withFlagFile(prefix: String, suffix: String? = null, body: (
|
|||||||
private val File.loggerCompatiblePath: String
|
private val File.loggerCompatiblePath: String
|
||||||
get() =
|
get() =
|
||||||
if (OSKind.current == OSKind.Windows) absolutePath.replace('\\', '/')
|
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