Add remote repl support to the daemon, refactor repl classes accordingly, simple tests

This commit is contained in:
Ilya Chernikov
2016-09-16 17:38:00 +02:00
parent 042fc4fadc
commit 86ece30330
12 changed files with 648 additions and 111 deletions
@@ -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>
@@ -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
@@ -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()
}
}
@@ -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")
}
}
}