Add remote repl support to the daemon, refactor repl classes accordingly, simple tests
This commit is contained in:
@@ -11,5 +11,6 @@
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
<orderEntry type="library" name="native-platform-uberjar" level="project" />
|
||||
<orderEntry type="module" module-name="cli-common" />
|
||||
</component>
|
||||
</module>
|
||||
+6
-13
@@ -31,7 +31,6 @@ import java.util.concurrent.TimeUnit
|
||||
import kotlin.comparisons.compareByDescending
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
|
||||
class CompilationServices(
|
||||
val incrementalCompilationComponents: IncrementalCompilationComponents? = null,
|
||||
val compilationCanceledStatus: CompilationCanceledStatus? = null
|
||||
@@ -53,18 +52,12 @@ object KotlinCompilerClient {
|
||||
autostart: Boolean = true,
|
||||
checkId: Boolean = true
|
||||
): CompileService? {
|
||||
fun newFlagFile(): File {
|
||||
val flagFile = File.createTempFile("kotlin-compiler-client-", "-is-running")
|
||||
flagFile.deleteOnExit()
|
||||
return flagFile
|
||||
}
|
||||
|
||||
val flagFile = System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)
|
||||
?.let { it.trimQuotes() }
|
||||
?.let(String::trimQuotes)
|
||||
?.check { !it.isBlank() }
|
||||
?.let { File(it) }
|
||||
?.check { it.exists() }
|
||||
?: newFlagFile()
|
||||
?.let(::File)
|
||||
?.check(File::exists)
|
||||
?: makeAutodeletingFlagFile()
|
||||
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart)
|
||||
}
|
||||
|
||||
@@ -287,7 +280,7 @@ object KotlinCompilerClient {
|
||||
val daemonLauncher = Native.get(ProcessLauncher::class.java)
|
||||
val daemon = daemonLauncher.start(processBuilder)
|
||||
|
||||
var isEchoRead = Semaphore(1)
|
||||
val isEchoRead = Semaphore(1)
|
||||
isEchoRead.acquire()
|
||||
|
||||
val stdoutThread =
|
||||
@@ -316,7 +309,7 @@ object KotlinCompilerClient {
|
||||
it.toLong()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
reportingTargets.report(DaemonReportCategory.INFO, "unable to interpret ${COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY} property ('$it'); using default timeout $DAEMON_DEFAULT_STARTUP_TIMEOUT_MS ms")
|
||||
reportingTargets.report(DaemonReportCategory.INFO, "unable to interpret $COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY property ('$it'); using default timeout $DAEMON_DEFAULT_STARTUP_TIMEOUT_MS ms")
|
||||
null
|
||||
}
|
||||
} ?: DAEMON_DEFAULT_STARTUP_TIMEOUT_MS
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.daemon.client
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.cli.common.repl.*
|
||||
import org.jetbrains.kotlin.daemon.common.CompileService
|
||||
import org.jetbrains.kotlin.daemon.common.RemoteOperationsTracer
|
||||
import org.jetbrains.kotlin.daemon.common.SOCKET_ANY_FREE_PORT
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
// TODO: reduce number of ports used then SOCKET_ANY_FREE_PORT is passed (same problem with other calls)
|
||||
|
||||
open class KotlinRemoteReplClientBase(
|
||||
disposable: Disposable,
|
||||
protected val compileService: CompileService,
|
||||
clientAliveFlagFile: File?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
compilerMessagesOutputStream: OutputStream,
|
||||
evalOutputStream: OutputStream? = null,
|
||||
evalErrorStream: OutputStream? = null,
|
||||
evalInputStream: InputStream? = null,
|
||||
port: Int = SOCKET_ANY_FREE_PORT,
|
||||
operationsTracer: RemoteOperationsTracer? = null
|
||||
) : ReplChecker {
|
||||
|
||||
val sessionId = compileService.leaseReplSession(
|
||||
clientAliveFlagFile?.absolutePath,
|
||||
targetPlatform,
|
||||
CompilerCallbackServicesFacadeServer(port = port),
|
||||
templateClasspath,
|
||||
templateClassName,
|
||||
RemoteOutputStreamServer(compilerMessagesOutputStream, port),
|
||||
evalOutputStream?.let { RemoteOutputStreamServer(it, port) },
|
||||
evalErrorStream?.let { RemoteOutputStreamServer(it, port) },
|
||||
evalInputStream?.let { RemoteInputStreamServer(it, port) },
|
||||
operationsTracer
|
||||
).get()
|
||||
|
||||
init {
|
||||
Disposer.register(disposable, Disposable {
|
||||
compileService.releaseReplSession(sessionId)
|
||||
})
|
||||
}
|
||||
|
||||
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
||||
return compileService.remoteReplLineCheck(sessionId, codeLine, history.toList()).get()
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinRemoteReplCompiler(
|
||||
disposable: Disposable,
|
||||
compileService: CompileService,
|
||||
clientAliveFlagFile: File?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
compilerMessagesOutputStream: OutputStream,
|
||||
port: Int = SOCKET_ANY_FREE_PORT,
|
||||
operationsTracer: RemoteOperationsTracer? = null
|
||||
) : KotlinRemoteReplClientBase(
|
||||
disposable = disposable,
|
||||
compileService = compileService,
|
||||
clientAliveFlagFile = clientAliveFlagFile,
|
||||
targetPlatform = targetPlatform,
|
||||
templateClasspath = templateClasspath,
|
||||
templateClassName = templateClassName,
|
||||
compilerMessagesOutputStream = compilerMessagesOutputStream,
|
||||
evalOutputStream = null,
|
||||
evalErrorStream = null,
|
||||
evalInputStream = null,
|
||||
port = port,
|
||||
operationsTracer = operationsTracer
|
||||
), ReplCompiler {
|
||||
|
||||
override fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult {
|
||||
return compileService.remoteReplLineCompile(sessionId, codeLine, history.toList()).get()
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinRemoteReplEvaluator(
|
||||
disposable: Disposable,
|
||||
compileService: CompileService,
|
||||
clientAliveFlagFile: File?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
compilerMessagesOutputStream: OutputStream,
|
||||
evalOutputStream: OutputStream?,
|
||||
evalErrorStream: OutputStream?,
|
||||
evalInputStream: InputStream?,
|
||||
port: Int = SOCKET_ANY_FREE_PORT,
|
||||
operationsTracer: RemoteOperationsTracer? = null
|
||||
) : KotlinRemoteReplClientBase(
|
||||
disposable = disposable,
|
||||
compileService = compileService,
|
||||
clientAliveFlagFile = clientAliveFlagFile,
|
||||
targetPlatform = targetPlatform,
|
||||
templateClasspath = templateClasspath,
|
||||
templateClassName = templateClassName,
|
||||
compilerMessagesOutputStream = compilerMessagesOutputStream,
|
||||
evalOutputStream = evalOutputStream,
|
||||
evalErrorStream = evalErrorStream,
|
||||
evalInputStream = evalInputStream,
|
||||
port = port,
|
||||
operationsTracer = operationsTracer
|
||||
), ReplEvaluator {
|
||||
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult {
|
||||
return compileService.remoteReplLineEval(sessionId, codeLine, history.toList()).get()
|
||||
}
|
||||
}
|
||||
+41
-1
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.daemon.common
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.repl.*
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.rmi.Remote
|
||||
import java.rmi.RemoteException
|
||||
@@ -44,7 +46,7 @@ interface CompileService : Remote {
|
||||
override fun hashCode(): Int = this.javaClass.hashCode() + (result?.hashCode() ?: 1)
|
||||
}
|
||||
class Ok : CallResult<Nothing>() {
|
||||
override fun get(): Nothing = throw IllegalStateException("Gey is inapplicable to Ok call result")
|
||||
override fun get(): Nothing = throw IllegalStateException("Get is inapplicable to Ok call result")
|
||||
override fun equals(other: Any?): Boolean = other is Ok
|
||||
override fun hashCode(): Int = this.javaClass.hashCode() + 1 // avoiding clash with the hash of class itself
|
||||
}
|
||||
@@ -122,4 +124,42 @@ interface CompileService : Remote {
|
||||
serviceOutputStream: RemoteOutputStream,
|
||||
operationsTracer: RemoteOperationsTracer?
|
||||
): CallResult<Int>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun leaseReplSession(
|
||||
aliveFlagPath: String?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
servicesFacade: CompilerCallbackServicesFacade,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
compilerMessagesOutputStream: RemoteOutputStream,
|
||||
evalOutputStream: RemoteOutputStream?,
|
||||
evalErrorStream: RemoteOutputStream?,
|
||||
evalInputStream: RemoteInputStream?,
|
||||
operationsTracer: RemoteOperationsTracer?
|
||||
): CallResult<Int>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun releaseReplSession(sessionId: Int): CallResult<Nothing>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineCheck(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CallResult<ReplCheckResult>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineCompile(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CallResult<ReplCompileResult>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineEval(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CallResult<ReplEvalResult>
|
||||
}
|
||||
|
||||
@@ -16,11 +16,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.daemon
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.impl.ZipHandler
|
||||
import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
|
||||
import org.jetbrains.kotlin.cli.common.CLICompiler
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
|
||||
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
|
||||
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
||||
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
|
||||
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.Services
|
||||
import org.jetbrains.kotlin.daemon.common.*
|
||||
@@ -45,6 +51,8 @@ import kotlin.concurrent.read
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.concurrent.write
|
||||
|
||||
const val REMOTE_STREAM_BUFFER_SIZE = 4096
|
||||
|
||||
fun nowSeconds() = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime())
|
||||
|
||||
interface CompilerSelector {
|
||||
@@ -83,10 +91,16 @@ class CompileServiceImpl(
|
||||
}
|
||||
|
||||
// wrapped in a class to encapsulate alive check logic
|
||||
private class ClientOrSessionProxy(val aliveFlagPath: String?) {
|
||||
private class ClientOrSessionProxy<out T: Any>(val aliveFlagPath: String?, val data: T? = null, private var disposable: Disposable? = null) {
|
||||
val registered = nowSeconds()
|
||||
val secondsSinceRegistered: Long get() = nowSeconds() - registered
|
||||
val isAlive: Boolean get() = aliveFlagPath?.let { File(it).exists() } ?: true // assuming that if no file was given, the client is alive
|
||||
fun dispose() {
|
||||
disposable?.let {
|
||||
Disposer.dispose(it)
|
||||
disposable = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val sessionsIdCounter = AtomicInteger(0)
|
||||
@@ -103,8 +117,8 @@ class CompileServiceImpl(
|
||||
// TODO: encapsulate operations on state here
|
||||
private val state = object {
|
||||
|
||||
val clientProxies: MutableSet<ClientOrSessionProxy> = hashSetOf()
|
||||
val sessions: MutableMap<Int, ClientOrSessionProxy> = hashMapOf()
|
||||
val clientProxies: MutableSet<ClientOrSessionProxy<Any>> = hashSetOf()
|
||||
val sessions: MutableMap<Int, ClientOrSessionProxy<Any>> = hashMapOf()
|
||||
|
||||
val delayedShutdownQueued = AtomicBoolean(false)
|
||||
|
||||
@@ -155,16 +169,20 @@ class CompileServiceImpl(
|
||||
|
||||
// TODO: consider tying a session to a client and use this info to cleanup
|
||||
override fun leaseCompileSession(aliveFlagPath: String?): CompileService.CallResult<Int> = ifAlive(minAliveness = Aliveness.Alive) {
|
||||
leaseSessionImpl(ClientOrSessionProxy<Any>(aliveFlagPath)).apply {
|
||||
log.info("leased a new session $this, client alive file: $aliveFlagPath")
|
||||
}
|
||||
}
|
||||
|
||||
private fun<T: Any> leaseSessionImpl(session: ClientOrSessionProxy<T>): Int {
|
||||
// fighting hypothetical integer wrapping
|
||||
var newId = sessionsIdCounter.incrementAndGet()
|
||||
val session = ClientOrSessionProxy(aliveFlagPath)
|
||||
for (attempt in 1..100) {
|
||||
if (newId != CompileService.NO_SESSION) {
|
||||
synchronized(state.sessions) {
|
||||
if (!state.sessions.containsKey(newId)) {
|
||||
state.sessions.put(newId, session)
|
||||
log.info("leased a new session $newId, client alive file: $aliveFlagPath")
|
||||
return@ifAlive newId
|
||||
return newId
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,6 +194,7 @@ class CompileServiceImpl(
|
||||
|
||||
override fun releaseCompileSession(sessionId: Int) = ifAlive_Nothing(minAliveness = Aliveness.LastSession) {
|
||||
synchronized(state.sessions) {
|
||||
state.sessions[sessionId]?.dispose()
|
||||
state.sessions.remove(sessionId)
|
||||
log.info("cleaning after session $sessionId")
|
||||
clearJarCache()
|
||||
@@ -248,6 +267,64 @@ class CompileServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun leaseReplSession(
|
||||
aliveFlagPath: String?,
|
||||
targetPlatform: CompileService.TargetPlatform,
|
||||
servicesFacade: CompilerCallbackServicesFacade,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
compilerMessagesOutputStream: RemoteOutputStream,
|
||||
evalOutputStream: RemoteOutputStream?,
|
||||
evalErrorStream: RemoteOutputStream?,
|
||||
evalInputStream: RemoteInputStream?,
|
||||
operationsTracer: RemoteOperationsTracer?
|
||||
): CompileService.CallResult<Int> = ifAlive_Raw(minAliveness = Aliveness.Alive) {
|
||||
if (targetPlatform != CompileService.TargetPlatform.JVM)
|
||||
CompileService.CallResult.Error("Sorry, only JVM target platform is supported now")
|
||||
else {
|
||||
val disposable = Disposer.newDisposable()
|
||||
val repl = KotlinJvmReplService(disposable, templateClasspath, templateClassName, compilerMessagesOutputStream, evalOutputStream, evalErrorStream, evalInputStream, operationsTracer)
|
||||
val sessionId = leaseSessionImpl(ClientOrSessionProxy(aliveFlagPath, repl, disposable))
|
||||
|
||||
CompileService.CallResult.Good(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun<R> withValidRepl(sessionId: Int, body: KotlinJvmReplService.() -> R): CompileService.CallResult<R> =
|
||||
state.sessions[sessionId]?.let {
|
||||
(it.data as? KotlinJvmReplService?)?.let {
|
||||
CompileService.CallResult.Good(it.body())
|
||||
}
|
||||
} ?: CompileService.CallResult.Error("Unknown or invalid session $sessionId")
|
||||
|
||||
// TODO: add more checks (e.g. is it a repl session)
|
||||
override fun releaseReplSession(sessionId: Int): CompileService.CallResult<Nothing> = releaseCompileSession(sessionId)
|
||||
|
||||
override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>): CompileService.CallResult<ReplCheckResult> =
|
||||
ifAlive_Raw(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
check(codeLine, history)
|
||||
}
|
||||
}
|
||||
|
||||
override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>): CompileService.CallResult<ReplCompileResult> =
|
||||
ifAlive_Raw(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
compile(codeLine, history)
|
||||
}
|
||||
}
|
||||
|
||||
override fun remoteReplLineEval(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>
|
||||
): CompileService.CallResult<ReplEvalResult> =
|
||||
ifAlive_Raw(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
eval(codeLine, history)
|
||||
}
|
||||
}
|
||||
|
||||
// internal implementation stuff
|
||||
|
||||
// TODO: consider matching compilerId coming from outside with actual one
|
||||
@@ -302,19 +379,31 @@ class CompileServiceImpl(
|
||||
shutdown()
|
||||
}
|
||||
else {
|
||||
var anyDead = false
|
||||
var shuttingDown = false
|
||||
|
||||
synchronized(state.sessions) {
|
||||
// 2. check if any session hanged - clean
|
||||
// making copy of the list before calling release
|
||||
state.sessions.filterValues { !it.isAlive }.keys.toList()
|
||||
}.forEach { releaseCompileSession(it) }
|
||||
state.sessions.filterValues { !it.isAlive }.apply { anyDead = true }.forEach {
|
||||
it.value.dispose()
|
||||
state.sessions.remove(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. check if in graceful shutdown state and all sessions are closed
|
||||
if (shutdownCondition({ state.alive.get() == Aliveness.LastSession.ordinal && state.sessions.none()}, "All sessions finished, shutting down")) {
|
||||
shutdown()
|
||||
shuttingDown = true
|
||||
}
|
||||
|
||||
// 4. clean dead clients, then check if any left - conditional shutdown (with small delay)
|
||||
synchronized(state.clientProxies) { state.clientProxies.removeAll(state.clientProxies.filter { !it.isAlive }) }
|
||||
synchronized(state.clientProxies) {
|
||||
state.clientProxies.removeAll(
|
||||
state.clientProxies.filter { !it.isAlive }.apply { anyDead = true }.map {
|
||||
it.dispose()
|
||||
it
|
||||
})
|
||||
}
|
||||
if (state.clientProxies.isEmpty() && compilationsCounter.get() > 0 && !state.delayedShutdownQueued.get()) {
|
||||
log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
|
||||
shutdownWithDelay()
|
||||
@@ -326,8 +415,14 @@ class CompileServiceImpl(
|
||||
shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") ||
|
||||
// 7. compiler changed (seldom check) - shutdown
|
||||
// TODO: could be too expensive anyway, consider removing this check
|
||||
shutdownCondition({ classpathWatcher.isChanged }, "Compiler changed")) {
|
||||
shutdownCondition({ classpathWatcher.isChanged }, "Compiler changed"))
|
||||
{
|
||||
shutdown()
|
||||
shuttingDown = true
|
||||
}
|
||||
|
||||
if (anyDead && !shuttingDown) {
|
||||
clearJarCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,6 +469,7 @@ class CompileServiceImpl(
|
||||
private fun shutdownImpl() {
|
||||
log.info("Shutdown started")
|
||||
state.alive.set(Aliveness.Dying.ordinal)
|
||||
|
||||
UnicastRemoteObject.unexportObject(this, true)
|
||||
log.info("Shutdown complete")
|
||||
onShutdown()
|
||||
@@ -414,8 +510,8 @@ class CompileServiceImpl(
|
||||
compilationsCounter.incrementAndGet()
|
||||
val rpcProfiler = if (daemonOptions.reportPerf) WallAndThreadTotalProfiler() else DummyProfiler()
|
||||
val eventManger = EventMangerImpl()
|
||||
val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesStreamProxy, rpcProfiler), 4096))
|
||||
val serviceOutputStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(serviceOutputStreamProxy, rpcProfiler), 4096))
|
||||
val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesStreamProxy, rpcProfiler), REMOTE_STREAM_BUFFER_SIZE))
|
||||
val serviceOutputStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(serviceOutputStreamProxy, rpcProfiler), REMOTE_STREAM_BUFFER_SIZE))
|
||||
try {
|
||||
checkedCompile(args, serviceOutputStream, rpcProfiler) {
|
||||
val res = body(compilerMessagesStream, eventManger, rpcProfiler).code
|
||||
@@ -490,17 +586,6 @@ class CompileServiceImpl(
|
||||
(KotlinCoreEnvironment.applicationEnvironment?.jarFileSystem as? CoreJarFileSystem)?.clearHandlersCache()
|
||||
}
|
||||
|
||||
// copied (with edit) from gradle plugin
|
||||
private fun callVoidStaticMethod(classFqName: String, methodName: String) {
|
||||
// compiler classloader == current classloader for now
|
||||
// TODO: consider abstracting classloader, for easier changing it for a compiler
|
||||
val cls = this.javaClass.classLoader.loadClass(classFqName)
|
||||
|
||||
val method = cls.getMethod(methodName)
|
||||
|
||||
method.invoke(null)
|
||||
}
|
||||
|
||||
private fun<R> ifAlive(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult<R> = rwlock.read {
|
||||
ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { CompileService.CallResult.Good(body()) }
|
||||
}
|
||||
@@ -513,6 +598,10 @@ class CompileServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private fun<R> ifAlive_Raw(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> CompileService.CallResult<R>): CompileService.CallResult<R> = rwlock.read {
|
||||
ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { body() }
|
||||
}
|
||||
|
||||
private fun<R> ifAliveExclusive(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult<R> = rwlock.write {
|
||||
ifAliveChecksImpl(minAliveness, ignoreCompilerChanged) { CompileService.CallResult.Good(body()) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.daemon
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import org.jetbrains.kotlin.cli.common.messages.*
|
||||
import org.jetbrains.kotlin.cli.common.repl.*
|
||||
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
|
||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||
import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompiler
|
||||
import org.jetbrains.kotlin.cli.jvm.repl.compileAndEval
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.daemon.common.DummyProfiler
|
||||
import org.jetbrains.kotlin.daemon.common.RemoteInputStream
|
||||
import org.jetbrains.kotlin.daemon.common.RemoteOperationsTracer
|
||||
import org.jetbrains.kotlin.daemon.common.RemoteOutputStream
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.net.URLClassLoader
|
||||
|
||||
open class KotlinJvmReplService(
|
||||
disposable: Disposable,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
compilerOutputStreamProxy: RemoteOutputStream,
|
||||
evalOutputStream: RemoteOutputStream?,
|
||||
evalErrorStream: RemoteOutputStream?,
|
||||
evalInputStream: RemoteInputStream?,
|
||||
val operationsTracer: RemoteOperationsTracer?
|
||||
) : ReplCompiler, ReplEvaluator {
|
||||
|
||||
protected val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerOutputStreamProxy, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE))
|
||||
|
||||
protected class KeepFirstErrorMessageCollector(compilerMessagesStream: PrintStream) : MessageCollector {
|
||||
|
||||
private val innerCollector = PrintingMessageCollector(compilerMessagesStream, MessageRenderer.WITHOUT_PATHS, false)
|
||||
|
||||
internal var firstErrorMessage: String? = null
|
||||
internal var firstErrorLocation: CompilerMessageLocation? = null
|
||||
|
||||
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
|
||||
if (firstErrorMessage == null && severity.isError) {
|
||||
firstErrorMessage = message
|
||||
firstErrorLocation = location
|
||||
}
|
||||
innerCollector.report(severity, message, location)
|
||||
}
|
||||
|
||||
override fun hasErrors(): Boolean = innerCollector.hasErrors()
|
||||
override fun clear() {
|
||||
innerCollector.clear()
|
||||
}
|
||||
}
|
||||
protected val messageCollector = KeepFirstErrorMessageCollector(compilerMessagesStream)
|
||||
|
||||
protected val configuration = CompilerConfiguration().apply {
|
||||
addJvmClasspathRoots(PathUtil.getJdkClassesRoots())
|
||||
addJvmClasspathRoots(PathUtil.getKotlinPathsForCompiler().let { listOf(it.runtimePath, it.reflectPath) })
|
||||
addJvmClasspathRoots(templateClasspath)
|
||||
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script")
|
||||
}
|
||||
|
||||
protected fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition? {
|
||||
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)
|
||||
|
||||
try {
|
||||
val cls = classloader.loadClass(templateClassName)
|
||||
val def = KotlinScriptDefinitionFromTemplate(cls.kotlin, null, null, emptyMap())
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.INFO,
|
||||
"New script definition $templateClassName: files pattern = \"${def.scriptFilePattern}\", resolver = ${def.resolver?.javaClass?.name}",
|
||||
CompilerMessageLocation.NO_LOCATION
|
||||
)
|
||||
return def
|
||||
}
|
||||
catch (ex: ClassNotFoundException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.ERROR, "Cannot find script definition template class $templateClassName", CompilerMessageLocation.NO_LOCATION
|
||||
)
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.ERROR, "Error processing script definition template $templateClassName: ${ex.message}", CompilerMessageLocation.NO_LOCATION
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val scriptDef = makeScriptDefinition(templateClasspath, templateClassName)
|
||||
|
||||
private val replCompiler : GenericReplCompiler? by lazy {
|
||||
if (scriptDef == null) null
|
||||
else GenericReplCompiler(disposable, scriptDef, configuration, messageCollector)
|
||||
}
|
||||
|
||||
private val compiledEvaluator : GenericReplCompiledEvaluator by lazy {
|
||||
if (evalOutputStream == null && evalErrorStream == null && evalInputStream == null)
|
||||
GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null)
|
||||
else object : GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null) {
|
||||
val out = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(evalOutputStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE))
|
||||
val err = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(evalErrorStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE))
|
||||
val `in` = BufferedInputStream(RemoteInputStreamClient(evalInputStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)
|
||||
override fun<T> evalWithIO(body: () -> T): T {
|
||||
val prevOut = System.out
|
||||
System.setOut(out)
|
||||
val prevErr = System.err
|
||||
System.setErr(err)
|
||||
val prevIn = System.`in`
|
||||
System.setIn(`in`)
|
||||
try {
|
||||
return body()
|
||||
}
|
||||
finally {
|
||||
System.setIn(prevIn)
|
||||
System.setErr(prevErr)
|
||||
System.setOut(prevOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
||||
operationsTracer?.before("check")
|
||||
try {
|
||||
return replCompiler?.check(codeLine, history)
|
||||
?: ReplCheckResult.Error(messageCollector.firstErrorMessage ?: "Unknown error",
|
||||
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
|
||||
}
|
||||
finally {
|
||||
operationsTracer?.after("check")
|
||||
}
|
||||
}
|
||||
|
||||
override fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult {
|
||||
operationsTracer?.before("compile")
|
||||
try {
|
||||
return replCompiler?.compile(codeLine, history)
|
||||
?: ReplCompileResult.Error(messageCollector.firstErrorMessage ?: "Unknown error",
|
||||
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
|
||||
}
|
||||
finally {
|
||||
operationsTracer?.after("compile")
|
||||
}
|
||||
}
|
||||
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult = synchronized(this) {
|
||||
operationsTracer?.before("eval")
|
||||
try {
|
||||
return replCompiler?.let { compileAndEval(it, compiledEvaluator, codeLine, history) }
|
||||
?: ReplEvalResult.Error.CompileTime(messageCollector.firstErrorMessage ?: "Unknown error",
|
||||
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
|
||||
}
|
||||
finally {
|
||||
operationsTracer?.after("eval")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user