Refactorings, reformatting code, applying code style and other cleanup
This commit is contained in:
+349
-336
@@ -26,6 +26,355 @@ import java.util.concurrent.Semaphore
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
|
public object KotlinCompilerClient {
|
||||||
|
|
||||||
|
val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L
|
||||||
|
val DAEMON_CONNECT_CYCLE_ATTEMPTS = 3
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: remove jvmStatic after all use sites will switch to kotlin
|
||||||
|
jvmStatic
|
||||||
|
public fun connectToCompileService(compilerId: CompilerId,
|
||||||
|
daemonJVMOptions: DaemonJVMOptions,
|
||||||
|
daemonOptions: DaemonOptions,
|
||||||
|
reportingTargets: DaemonReportingTargets,
|
||||||
|
autostart: Boolean = true,
|
||||||
|
checkId: Boolean = true
|
||||||
|
): CompileService? {
|
||||||
|
|
||||||
|
var attempts = 0
|
||||||
|
var fileLock: FileBasedLock? = null
|
||||||
|
try {
|
||||||
|
while (attempts++ < DAEMON_CONNECT_CYCLE_ATTEMPTS) {
|
||||||
|
val service = tryFindDaemon(File(daemonOptions.runFilesPath), compilerId, reportingTargets)
|
||||||
|
if (service != null) {
|
||||||
|
if (!checkId || checkCompilerId(service, compilerId)) {
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" "))
|
||||||
|
if (!autostart) return null
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "shutdown the daemon")
|
||||||
|
service.shutdown()
|
||||||
|
// TODO: find more reliable way
|
||||||
|
Thread.sleep(1000)
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon shut down correctly, restarting search")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!autostart) return null
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found, starting a new one")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileLock == null || !fileLock.isLocked()) {
|
||||||
|
File(daemonOptions.runFilesPath).mkdirs()
|
||||||
|
fileLock = FileBasedLock(compilerId, daemonOptions)
|
||||||
|
// need to check the daemons again here, because of possible racing conditions
|
||||||
|
// note: the algorithm could be simpler if we'll acquire lock right from the beginning, but it may be costly
|
||||||
|
attempts--
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
startDaemon(compilerId, daemonJVMOptions, daemonOptions, reportingTargets)
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to resolve")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
reportingTargets.report(DaemonReportCategory.EXCEPTION, e.toString())
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
fileLock?.release()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit {
|
||||||
|
KotlinCompilerClient.connectToCompileService(CompilerId(), DaemonJVMOptions(), daemonOptions, DaemonReportingTargets(out = System.out), autostart = false, checkId = false)
|
||||||
|
?.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public fun shutdownCompileService(): Unit {
|
||||||
|
shutdownCompileService(DaemonOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public fun compile(compiler: CompileService, args: Array<out String>, out: OutputStream): Int {
|
||||||
|
|
||||||
|
val outStrm = RemoteOutputStreamServer(out)
|
||||||
|
try {
|
||||||
|
return compiler.remoteCompile(args, outStrm, CompileService.OutputFormat.PLAIN, outStrm)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
outStrm.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: remove jvmStatic after all use sites will switch to kotlin
|
||||||
|
jvmStatic
|
||||||
|
public fun incrementalCompile(compiler: CompileService, args: Array<out String>, caches: Map<TargetId, IncrementalCache>, compilerOut: OutputStream, daemonOut: OutputStream): Int {
|
||||||
|
|
||||||
|
val compilerOutStreamServer = RemoteOutputStreamServer(compilerOut)
|
||||||
|
val daemonOutStreamServer = RemoteOutputStreamServer(daemonOut)
|
||||||
|
val cacheServers = hashMapOf<TargetId, RemoteIncrementalCacheServer>()
|
||||||
|
try {
|
||||||
|
caches.mapValuesTo(cacheServers, { RemoteIncrementalCacheServer(it.getValue()) })
|
||||||
|
return compiler.remoteIncrementalCompile(args, cacheServers, compilerOutStreamServer, CompileService.OutputFormat.XML, daemonOutStreamServer)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cacheServers.forEach { it.getValue().disconnect() }
|
||||||
|
compilerOutStreamServer.disconnect()
|
||||||
|
daemonOutStreamServer.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class ClientOptions(
|
||||||
|
public var stop: Boolean = false
|
||||||
|
) : OptionsGroup {
|
||||||
|
override val mappers: List<PropMapper<*, *, *>>
|
||||||
|
get() = listOf(BoolPropMapper(this, ::stop))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
jvmStatic public fun main(vararg args: String) {
|
||||||
|
val compilerId = CompilerId()
|
||||||
|
val daemonOptions = DaemonOptions()
|
||||||
|
val daemonLaunchingOptions = DaemonJVMOptions()
|
||||||
|
val clientOptions = ClientOptions()
|
||||||
|
val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, daemonLaunchingOptions, clientOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX)
|
||||||
|
|
||||||
|
if (!clientOptions.stop) {
|
||||||
|
if (compilerId.compilerClasspath.none()) {
|
||||||
|
// attempt to find compiler to use
|
||||||
|
System.err.println("compiler wasn't explicitly specified, attempt to find appropriate jar")
|
||||||
|
System.getProperty("java.class.path")
|
||||||
|
?.split(File.pathSeparator)
|
||||||
|
?.map { File(it).parentFile }
|
||||||
|
?.distinct()
|
||||||
|
?.map {
|
||||||
|
it?.walk()
|
||||||
|
?.firstOrNull { it.name.equals(COMPILER_JAR_NAME, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
?.filterNotNull()
|
||||||
|
?.firstOrNull()
|
||||||
|
?.let { compilerId.compilerClasspath = listOf(it.absolutePath) }
|
||||||
|
}
|
||||||
|
if (compilerId.compilerClasspath.none())
|
||||||
|
throw IllegalArgumentException("Cannot find compiler jar")
|
||||||
|
else
|
||||||
|
println("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator))
|
||||||
|
|
||||||
|
compilerId.updateDigest()
|
||||||
|
}
|
||||||
|
|
||||||
|
val daemon = connectToCompileService(compilerId, daemonLaunchingOptions, daemonOptions, DaemonReportingTargets(out = System.out), autostart = !clientOptions.stop, checkId = !clientOptions.stop)
|
||||||
|
|
||||||
|
if (daemon == null) {
|
||||||
|
if (clientOptions.stop) {
|
||||||
|
System.err.println("No daemon found to shut down")
|
||||||
|
}
|
||||||
|
else throw Exception("Unable to connect to daemon")
|
||||||
|
}
|
||||||
|
else when {
|
||||||
|
clientOptions.stop -> {
|
||||||
|
println("Shutdown the daemon")
|
||||||
|
daemon.shutdown()
|
||||||
|
println("Daemon shut down successfully")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
println("Executing daemon compilation with args: " + filteredArgs.joinToString(" "))
|
||||||
|
val outStrm = RemoteOutputStreamServer(System.out)
|
||||||
|
try {
|
||||||
|
val memBefore = daemon.getUsedMemory() / 1024
|
||||||
|
val startTime = System.nanoTime()
|
||||||
|
|
||||||
|
val res = daemon.remoteCompile(filteredArgs.toArrayList().toTypedArray(), outStrm, CompileService.OutputFormat.PLAIN, outStrm)
|
||||||
|
|
||||||
|
val endTime = System.nanoTime()
|
||||||
|
println("Compilation result code: $res")
|
||||||
|
val memAfter = daemon.getUsedMemory() / 1024
|
||||||
|
println("Compilation time: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms")
|
||||||
|
println("Used memory $memAfter (${"%+d".format(memAfter - memBefore)} kb)")
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
outStrm.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Implementation ---------------------------------------
|
||||||
|
|
||||||
|
val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null
|
||||||
|
|
||||||
|
fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String = "daemon client") {
|
||||||
|
if (category == DaemonReportCategory.DEBUG && !verboseReporting) return
|
||||||
|
out?.println("[$source] ${category.name()}: $message")
|
||||||
|
messages?.add(DaemonReportMessage(category, "[$source] $message"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryFindDaemon(registryDir: File, compilerId: CompilerId, reportingTargets: DaemonReportingTargets): CompileService? {
|
||||||
|
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest()
|
||||||
|
val daemons = registryDir.walk()
|
||||||
|
.map { Pair(it, makeRunFilenameRegex(digest = classPathDigest, port = "(\\d+)").match(it.name)?.groups?.get(1)?.value?.toInt() ?: 0) }
|
||||||
|
.filter { it.second != 0 }
|
||||||
|
.map {
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "found suitable daemon on port ${it.second}, trying to connect")
|
||||||
|
val daemon = tryConnectToDaemon(it.second, reportingTargets)
|
||||||
|
// cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted
|
||||||
|
if (daemon == null && !it.first.delete()) {
|
||||||
|
reportingTargets.report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', cleanup recommended")
|
||||||
|
}
|
||||||
|
daemon
|
||||||
|
}
|
||||||
|
.filterNotNull()
|
||||||
|
.toList()
|
||||||
|
return when (daemons.size()) {
|
||||||
|
0 -> null
|
||||||
|
1 -> daemons.first()
|
||||||
|
else -> throw IllegalStateException("Multiple daemons serving the same compiler, reset with the cleanup required")
|
||||||
|
// TODO: consider implementing automatic recovery instead, e.g. getting the youngest or least used daemon and shut down others
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryConnectToDaemon(port: Int, reportingTargets: DaemonReportingTargets): CompileService? {
|
||||||
|
try {
|
||||||
|
val daemon = LocateRegistry.getRegistry(loopbackAddrName, port)
|
||||||
|
?.lookup(COMPILER_SERVICE_RMI_NAME)
|
||||||
|
if (daemon != null)
|
||||||
|
return daemon as? CompileService ?:
|
||||||
|
throw ClassCastException("Unable to cast compiler service, actual class received: ${daemon.javaClass}")
|
||||||
|
reportingTargets.report(DaemonReportCategory.EXCEPTION, "daemon not found")
|
||||||
|
}
|
||||||
|
catch (e: ConnectException) {
|
||||||
|
reportingTargets.report(DaemonReportCategory.EXCEPTION, "cannot connect to registry: " + (e.getCause()?.getMessage() ?: e.getMessage() ?: "unknown exception"))
|
||||||
|
// ignoring it - processing below
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun startDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, reportingTargets: DaemonReportingTargets) {
|
||||||
|
val javaExecutable = File(System.getProperty("java.home"), "bin").let {
|
||||||
|
val javaw = File(it, "javaw.exe")
|
||||||
|
// TODO: doesn't seem reliable enough, consider more checks if OS is of windows flavor, etc.
|
||||||
|
if (javaw.exists() && javaw.isFile && javaw.canExecute()) javaw else File(it, "java")
|
||||||
|
}
|
||||||
|
val args = listOf(javaExecutable.absolutePath,
|
||||||
|
"-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) +
|
||||||
|
daemonJVMOptions.mappers.flatMap { it.toArgs("-") } +
|
||||||
|
COMPILER_DAEMON_CLASS_FQN +
|
||||||
|
daemonOptions.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } +
|
||||||
|
compilerId.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) }
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, "starting the daemon as: " + args.joinToString(" "))
|
||||||
|
val processBuilder = ProcessBuilder(args).redirectErrorStream(true)
|
||||||
|
// assuming daemon process is deaf and (mostly) silent, so do not handle streams
|
||||||
|
val daemon = processBuilder.start()
|
||||||
|
|
||||||
|
var isEchoRead = Semaphore(1)
|
||||||
|
isEchoRead.acquire()
|
||||||
|
|
||||||
|
val stdoutThread =
|
||||||
|
thread {
|
||||||
|
daemon.inputStream
|
||||||
|
.reader()
|
||||||
|
.forEachLine {
|
||||||
|
if (daemonOptions.runFilesPath.isNotEmpty() && it.contains(daemonOptions.runFilesPath)) {
|
||||||
|
isEchoRead.release()
|
||||||
|
return@forEachLine
|
||||||
|
}
|
||||||
|
reportingTargets.report(DaemonReportCategory.DEBUG, it, "daemon")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// trying to wait for process
|
||||||
|
val daemonStartupTimeout = System.getProperty(COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY)?.let {
|
||||||
|
try {
|
||||||
|
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")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} ?: DAEMON_DEFAULT_STARTUP_TIMEOUT_MS
|
||||||
|
if (daemonOptions.runFilesPath.isNotEmpty()) {
|
||||||
|
val succeeded = isEchoRead.tryAcquire(daemonStartupTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
if (!daemon.isAlive())
|
||||||
|
throw Exception("Daemon terminated unexpectedly")
|
||||||
|
if (!succeeded)
|
||||||
|
throw Exception("Unable to get response from daemon in $daemonStartupTimeout ms")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// without startEcho defined waiting for max timeout
|
||||||
|
Thread.sleep(daemonStartupTimeout)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// assuming that all important output is already done, the rest should be routed to the log by the daemon itself
|
||||||
|
if (stdoutThread.isAlive) {
|
||||||
|
// TODO: find better method to stop the thread, but seems it will require asynchronous consuming of the stream
|
||||||
|
stdoutThread.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun checkCompilerId(compiler: CompileService, localId: CompilerId): Boolean {
|
||||||
|
val remoteId = compiler.getCompilerId()
|
||||||
|
return (localId.compilerVersion.isEmpty() || localId.compilerVersion == remoteId.compilerVersion) &&
|
||||||
|
(localId.compilerClasspath.all { remoteId.compilerClasspath.contains(it) }) &&
|
||||||
|
(localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FileBasedLock(compilerId: CompilerId, daemonOptions: DaemonOptions) {
|
||||||
|
private val lockFile: File =
|
||||||
|
File(daemonOptions.runFilesPath,
|
||||||
|
makeRunFilenameString(ts = "lock",
|
||||||
|
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
||||||
|
port = "0"))
|
||||||
|
|
||||||
|
private var locked: Boolean = acquireLockFile(lockFile)
|
||||||
|
|
||||||
|
public fun isLocked(): Boolean = locked
|
||||||
|
|
||||||
|
synchronized public fun release(): Unit {
|
||||||
|
if (locked) {
|
||||||
|
lock?.release()
|
||||||
|
channel?.close()
|
||||||
|
lockFile.delete()
|
||||||
|
locked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val channel = if (locked) RandomAccessFile(lockFile, "rw").channel else null
|
||||||
|
private val lock = channel?.lock()
|
||||||
|
|
||||||
|
synchronized private fun acquireLockFile(lockFile: File): Boolean {
|
||||||
|
if (lockFile.createNewFile()) return true
|
||||||
|
try {
|
||||||
|
// attempt to delete if file is orphaned
|
||||||
|
if (lockFile.delete() && lockFile.createNewFile())
|
||||||
|
return true // if orphaned file deleted assuming that the probability of
|
||||||
|
}
|
||||||
|
catch (e: IOException) {
|
||||||
|
// Ignoring it - assuming it is another client process owning it
|
||||||
|
}
|
||||||
|
var attempts = 0L
|
||||||
|
while (lockFile.exists() && attempts++ * COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS < COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_MS) {
|
||||||
|
Thread.sleep(COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS)
|
||||||
|
}
|
||||||
|
if (lockFile.exists())
|
||||||
|
throw IOException("Timeout waiting the release of the lock file '${lockFile.absolutePath}")
|
||||||
|
|
||||||
|
return lockFile.createNewFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Process.isAlive() =
|
fun Process.isAlive() =
|
||||||
try {
|
try {
|
||||||
this.exitValue()
|
this.exitValue()
|
||||||
@@ -43,339 +392,3 @@ public enum class DaemonReportCategory {
|
|||||||
public data class DaemonReportMessage(public val category: DaemonReportCategory, public val message: String)
|
public data class DaemonReportMessage(public val category: DaemonReportCategory, public val message: String)
|
||||||
|
|
||||||
public class DaemonReportingTargets(public val out: PrintStream? = null, public val messages: MutableCollection<DaemonReportMessage>? = null)
|
public class DaemonReportingTargets(public val out: PrintStream? = null, public val messages: MutableCollection<DaemonReportMessage>? = null)
|
||||||
|
|
||||||
|
|
||||||
public class KotlinCompilerClient {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L
|
|
||||||
val DAEMON_CONNECT_CYCLE_ATTEMPTS = 3
|
|
||||||
|
|
||||||
val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null
|
|
||||||
|
|
||||||
fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String = "daemon client") {
|
|
||||||
if (category == DaemonReportCategory.DEBUG && !verboseReporting) return
|
|
||||||
out?.println("[$source] ${category.name()}: $message")
|
|
||||||
messages?.add(DaemonReportMessage(category, "[$source] $message"))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryFindDaemon(registryDir: File, compilerId: CompilerId, reportingTargets: DaemonReportingTargets): CompileService? {
|
|
||||||
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest()
|
|
||||||
val daemons = registryDir.walk()
|
|
||||||
.map { Pair(it, makeRunFilenameRegex(digest = classPathDigest, port = "(\\d+)").match(it.name)?.groups?.get(1)?.value?.toInt() ?: 0) }
|
|
||||||
.filter { it.second != 0 }
|
|
||||||
.map {
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "found suitable daemon on port ${it.second}, trying to connect")
|
|
||||||
val daemon = tryConnectToDaemon(it.second, reportingTargets)
|
|
||||||
// cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted
|
|
||||||
if (daemon == null && !it.first.delete()) {
|
|
||||||
reportingTargets.report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', cleanup recommended")
|
|
||||||
}
|
|
||||||
daemon
|
|
||||||
}
|
|
||||||
.filterNotNull()
|
|
||||||
.toList()
|
|
||||||
return when (daemons.size()) {
|
|
||||||
0 -> null
|
|
||||||
1 -> daemons.first()
|
|
||||||
else -> throw IllegalStateException("Multiple daemons serving the same compiler, reset with the cleanup required")
|
|
||||||
// TODO: consider implementing automatic recovery instead, e.g. getting the youngest or least used daemon and shut down others
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryConnectToDaemon(port: Int, reportingTargets: DaemonReportingTargets): CompileService? {
|
|
||||||
try {
|
|
||||||
val daemon = LocateRegistry.getRegistry(loopbackAddrName, port)
|
|
||||||
?.lookup(COMPILER_SERVICE_RMI_NAME)
|
|
||||||
if (daemon != null)
|
|
||||||
return daemon as? CompileService ?:
|
|
||||||
throw ClassCastException("Unable to cast compiler service, actual class received: ${daemon.javaClass}")
|
|
||||||
reportingTargets.report(DaemonReportCategory.EXCEPTION, "daemon not found")
|
|
||||||
}
|
|
||||||
catch (e: ConnectException) {
|
|
||||||
reportingTargets.report(DaemonReportCategory.EXCEPTION, "cannot connect to registry: " + (e.getCause()?.getMessage() ?: e.getMessage() ?: "unknown exception"))
|
|
||||||
// ignoring it - processing below
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun startDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, reportingTargets: DaemonReportingTargets) {
|
|
||||||
val javaExecutable = File(System.getProperty("java.home"), "bin").let {
|
|
||||||
val javaw = File(it, "javaw.exe")
|
|
||||||
if (javaw.exists()) javaw
|
|
||||||
else File(it, "java")
|
|
||||||
}
|
|
||||||
// TODO: add some specific environment variables to the cp and may be command line, to allow some specific startup configs
|
|
||||||
val args = listOf(javaExecutable.absolutePath,
|
|
||||||
"-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) +
|
|
||||||
daemonJVMOptions.mappers.flatMap { it.toArgs("-") } +
|
|
||||||
COMPILER_DAEMON_CLASS_FQN +
|
|
||||||
daemonOptions.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } +
|
|
||||||
compilerId.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) }
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "starting the daemon as: " + args.joinToString(" "))
|
|
||||||
val processBuilder = ProcessBuilder(args).redirectErrorStream(true)
|
|
||||||
// assuming daemon process is deaf and (mostly) silent, so do not handle streams
|
|
||||||
val daemon = processBuilder.start()
|
|
||||||
|
|
||||||
var isEchoRead = Semaphore(1)
|
|
||||||
isEchoRead.acquire()
|
|
||||||
|
|
||||||
val stdoutThread =
|
|
||||||
thread {
|
|
||||||
daemon.getInputStream()
|
|
||||||
.reader()
|
|
||||||
.forEachLine {
|
|
||||||
if (daemonOptions.runFilesPath.isNotEmpty() && it.contains(daemonOptions.runFilesPath)) {
|
|
||||||
isEchoRead.release()
|
|
||||||
return@forEachLine
|
|
||||||
}
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, it, "daemon")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// trying to wait for process
|
|
||||||
val daemonStartupTimeout = System.getProperty(COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY)?.let {
|
|
||||||
try {
|
|
||||||
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")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} ?: DAEMON_DEFAULT_STARTUP_TIMEOUT_MS
|
|
||||||
if (daemonOptions.runFilesPath.isNotEmpty()) {
|
|
||||||
val succeeded = isEchoRead.tryAcquire(daemonStartupTimeout, TimeUnit.MILLISECONDS)
|
|
||||||
if (!daemon.isAlive())
|
|
||||||
throw Exception("Daemon terminated unexpectedly")
|
|
||||||
if (!succeeded)
|
|
||||||
throw Exception("Unable to get response from daemon in $daemonStartupTimeout ms")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// without startEcho defined waiting for max timeout
|
|
||||||
Thread.sleep(daemonStartupTimeout)
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
// assuming that all important output is already done, the rest should be routed to the log by the daemon itself
|
|
||||||
if (stdoutThread.isAlive)
|
|
||||||
// TODO: find better method to stop the thread, but seems it will require asynchronous consuming of the stream
|
|
||||||
stdoutThread.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun checkCompilerId(compiler: CompileService, localId: CompilerId): Boolean {
|
|
||||||
val remoteId = compiler.getCompilerId()
|
|
||||||
return (localId.compilerVersion.isEmpty() || localId.compilerVersion == remoteId.compilerVersion) &&
|
|
||||||
(localId.compilerClasspath.all { remoteId.compilerClasspath.contains(it) }) &&
|
|
||||||
(localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class FileBasedLock(compilerId: CompilerId, daemonOptions: DaemonOptions) {
|
|
||||||
private val lockFile: File =
|
|
||||||
File(daemonOptions.runFilesPath,
|
|
||||||
makeRunFilenameString(ts = "lock",
|
|
||||||
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
|
||||||
port = "0"))
|
|
||||||
|
|
||||||
private var locked: Boolean = acquireLockFile(lockFile)
|
|
||||||
|
|
||||||
public fun isLocked(): Boolean = locked
|
|
||||||
|
|
||||||
synchronized public fun release(): Unit {
|
|
||||||
if (locked) {
|
|
||||||
lock?.release()
|
|
||||||
channel?.close()
|
|
||||||
lockFile.delete()
|
|
||||||
locked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val channel = if (locked) RandomAccessFile(lockFile, "rw").channel else null
|
|
||||||
private val lock = channel?.lock()
|
|
||||||
|
|
||||||
synchronized private fun acquireLockFile(lockFile: File): Boolean {
|
|
||||||
if (lockFile.createNewFile()) return true
|
|
||||||
try {
|
|
||||||
// attempt to delete if file is orphaned
|
|
||||||
if (lockFile.delete() && lockFile.createNewFile())
|
|
||||||
return true // if orphaned file deleted assuming that the probability of
|
|
||||||
}
|
|
||||||
catch (e: IOException) {
|
|
||||||
// Ignoring it - assuming it is another client process owning it
|
|
||||||
}
|
|
||||||
var attempts = 0L
|
|
||||||
while (lockFile.exists() && attempts++ * COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS < COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_MS) {
|
|
||||||
Thread.sleep(COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS)
|
|
||||||
}
|
|
||||||
if (lockFile.exists())
|
|
||||||
throw IOException("Timeout waiting the release of the lock file '${lockFile.absolutePath}")
|
|
||||||
|
|
||||||
return lockFile.createNewFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public fun connectToCompileService(compilerId: CompilerId,
|
|
||||||
daemonJVMOptions: DaemonJVMOptions,
|
|
||||||
daemonOptions: DaemonOptions,
|
|
||||||
reportingTargets: DaemonReportingTargets,
|
|
||||||
autostart: Boolean = true,
|
|
||||||
checkId: Boolean = true
|
|
||||||
): CompileService? {
|
|
||||||
|
|
||||||
var attempts = 0
|
|
||||||
var fileLock: FileBasedLock? = null
|
|
||||||
try {
|
|
||||||
while (attempts++ < DAEMON_CONNECT_CYCLE_ATTEMPTS) {
|
|
||||||
val service = tryFindDaemon(File(daemonOptions.runFilesPath), compilerId, reportingTargets)
|
|
||||||
if (service != null) {
|
|
||||||
if (!checkId || checkCompilerId(service, compilerId)) {
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
|
|
||||||
return service
|
|
||||||
}
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" "))
|
|
||||||
if (!autostart) return null
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "shutdown the daemon")
|
|
||||||
service.shutdown()
|
|
||||||
// TODO: find more reliable way
|
|
||||||
Thread.sleep(1000)
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon shut down correctly, restarting search")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!autostart) return null
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found, starting a new one")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileLock == null || !fileLock.isLocked()) {
|
|
||||||
File(daemonOptions.runFilesPath).mkdirs()
|
|
||||||
fileLock = FileBasedLock(compilerId, daemonOptions)
|
|
||||||
// need to check the daemons again here, because of possible racing conditions
|
|
||||||
// note: the algorithm could be simpler if we'll acquire lock right from the beginning, but it may be costly
|
|
||||||
attempts--
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
startDaemon(compilerId, daemonJVMOptions, daemonOptions, reportingTargets)
|
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to resolve")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
reportingTargets.report(DaemonReportCategory.EXCEPTION, e.toString())
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
fileLock?.release()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit {
|
|
||||||
KotlinCompilerClient.connectToCompileService(CompilerId(), DaemonJVMOptions(), daemonOptions, DaemonReportingTargets(out = System.out), autostart = false, checkId = false)
|
|
||||||
?.shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun shutdownCompileService(): Unit {
|
|
||||||
shutdownCompileService(DaemonOptions())
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun compile(compiler: CompileService, args: Array<out String>, out: OutputStream): Int {
|
|
||||||
|
|
||||||
val outStrm = RemoteOutputStreamServer(out)
|
|
||||||
try {
|
|
||||||
return compiler.remoteCompile(args, outStrm, CompileService.OutputFormat.PLAIN, outStrm)
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
outStrm.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun incrementalCompile(compiler: CompileService, args: Array<out String>, caches: Map<TargetId, IncrementalCache>, compilerOut: OutputStream, daemonOut: OutputStream): Int {
|
|
||||||
|
|
||||||
val compilerOutStreamServer = RemoteOutputStreamServer(compilerOut)
|
|
||||||
val daemonOutStreamServer = RemoteOutputStreamServer(daemonOut)
|
|
||||||
val cacheServers = hashMapOf<TargetId, RemoteIncrementalCacheServer>()
|
|
||||||
try {
|
|
||||||
caches.mapValuesTo(cacheServers, { RemoteIncrementalCacheServer( it.getValue()) })
|
|
||||||
return compiler.remoteIncrementalCompile(args, cacheServers, compilerOutStreamServer, CompileService.OutputFormat.XML, daemonOutStreamServer)
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
cacheServers.forEach { it.getValue().disconnect() }
|
|
||||||
compilerOutStreamServer.disconnect()
|
|
||||||
daemonOutStreamServer.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ClientOptions(
|
|
||||||
public var stop: Boolean = false
|
|
||||||
) : OptionsGroup {
|
|
||||||
override val mappers: List<PropMapper<*, *, *>>
|
|
||||||
get() = listOf( BoolPropMapper(this, ::stop))
|
|
||||||
}
|
|
||||||
|
|
||||||
jvmStatic public fun main(vararg args: String) {
|
|
||||||
val compilerId = CompilerId()
|
|
||||||
val daemonOptions = DaemonOptions()
|
|
||||||
val daemonLaunchingOptions = DaemonJVMOptions()
|
|
||||||
val clientOptions = ClientOptions()
|
|
||||||
val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, daemonLaunchingOptions, clientOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX)
|
|
||||||
|
|
||||||
if (!clientOptions.stop) {
|
|
||||||
if (compilerId.compilerClasspath.none()) {
|
|
||||||
// attempt to find compiler to use
|
|
||||||
System.err.println("compiler wasn't explicitly specified, attempt to find appropriate jar")
|
|
||||||
System.getProperty("java.class.path")
|
|
||||||
?.split(File.pathSeparator)
|
|
||||||
?.map { File(it).parentFile }
|
|
||||||
?.distinct()
|
|
||||||
?.map {
|
|
||||||
it?.walk()
|
|
||||||
?.firstOrNull { it.getName().equals(COMPILER_JAR_NAME, ignoreCase = true) }
|
|
||||||
}
|
|
||||||
?.filterNotNull()
|
|
||||||
?.firstOrNull()
|
|
||||||
?.let { compilerId.compilerClasspath = listOf(it.absolutePath) }
|
|
||||||
}
|
|
||||||
if (compilerId.compilerClasspath.none())
|
|
||||||
throw IllegalArgumentException("Cannot find compiler jar")
|
|
||||||
else
|
|
||||||
println("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator))
|
|
||||||
|
|
||||||
compilerId.updateDigest()
|
|
||||||
}
|
|
||||||
|
|
||||||
val daemon = connectToCompileService(compilerId, daemonLaunchingOptions, daemonOptions, DaemonReportingTargets(out = System.out), autostart = !clientOptions.stop, checkId = !clientOptions.stop)
|
|
||||||
|
|
||||||
if (daemon == null) {
|
|
||||||
if (clientOptions.stop) System.err.println("No daemon found to shut down")
|
|
||||||
else throw Exception("Unable to connect to daemon")
|
|
||||||
}
|
|
||||||
else when {
|
|
||||||
clientOptions.stop -> {
|
|
||||||
println("Shutdown the daemon")
|
|
||||||
daemon.shutdown()
|
|
||||||
println("Daemon shut down successfully")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
println("Executing daemon compilation with args: " + filteredArgs.joinToString(" "))
|
|
||||||
val outStrm = RemoteOutputStreamServer(System.out)
|
|
||||||
try {
|
|
||||||
val memBefore = daemon.getUsedMemory() / 1024
|
|
||||||
val startTime = System.nanoTime()
|
|
||||||
val res = daemon.remoteCompile(filteredArgs.toArrayList().toTypedArray(), outStrm, CompileService.OutputFormat.PLAIN, outStrm)
|
|
||||||
val endTime = System.nanoTime()
|
|
||||||
println("Compilation result code: $res")
|
|
||||||
val memAfter = daemon.getUsedMemory() / 1024
|
|
||||||
println("Compilation time: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms")
|
|
||||||
println("Used memory $memAfter (${"%+d".format(memAfter - memBefore)} kb)")
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
outStrm.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public val COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY: String = "kotlin.daemon.cl
|
|||||||
public val COMPILE_DAEMON_REPORT_PERF_PROPERTY: String = "kotlin.daemon.perf"
|
public val COMPILE_DAEMON_REPORT_PERF_PROPERTY: String = "kotlin.daemon.perf"
|
||||||
public val COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY: String = "kotlin.daemon.verbose"
|
public val COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY: String = "kotlin.daemon.verbose"
|
||||||
public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String = "--daemon-"
|
public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String = "--daemon-"
|
||||||
public val COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY: String ="kotlin.daemon.startup.timeout"
|
public val COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY: String = "kotlin.daemon.startup.timeout"
|
||||||
public val COMPILE_DAEMON_DEFAULT_FILES_PREFIX: String = "kotlin-daemon"
|
public val COMPILE_DAEMON_DEFAULT_FILES_PREFIX: String = "kotlin-daemon"
|
||||||
public val COMPILE_DAEMON_DATA_DIRECTORY_NAME: String = "." + COMPILE_DAEMON_DEFAULT_FILES_PREFIX
|
public val COMPILE_DAEMON_DATA_DIRECTORY_NAME: String = "." + COMPILE_DAEMON_DEFAULT_FILES_PREFIX
|
||||||
public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
|
public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
|
||||||
@@ -51,67 +51,70 @@ public val COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH: String get() =
|
|||||||
// TODO consider special case for windows - local appdata
|
// TODO consider special case for windows - local appdata
|
||||||
File(System.getProperty("user.home"), COMPILE_DAEMON_DATA_DIRECTORY_NAME).absolutePath
|
File(System.getProperty("user.home"), COMPILE_DAEMON_DATA_DIRECTORY_NAME).absolutePath
|
||||||
|
|
||||||
|
|
||||||
val COMPILER_ID_DIGEST = "MD5"
|
val COMPILER_ID_DIGEST = "MD5"
|
||||||
|
|
||||||
|
|
||||||
public fun makeRunFilenameString(ts: String, digest: String, port: String, esc: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$esc.$ts$esc.$digest$esc.$port$esc.run"
|
public fun makeRunFilenameString(ts: String, digest: String, port: String, esc: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$esc.$ts$esc.$digest$esc.$port$esc.run"
|
||||||
|
|
||||||
public fun makeRunFilenameRegex(ts: String = "[0-9TZ:\\.\\+-]+", digest: String = "[0-9a-f]+", port: String = "\\d+"): Regex = makeRunFilenameString(ts, digest, port, esc = "\\").toRegex()
|
public fun makeRunFilenameRegex(ts: String = "[0-9TZ:\\.\\+-]+", digest: String = "[0-9a-f]+", port: String = "\\d+"): Regex = makeRunFilenameString(ts, digest, port, esc = "\\").toRegex()
|
||||||
|
|
||||||
|
|
||||||
open class PropMapper<C, V, P: KMutableProperty1<C, V>>(val dest: C,
|
open class PropMapper<C, V, P : KMutableProperty1<C, V>>(val dest: C,
|
||||||
val prop: P,
|
val prop: P,
|
||||||
val names: List<String> = listOf(prop.name),
|
val names: List<String> = listOf(prop.name),
|
||||||
val fromString: (String) -> V,
|
val fromString: (String) -> V,
|
||||||
val toString: ((V) -> String?) = { it.toString() },
|
val toString: ((V) -> String?) = { it.toString() },
|
||||||
val skipIf: ((V) -> Boolean) = { false },
|
val skipIf: ((V) -> Boolean) = { false },
|
||||||
val mergeDelimiter: String? = null)
|
val mergeDelimiter: String? = null) {
|
||||||
{
|
|
||||||
open fun toArgs(prefix: String = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX): List<String> =
|
open fun toArgs(prefix: String = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX): List<String> =
|
||||||
when {
|
when {
|
||||||
skipIf(prop.get(dest)) -> listOf<String>()
|
skipIf(prop.get(dest)) -> listOf<String>()
|
||||||
mergeDelimiter != null -> listOf( listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull().joinToString(mergeDelimiter))
|
mergeDelimiter != null -> listOf(listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull().joinToString(mergeDelimiter))
|
||||||
else -> listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull()
|
else -> listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun apply(s: String) = prop.set(dest, fromString(s))
|
open fun apply(s: String) = prop.set(dest, fromString(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class NullablePropMapper<C, V: Any?, P: KMutableProperty1<C, V>>(dest: C,
|
class NullablePropMapper<C, V : Any?, P : KMutableProperty1<C, V>>(dest: C,
|
||||||
prop: P,
|
prop: P,
|
||||||
names: List<String> = listOf(),
|
names: List<String> = listOf(),
|
||||||
fromString: ((String) -> V),
|
fromString: ((String) -> V),
|
||||||
toString: ((V) -> String?) = { it.toString() },
|
toString: ((V) -> String?) = { it.toString() },
|
||||||
skipIf: ((V) -> Boolean) = { it == null },
|
skipIf: ((V) -> Boolean) = { it == null },
|
||||||
mergeDelimiter: String? = null)
|
mergeDelimiter: String? = null)
|
||||||
: PropMapper<C, V, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
|
: PropMapper<C, V, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
|
||||||
fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)
|
fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)
|
||||||
|
|
||||||
class StringPropMapper<C, P: KMutableProperty1<C, String>>(dest: C,
|
|
||||||
prop: P,
|
class StringPropMapper<C, P : KMutableProperty1<C, String>>(dest: C,
|
||||||
names: List<String> = listOf(),
|
prop: P,
|
||||||
fromString: ((String) -> String) = { it },
|
names: List<String> = listOf(),
|
||||||
toString: ((String) -> String?) = { it.toString() },
|
fromString: ((String) -> String) = { it },
|
||||||
skipIf: ((String) -> Boolean) = { it.isEmpty() },
|
toString: ((String) -> String?) = { it.toString() },
|
||||||
mergeDelimiter: String? = null)
|
skipIf: ((String) -> Boolean) = { it.isEmpty() },
|
||||||
|
mergeDelimiter: String? = null)
|
||||||
: PropMapper<C, String, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
|
: PropMapper<C, String, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
|
||||||
fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)
|
fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)
|
||||||
|
|
||||||
|
|
||||||
class BoolPropMapper<C, P: KMutableProperty1<C, Boolean>>(dest: C, prop: P, names: List<String> = listOf())
|
class BoolPropMapper<C, P : KMutableProperty1<C, Boolean>>(dest: C, prop: P, names: List<String> = listOf())
|
||||||
: PropMapper<C, Boolean, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
|
: PropMapper<C, Boolean, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
|
||||||
fromString = { true }, toString = { null }, skipIf = { !prop.get(dest) })
|
fromString = { true }, toString = { null }, skipIf = { !prop.get(dest) })
|
||||||
|
|
||||||
|
|
||||||
class RestPropMapper<C, P: KMutableProperty1<C, MutableCollection<String>>>(dest: C, prop: P)
|
class RestPropMapper<C, P : KMutableProperty1<C, MutableCollection<String>>>(dest: C, prop: P)
|
||||||
: PropMapper<C, MutableCollection<String>, P>(dest = dest, prop = prop, toString = { null }, fromString = { arrayListOf() })
|
: PropMapper<C, MutableCollection<String>, P>(dest = dest, prop = prop, toString = { null }, fromString = { arrayListOf() }) {
|
||||||
{
|
|
||||||
override fun toArgs(prefix: String): List<String> = prop.get(dest).map { prefix + it }
|
override fun toArgs(prefix: String): List<String> = prop.get(dest).map { prefix + it }
|
||||||
override fun apply(s: String) = add(s)
|
override fun apply(s: String) = add(s)
|
||||||
fun add(s: String) { prop.get(dest).add(s) }
|
fun add(s: String) {
|
||||||
|
prop.get(dest).add(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline fun <T, R: Any> Iterable<T>.findWithTransform(mappingPredicate: (T) -> Pair<Boolean, R?>): R? {
|
inline fun <T, R : Any> Iterable<T>.findWithTransform(mappingPredicate: (T) -> Pair<Boolean, R?>): R? {
|
||||||
for (element in this) {
|
for (element in this) {
|
||||||
val (found, mapped) = mappingPredicate(element)
|
val (found, mapped) = mappingPredicate(element)
|
||||||
if (found) return mapped
|
if (found) return mapped
|
||||||
@@ -120,7 +123,7 @@ inline fun <T, R: Any> Iterable<T>.findWithTransform(mappingPredicate: (T) -> Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Iterable<String>.filterExtractProps(propMappers: List<PropMapper<*,*,*>>, prefix: String, restParser: RestPropMapper<*,*>? = null) : Iterable<String> {
|
fun Iterable<String>.filterExtractProps(propMappers: List<PropMapper<*, *, *>>, prefix: String, restParser: RestPropMapper<*, *>? = null): Iterable<String> {
|
||||||
|
|
||||||
val iter = iterator()
|
val iter = iterator()
|
||||||
val rest = arrayListOf<String>()
|
val rest = arrayListOf<String>()
|
||||||
@@ -137,7 +140,7 @@ fun Iterable<String>.filterExtractProps(propMappers: List<PropMapper<*,*,*>>, pr
|
|||||||
propMapper != null -> {
|
propMapper != null -> {
|
||||||
val optionLength = prefix.length() + matchingOption!!.length()
|
val optionLength = prefix.length() + matchingOption!!.length()
|
||||||
when {
|
when {
|
||||||
propMapper is BoolPropMapper<*,*> -> {
|
propMapper is BoolPropMapper<*, *> -> {
|
||||||
if (param.length() > optionLength)
|
if (param.length() > optionLength)
|
||||||
throw IllegalArgumentException("Invalid switch option '$param', expecting $prefix$matchingOption without arguments")
|
throw IllegalArgumentException("Invalid switch option '$param', expecting $prefix$matchingOption without arguments")
|
||||||
propMapper.apply("")
|
propMapper.apply("")
|
||||||
@@ -166,19 +169,11 @@ fun Iterable<String>.filterExtractProps(propMappers: List<PropMapper<*,*,*>>, pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: find out how to create more generic variant using first constructor
|
|
||||||
//fun<C> C.propsToParams() {
|
|
||||||
// val kc = C::class
|
|
||||||
// kc.constructors.first().
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public interface OptionsGroup : Serializable {
|
public interface OptionsGroup : Serializable {
|
||||||
public val mappers: List<PropMapper<*,*,*>>
|
public val mappers: List<PropMapper<*, *, *>>
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Iterable<String>.filterExtractProps(vararg groups: OptionsGroup, prefix: String) : Iterable<String> =
|
public fun Iterable<String>.filterExtractProps(vararg groups: OptionsGroup, prefix: String): Iterable<String> =
|
||||||
filterExtractProps(groups.flatMap { it.mappers }, prefix)
|
filterExtractProps(groups.flatMap { it.mappers }, prefix)
|
||||||
|
|
||||||
|
|
||||||
@@ -189,13 +184,13 @@ public data class DaemonJVMOptions(
|
|||||||
public var jvmParams: MutableCollection<String> = arrayListOf()
|
public var jvmParams: MutableCollection<String> = arrayListOf()
|
||||||
) : OptionsGroup {
|
) : OptionsGroup {
|
||||||
|
|
||||||
override val mappers: List<PropMapper<*,*,*>>
|
override val mappers: List<PropMapper<*, *, *>>
|
||||||
get() = listOf( StringPropMapper(this, ::maxMemory, listOf("Xmx"), mergeDelimiter = ""),
|
get() = listOf(StringPropMapper(this, ::maxMemory, listOf("Xmx"), mergeDelimiter = ""),
|
||||||
StringPropMapper(this, ::maxPermSize, listOf("XX:MaxPermSize"), mergeDelimiter = "="),
|
StringPropMapper(this, ::maxPermSize, listOf("XX:MaxPermSize"), mergeDelimiter = "="),
|
||||||
StringPropMapper(this, ::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="),
|
StringPropMapper(this, ::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="),
|
||||||
restMapper)
|
restMapper)
|
||||||
|
|
||||||
val restMapper: RestPropMapper<*,*>
|
val restMapper: RestPropMapper<*, *>
|
||||||
get() = RestPropMapper(this, ::jvmParams)
|
get() = RestPropMapper(this, ::jvmParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,17 +203,17 @@ public data class DaemonOptions(
|
|||||||
) : OptionsGroup {
|
) : OptionsGroup {
|
||||||
|
|
||||||
override val mappers: List<PropMapper<*, *, *>>
|
override val mappers: List<PropMapper<*, *, *>>
|
||||||
get() = listOf( PropMapper(this, ::runFilesPath, fromString = { it.trim('"') }),
|
get() = listOf(PropMapper(this, ::runFilesPath, fromString = { it.trim('"') }),
|
||||||
PropMapper(this, ::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }, mergeDelimiter = "="),
|
PropMapper(this, ::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }, mergeDelimiter = "="),
|
||||||
PropMapper(this, ::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }, mergeDelimiter = "="),
|
PropMapper(this, ::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }, mergeDelimiter = "="),
|
||||||
NullablePropMapper(this, ::clientAliveFlagPath, fromString = { it }, toString = { "${it?.trim('\"','\'')}" }, mergeDelimiter = "="))
|
NullablePropMapper(this, ::clientAliveFlagPath, fromString = { it }, toString = { "${it?.trim('\"', '\'')}" }, mergeDelimiter = "="))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateSingleFileDigest(file: File, md: MessageDigest) {
|
fun updateSingleFileDigest(file: File, md: MessageDigest) {
|
||||||
DigestInputStream(file.inputStream(), md).use {
|
DigestInputStream(file.inputStream(), md).use {
|
||||||
val buf = ByteArray(1024)
|
val buf = ByteArray(1024)
|
||||||
while (it.read(buf) != -1) { }
|
while (it.read(buf) != -1) {}
|
||||||
it.close()
|
it.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,10 +227,10 @@ fun updateEntryDigest(entry: File, md: MessageDigest) {
|
|||||||
entry.isDirectory
|
entry.isDirectory
|
||||||
-> updateForAllClasses(entry, md)
|
-> updateForAllClasses(entry, md)
|
||||||
entry.isFile &&
|
entry.isFile &&
|
||||||
(entry.getName().endsWith(".class", ignoreCase = true) ||
|
(entry.extension.equals("class", ignoreCase = true) ||
|
||||||
entry.getName().endsWith(".jar", ignoreCase = true))
|
entry.extension.equals("jar", ignoreCase = true))
|
||||||
-> updateSingleFileDigest(entry, md)
|
-> updateSingleFileDigest(entry, md)
|
||||||
// else skip
|
// else skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,22 +245,21 @@ jvmName("getFilesClasspathDigest_Strings")
|
|||||||
fun Iterable<String>.getFilesClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest()
|
fun Iterable<String>.getFilesClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest()
|
||||||
|
|
||||||
fun Iterable<String>.distinctStringsDigest(): String =
|
fun Iterable<String>.distinctStringsDigest(): String =
|
||||||
MessageDigest.getInstance(COMPILER_ID_DIGEST)
|
MessageDigest.getInstance(COMPILER_ID_DIGEST)
|
||||||
.digest(this.distinct().sort().joinToString("").toByteArray())
|
.digest(this.distinct().sorted().joinToString("").toByteArray())
|
||||||
.joinToString("", transform = { "%02x".format(it) })
|
.joinToString("", transform = { "%02x".format(it) })
|
||||||
|
|
||||||
|
|
||||||
public data class CompilerId(
|
public data class CompilerId(
|
||||||
public var compilerClasspath: List<String> = listOf(),
|
public var compilerClasspath: List<String> = listOf(),
|
||||||
public var compilerDigest: String = "",
|
public var compilerDigest: String = "",
|
||||||
public var compilerVersion: String = ""
|
public var compilerVersion: String = ""
|
||||||
// TODO: checksum
|
|
||||||
) : OptionsGroup {
|
) : OptionsGroup {
|
||||||
|
|
||||||
override val mappers: List<PropMapper<*, *, *>>
|
override val mappers: List<PropMapper<*, *, *>>
|
||||||
get() = listOf( PropMapper(this, ::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trim('"').split(File.pathSeparator)}),
|
get() = listOf(PropMapper(this, ::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trim('"').split(File.pathSeparator) }),
|
||||||
StringPropMapper(this, ::compilerDigest),
|
StringPropMapper(this, ::compilerDigest),
|
||||||
StringPropMapper(this, ::compilerVersion))
|
StringPropMapper(this, ::compilerVersion))
|
||||||
|
|
||||||
public fun updateDigest() {
|
public fun updateDigest() {
|
||||||
compilerDigest = compilerClasspath.getFilesClasspathDigest()
|
compilerDigest = compilerClasspath.getFilesClasspathDigest()
|
||||||
@@ -275,7 +269,6 @@ public data class CompilerId(
|
|||||||
public jvmStatic fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable())
|
public jvmStatic fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable())
|
||||||
|
|
||||||
public jvmStatic fun makeCompilerId(paths: Iterable<File>): CompilerId =
|
public jvmStatic fun makeCompilerId(paths: Iterable<File>): CompilerId =
|
||||||
// TODO consider reading version here
|
|
||||||
CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest())
|
CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,14 +279,14 @@ public fun isDaemonEnabled(): Boolean = System.getProperty(COMPILE_DAEMON_ENABLE
|
|||||||
|
|
||||||
public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits: Boolean): DaemonJVMOptions {
|
public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits: Boolean): DaemonJVMOptions {
|
||||||
// note: sequence matters, explicit override in COMPILE_DAEMON_JVM_OPTIONS_PROPERTY should be done after inputArguments processing
|
// note: sequence matters, explicit override in COMPILE_DAEMON_JVM_OPTIONS_PROPERTY should be done after inputArguments processing
|
||||||
if (inheritMemoryLimits)
|
if (inheritMemoryLimits) {
|
||||||
ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-")
|
ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-")
|
||||||
|
}
|
||||||
System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let {
|
System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let {
|
||||||
opts.jvmParams.addAll( it.trim('"', '\'')
|
opts.jvmParams.addAll(it.trim('"', '\'')
|
||||||
.split("(?<!\\\\),".toRegex())
|
.split("(?<!\\\\),".toRegex())
|
||||||
.map { it.replace("[\\\\](.)".toRegex(), "$1") }
|
.map { it.replace("[\\\\](.)".toRegex(), "$1") }
|
||||||
.filterExtractProps(opts.mappers, "-", opts.restMapper))
|
.filterExtractProps(opts.mappers, "-", opts.restMapper))
|
||||||
}
|
}
|
||||||
|
|
||||||
System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let { opts.jvmParams.add("D" + COMPILE_DAEMON_REPORT_PERF_PROPERTY) }
|
System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let { opts.jvmParams.add("D" + COMPILE_DAEMON_REPORT_PERF_PROPERTY) }
|
||||||
@@ -302,7 +295,7 @@ public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun configureDaemonJVMOptions(inheritMemoryLimits: Boolean): DaemonJVMOptions =
|
public fun configureDaemonJVMOptions(inheritMemoryLimits: Boolean): DaemonJVMOptions =
|
||||||
configureDaemonJVMOptions(DaemonJVMOptions(), inheritMemoryLimits = inheritMemoryLimits)
|
configureDaemonJVMOptions(DaemonJVMOptions(), inheritMemoryLimits = inheritMemoryLimits)
|
||||||
|
|
||||||
public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
|
public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
|
||||||
System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)?.let {
|
System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)?.let {
|
||||||
@@ -313,7 +306,7 @@ public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
|
|||||||
"\nSupported options: " + opts.mappers.joinToString(", ", transform = { it.names.first() }))
|
"\nSupported options: " + opts.mappers.joinToString(", ", transform = { it.names.first() }))
|
||||||
}
|
}
|
||||||
System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)?.let {
|
System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)?.let {
|
||||||
val trimmed = it.trim('"','\'')
|
val trimmed = it.trim('"', '\'')
|
||||||
if (!trimmed.isBlank()) {
|
if (!trimmed.isBlank()) {
|
||||||
opts.clientAliveFlagPath = trimmed
|
opts.clientAliveFlagPath = trimmed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,28 +26,23 @@ import java.rmi.server.RMIServerSocketFactory
|
|||||||
|
|
||||||
|
|
||||||
// TODO switch to InetAddress.getLoopbackAddress on java 7+
|
// TODO switch to InetAddress.getLoopbackAddress on java 7+
|
||||||
val loopbackAddrName = if (java.net.InetAddress.getLocalHost() is java.net.Inet6Address) "::1" else "127.0.0.1"
|
val loopbackAddrName by lazy { if (java.net.InetAddress.getLocalHost() is java.net.Inet6Address) "::1" else "127.0.0.1" }
|
||||||
val loopbackAddr by lazy { InetAddress.getByName(loopbackAddrName) }
|
val loopbackAddr by lazy { InetAddress.getByName(loopbackAddrName) }
|
||||||
val serverLoopbackSocketFactory by lazy { ServerLoopbackSocketFactory() }
|
val serverLoopbackSocketFactory by lazy { ServerLoopbackSocketFactory() }
|
||||||
val clientLoopbackSocketFactory by lazy { ClientLoopbackSocketFactory() }
|
val clientLoopbackSocketFactory by lazy { ClientLoopbackSocketFactory() }
|
||||||
|
|
||||||
|
|
||||||
data class ServerLoopbackSocketFactory : RMIServerSocketFactory, Serializable {
|
data class ServerLoopbackSocketFactory : RMIServerSocketFactory, Serializable {
|
||||||
|
|
||||||
throws(IOException::class)
|
throws(IOException::class)
|
||||||
override fun createServerSocket(port: Int): ServerSocket {
|
override fun createServerSocket(port: Int): ServerSocket = ServerSocket(port, 5, loopbackAddr)
|
||||||
return ServerSocket(port, 5, loopbackAddr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class ClientLoopbackSocketFactory : RMIClientSocketFactory, Serializable {
|
data class ClientLoopbackSocketFactory : RMIClientSocketFactory, Serializable {
|
||||||
|
|
||||||
throws(IOException::class)
|
throws(IOException::class)
|
||||||
override fun createSocket(host: String, port: Int): Socket {
|
override fun createSocket(host: String, port: Int): Socket = Socket(loopbackAddr, port)
|
||||||
return Socket(loopbackAddr, port)
|
|
||||||
// just call the default client socket factory
|
|
||||||
// return RMISocketFactory.getDefaultSocketFactory().createSocket(host, port)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ package org.jetbrains.kotlin.rmi.service
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||||
import org.jetbrains.kotlin.rmi.*
|
import org.jetbrains.kotlin.rmi.*
|
||||||
import org.jetbrains.kotlin.service.CompileServiceImpl
|
|
||||||
import org.jetbrains.kotlin.service.nowSeconds
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -57,151 +55,148 @@ class LogStream(name: String) : OutputStream() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class CompileDaemon {
|
public object CompileDaemon {
|
||||||
|
|
||||||
companion object {
|
init {
|
||||||
|
val logPath: String = System.getProperty("kotlin.daemon.log.path")?.trimEnd('/','\\') ?: "%t"
|
||||||
|
val logTime: String = SimpleDateFormat("yyyy-MM-dd.HH-mm-ss-SSS").format(Date())
|
||||||
|
val cfg: String =
|
||||||
|
"handlers = java.util.logging.FileHandler\n" +
|
||||||
|
"java.util.logging.FileHandler.level = ALL\n" +
|
||||||
|
"java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" +
|
||||||
|
"java.util.logging.FileHandler.encoding = UTF-8\n" +
|
||||||
|
"java.util.logging.FileHandler.limit = 1073741824\n" + // 1Mb
|
||||||
|
"java.util.logging.FileHandler.count = 3\n" +
|
||||||
|
"java.util.logging.FileHandler.append = false\n" +
|
||||||
|
"java.util.logging.FileHandler.pattern = $logPath/$COMPILE_DAEMON_DEFAULT_FILES_PREFIX.$logTime.%u%g.log\n" +
|
||||||
|
"java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s\\n\n"
|
||||||
|
|
||||||
init {
|
LogManager.getLogManager().readConfiguration(cfg.byteInputStream())
|
||||||
val logPath: String = System.getProperty("kotlin.daemon.log.path")?.trimEnd('/','\\') ?: "%t"
|
}
|
||||||
val logTime: String = SimpleDateFormat("yyyy-MM-dd.HH-mm-ss-SSS").format(Date())
|
|
||||||
val cfg: String =
|
|
||||||
"handlers = java.util.logging.FileHandler\n" +
|
|
||||||
"java.util.logging.FileHandler.level = ALL\n" +
|
|
||||||
"java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" +
|
|
||||||
"java.util.logging.FileHandler.encoding = UTF-8\n" +
|
|
||||||
"java.util.logging.FileHandler.limit = 1073741824\n" + // 1Mb
|
|
||||||
"java.util.logging.FileHandler.count = 3\n" +
|
|
||||||
"java.util.logging.FileHandler.append = false\n" +
|
|
||||||
"java.util.logging.FileHandler.pattern = $logPath/$COMPILE_DAEMON_DEFAULT_FILES_PREFIX.$logTime.%u%g.log\n" +
|
|
||||||
"java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s\\n\n"
|
|
||||||
|
|
||||||
LogManager.getLogManager().readConfiguration(cfg.byteInputStream())
|
val log by lazy { Logger.getLogger("daemon") }
|
||||||
}
|
|
||||||
|
|
||||||
val log by lazy { Logger.getLogger("daemon") }
|
private fun loadVersionFromResource(): String? {
|
||||||
|
(CompileDaemon::class.java.classLoader as? URLClassLoader)
|
||||||
private fun loadVersionFromResource(): String? {
|
?.findResource("META-INF/MANIFEST.MF")
|
||||||
(javaClass.classLoader as? URLClassLoader)
|
?.let {
|
||||||
?.findResource("META-INF/MANIFEST.MF")
|
try {
|
||||||
?.let {
|
return Manifest(it.openStream()).mainAttributes.getValue("Implementation-Version") ?: null
|
||||||
try {
|
|
||||||
return Manifest(it.openStream()).mainAttributes.getValue("Implementation-Version") ?: ""
|
|
||||||
}
|
|
||||||
catch (e: IOException) {}
|
|
||||||
}
|
}
|
||||||
return null
|
catch (e: IOException) {}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
jvmStatic public fun main(args: Array<String>) {
|
||||||
|
|
||||||
|
log.info("Kotlin compiler daemon version " + (loadVersionFromResource() ?: "<unknown>"))
|
||||||
|
log.info("daemon JVM args: " + ManagementFactory.getRuntimeMXBean().inputArguments.joinToString(" "))
|
||||||
|
log.info("daemon args: " + args.joinToString(" "))
|
||||||
|
|
||||||
|
val compilerId = CompilerId()
|
||||||
|
val daemonOptions = DaemonOptions()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX)
|
||||||
|
|
||||||
|
if (filteredArgs.any()) {
|
||||||
|
val helpLine = "usage: <daemon> <compilerId options> <daemon options>"
|
||||||
|
log.info(helpLine)
|
||||||
|
println(helpLine)
|
||||||
|
throw IllegalArgumentException("Unknown arguments: " + filteredArgs.joinToString(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("starting daemon")
|
||||||
|
|
||||||
|
// TODO: find minimal set of permissions and restore security management
|
||||||
|
// note: may be not needed anymore since (hopefully) server is now loopback-only
|
||||||
|
// if (System.getSecurityManager() == null)
|
||||||
|
// System.setSecurityManager (RMISecurityManager())
|
||||||
|
//
|
||||||
|
// setDaemonPpermissions(daemonOptions.port)
|
||||||
|
|
||||||
|
val (registry, port) = createRegistry(COMPILE_DAEMON_FIND_PORT_ATTEMPTS)
|
||||||
|
val runFileDir = File(if (daemonOptions.runFilesPath.isBlank()) COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH else daemonOptions.runFilesPath)
|
||||||
|
runFileDir.mkdirs()
|
||||||
|
val runFile = File(runFileDir,
|
||||||
|
makeRunFilenameString(ts = "%tFT%<tT.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
|
||||||
|
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
||||||
|
port = port.toString()))
|
||||||
|
if (!runFile.createNewFile()) {
|
||||||
|
throw IllegalStateException("Unable to create run file '${runFile.absolutePath}'")
|
||||||
|
}
|
||||||
|
runFile.deleteOnExit()
|
||||||
|
|
||||||
|
val compiler = K2JVMCompiler()
|
||||||
|
val compilerService = CompileServiceImpl(registry, compiler, compilerId, port)
|
||||||
|
|
||||||
|
if (daemonOptions.runFilesPath.isNotEmpty())
|
||||||
|
println(daemonOptions.runFilesPath)
|
||||||
|
|
||||||
|
daemonOptions.clientAliveFlagPath?.let {
|
||||||
|
if (!File(it).exists()) {
|
||||||
|
log.info("Client alive flag $it do not exist, disable watching it")
|
||||||
|
daemonOptions.clientAliveFlagPath = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this supposed to stop redirected streams reader(s) on the client side and prevent some situations with hanging threads, but doesn't work reliably
|
||||||
|
// TODO: implement more reliable scheme
|
||||||
|
System.out.close()
|
||||||
|
System.err.close()
|
||||||
|
|
||||||
|
System.setErr(PrintStream(LogStream("stderr")))
|
||||||
|
System.setOut(PrintStream(LogStream("stdout")))
|
||||||
|
|
||||||
|
fun shutdownCondition(check: () -> Boolean, message: String): Boolean {
|
||||||
|
val res = check()
|
||||||
|
if (res) {
|
||||||
|
log.info(message)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopping daemon if any shutdown condition is met
|
||||||
|
timer(initialDelay = DAEMON_PERIODIC_CHECK_INTERVAL_MS, period = DAEMON_PERIODIC_CHECK_INTERVAL_MS) {
|
||||||
|
val idleSeconds = nowSeconds() - compilerService.lastUsedSeconds
|
||||||
|
if (shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") ||
|
||||||
|
shutdownCondition({ daemonOptions.autoshutdownIdleSeconds != COMPILE_DAEMON_TIMEOUT_INFINITE_S && idleSeconds > daemonOptions.autoshutdownIdleSeconds},
|
||||||
|
"Idle timeout exceeded ${daemonOptions.autoshutdownIdleSeconds}s, shutting down") ||
|
||||||
|
shutdownCondition({ daemonOptions.clientAliveFlagPath?.let { !File(it).exists() } ?: false },
|
||||||
|
"Client alive flag ${daemonOptions.clientAliveFlagPath} removed, shutting down"))
|
||||||
|
{
|
||||||
|
cancel()
|
||||||
|
compilerService.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
System.err.println("Exception: " + e.getMessage())
|
||||||
|
log.info("Exception" + e.getMessage())
|
||||||
|
log.info(e.toString())
|
||||||
|
// TODO consider exiting without throwing
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
jvmStatic public fun main(args: Array<String>) {
|
}
|
||||||
|
|
||||||
log.info("Kotlin compiler daemon version " + (loadVersionFromResource() ?: "<unknown>"))
|
val random = Random()
|
||||||
log.info("daemon JVM args: " + ManagementFactory.getRuntimeMXBean().inputArguments.joinToString(" "))
|
|
||||||
log.info("daemon args: " + args.joinToString(" "))
|
|
||||||
|
|
||||||
val compilerId = CompilerId()
|
private fun createRegistry(attempts: Int) : Pair<Registry, Int> {
|
||||||
val daemonOptions = DaemonOptions()
|
var i = 0
|
||||||
|
var lastException: RemoteException? = null
|
||||||
|
|
||||||
|
while (i++ < attempts) {
|
||||||
|
val port = random.nextInt(COMPILE_DAEMON_PORTS_RANGE_END - COMPILE_DAEMON_PORTS_RANGE_START) + COMPILE_DAEMON_PORTS_RANGE_START
|
||||||
try {
|
try {
|
||||||
val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX)
|
return Pair(LocateRegistry.createRegistry(port, clientLoopbackSocketFactory, serverLoopbackSocketFactory), port)
|
||||||
|
|
||||||
if (filteredArgs.any()) {
|
|
||||||
val helpLine = "usage: <daemon> <compilerId options> <daemon options>"
|
|
||||||
log.info(helpLine)
|
|
||||||
println(helpLine)
|
|
||||||
throw IllegalArgumentException("Unknown arguments: " + filteredArgs.joinToString(" "))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("starting daemon")
|
|
||||||
|
|
||||||
// TODO: find minimal set of permissions and restore security management
|
|
||||||
// note: may be not needed anymore since (hopefully) server is now loopback-only
|
|
||||||
// if (System.getSecurityManager() == null)
|
|
||||||
// System.setSecurityManager (RMISecurityManager())
|
|
||||||
//
|
|
||||||
// setDaemonPpermissions(daemonOptions.port)
|
|
||||||
|
|
||||||
val (registry, port) = createRegistry(COMPILE_DAEMON_FIND_PORT_ATTEMPTS)
|
|
||||||
val runFileDir = File(if (daemonOptions.runFilesPath.isBlank()) COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH else daemonOptions.runFilesPath)
|
|
||||||
runFileDir.mkdirs()
|
|
||||||
val runFile = File(runFileDir,
|
|
||||||
makeRunFilenameString(ts = "%tFT%<tT.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
|
|
||||||
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
|
||||||
port = port.toString()))
|
|
||||||
if (!runFile.createNewFile()) {
|
|
||||||
throw IllegalStateException("Unable to create run file '${runFile.absolutePath}'")
|
|
||||||
}
|
|
||||||
runFile.deleteOnExit()
|
|
||||||
|
|
||||||
val compiler = K2JVMCompiler()
|
|
||||||
val compilerService = CompileServiceImpl(registry, compiler, compilerId, daemonOptions, port)
|
|
||||||
|
|
||||||
if (daemonOptions.runFilesPath.isNotEmpty())
|
|
||||||
println(daemonOptions.runFilesPath)
|
|
||||||
|
|
||||||
daemonOptions.clientAliveFlagPath?.let {
|
|
||||||
if (!File(it).exists()) {
|
|
||||||
log.info("Client alive flag $it do not exist, disable watching it")
|
|
||||||
daemonOptions.clientAliveFlagPath = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this supposed to stop redirected streams reader(s) on the client side and prevent some situations with hanging threads, but doesn't work reliably
|
|
||||||
// TODO: implement more reliable scheme
|
|
||||||
System.out.close()
|
|
||||||
System.err.close()
|
|
||||||
|
|
||||||
System.setErr(PrintStream(LogStream("stderr")))
|
|
||||||
System.setOut(PrintStream(LogStream("stdout")))
|
|
||||||
|
|
||||||
fun shutdownCondition(check: () -> Boolean, message: String): Boolean {
|
|
||||||
val res = check()
|
|
||||||
if (res) {
|
|
||||||
log.info(message)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopping daemon if any shutdown condition is met
|
|
||||||
timer(initialDelay = DAEMON_PERIODIC_CHECK_INTERVAL_MS, period = DAEMON_PERIODIC_CHECK_INTERVAL_MS) {
|
|
||||||
val idleSeconds = nowSeconds() - compilerService.lastUsedSeconds
|
|
||||||
if (shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") ||
|
|
||||||
shutdownCondition({ daemonOptions.autoshutdownIdleSeconds != COMPILE_DAEMON_TIMEOUT_INFINITE_S && idleSeconds > daemonOptions.autoshutdownIdleSeconds},
|
|
||||||
"Idle timeout exceeded ${daemonOptions.autoshutdownIdleSeconds}s, shutting down") ||
|
|
||||||
shutdownCondition({ daemonOptions.clientAliveFlagPath?.let { !File(it).exists() } ?: false },
|
|
||||||
"Client alive flag ${daemonOptions.clientAliveFlagPath} removed, shutting down"))
|
|
||||||
{
|
|
||||||
cancel()
|
|
||||||
compilerService.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: RemoteException) {
|
||||||
System.err.println("Exception: " + e.getMessage())
|
// assuming that the port is already taken
|
||||||
log.info("Exception" + e.getMessage())
|
lastException = e
|
||||||
log.info(e.toString())
|
|
||||||
// TODO consider exiting without throwing
|
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
val random = Random()
|
|
||||||
|
|
||||||
private fun createRegistry(attempts: Int) : Pair<Registry, Int> {
|
|
||||||
var i = 0
|
|
||||||
var lastException: RemoteException? = null
|
|
||||||
|
|
||||||
while (i++ < attempts) {
|
|
||||||
val port = random.nextInt(COMPILE_DAEMON_PORTS_RANGE_END - COMPILE_DAEMON_PORTS_RANGE_START) + COMPILE_DAEMON_PORTS_RANGE_START
|
|
||||||
try {
|
|
||||||
return Pair(LocateRegistry.createRegistry(port, clientLoopbackSocketFactory, serverLoopbackSocketFactory), port)
|
|
||||||
}
|
|
||||||
catch (e: RemoteException) {
|
|
||||||
// assuming that the port is already taken
|
|
||||||
lastException = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw IllegalStateException("Cannot find free port in $attempts attempts", lastException)
|
|
||||||
}
|
}
|
||||||
|
throw IllegalStateException("Cannot find free port in $attempts attempts", lastException)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+14
-16
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.service
|
package org.jetbrains.kotlin.rmi.service
|
||||||
|
|
||||||
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
|
||||||
@@ -24,13 +24,12 @@ import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
|||||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
||||||
import org.jetbrains.kotlin.modules.TargetId
|
import org.jetbrains.kotlin.modules.TargetId
|
||||||
import org.jetbrains.kotlin.rmi.*
|
import org.jetbrains.kotlin.rmi.*
|
||||||
import org.jetbrains.kotlin.rmi.service.RemoteIncrementalCacheClient
|
|
||||||
import org.jetbrains.kotlin.rmi.service.RemoteOutputStreamClient
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
import java.lang.management.ThreadMXBean
|
import java.lang.management.ThreadMXBean
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
import java.rmi.NoSuchObjectException
|
||||||
import java.rmi.registry.Registry
|
import java.rmi.registry.Registry
|
||||||
import java.rmi.server.UnicastRemoteObject
|
import java.rmi.server.UnicastRemoteObject
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -47,7 +46,6 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
val registry: Registry,
|
val registry: Registry,
|
||||||
val compiler: Compiler,
|
val compiler: Compiler,
|
||||||
val selfCompilerId: CompilerId,
|
val selfCompilerId: CompilerId,
|
||||||
val daemonOptions: DaemonOptions,
|
|
||||||
port: Int
|
port: Int
|
||||||
) : CompileService, UnicastRemoteObject() {
|
) : CompileService, UnicastRemoteObject() {
|
||||||
|
|
||||||
@@ -120,7 +118,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
// cleanup for the case of incorrect restart and many other situations
|
// cleanup for the case of incorrect restart and many other situations
|
||||||
UnicastRemoteObject.unexportObject(this, false)
|
UnicastRemoteObject.unexportObject(this, false)
|
||||||
}
|
}
|
||||||
catch (e: java.rmi.NoSuchObjectException) {
|
catch (e: NoSuchObjectException) {
|
||||||
// ignoring if object already exported
|
// ignoring if object already exported
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,8 +136,8 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
|
|
||||||
private fun doCompile(args: Array<out String>, compilerMessagesStreamProxy: RemoteOutputStream, serviceOutputStreamProxy: RemoteOutputStream, body: (PrintStream) -> ExitCode): Int =
|
private fun doCompile(args: Array<out String>, compilerMessagesStreamProxy: RemoteOutputStream, serviceOutputStreamProxy: RemoteOutputStream, body: (PrintStream) -> ExitCode): Int =
|
||||||
ifAlive {
|
ifAlive {
|
||||||
val compilerMessagesStream = PrintStream( RemoteOutputStreamClient(compilerMessagesStreamProxy))
|
val compilerMessagesStream = PrintStream(RemoteOutputStreamClient(compilerMessagesStreamProxy))
|
||||||
val serviceOutputStream = PrintStream( RemoteOutputStreamClient(serviceOutputStreamProxy))
|
val serviceOutputStream = PrintStream(RemoteOutputStreamClient(serviceOutputStreamProxy))
|
||||||
checkedCompile(args, serviceOutputStream) {
|
checkedCompile(args, serviceOutputStream) {
|
||||||
val res = body( compilerMessagesStream).code
|
val res = body( compilerMessagesStream).code
|
||||||
_lastUsedSeconds = nowSeconds()
|
_lastUsedSeconds = nowSeconds()
|
||||||
@@ -176,8 +174,8 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ThreadMXBean.threadCputime() = if (isCurrentThreadCpuTimeSupported()) currentThreadCpuTime else 0L
|
fun ThreadMXBean.threadCpuTime() = if (isCurrentThreadCpuTimeSupported) currentThreadCpuTime else 0L
|
||||||
fun ThreadMXBean.threadUsertime() = if (isCurrentThreadCpuTimeSupported()) currentThreadUserTime else 0L
|
fun ThreadMXBean.threadUserTime() = if (isCurrentThreadCpuTimeSupported) currentThreadUserTime else 0L
|
||||||
|
|
||||||
fun<R> checkedCompile(args: Array<out String>, serviceOut: PrintStream, body: () -> R): R {
|
fun<R> checkedCompile(args: Array<out String>, serviceOut: PrintStream, body: () -> R): R {
|
||||||
try {
|
try {
|
||||||
@@ -187,12 +185,12 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
val threadMXBean: ThreadMXBean = ManagementFactory.getThreadMXBean()
|
val threadMXBean: ThreadMXBean = ManagementFactory.getThreadMXBean()
|
||||||
val startMem = usedMemory() / 1024
|
val startMem = usedMemory() / 1024
|
||||||
val startTime = System.nanoTime()
|
val startTime = System.nanoTime()
|
||||||
val startThreadTime = threadMXBean.threadCputime()
|
val startThreadTime = threadMXBean.threadCpuTime()
|
||||||
val startThreadUserTime = threadMXBean.threadUsertime()
|
val startThreadUserTime = threadMXBean.threadUserTime()
|
||||||
val res = body()
|
val res = body()
|
||||||
val endTime = System.nanoTime()
|
val endTime = System.nanoTime()
|
||||||
val endThreadTime = threadMXBean.threadCputime()
|
val endThreadTime = threadMXBean.threadCpuTime()
|
||||||
val endThreadUserTime = threadMXBean.threadUsertime()
|
val endThreadUserTime = threadMXBean.threadUserTime()
|
||||||
val endMem = usedMemory() / 1024
|
val endMem = usedMemory() / 1024
|
||||||
log.info("Done with result " + res.toString())
|
log.info("Done with result " + res.toString())
|
||||||
val elapsed = TimeUnit.NANOSECONDS.toMillis(endTime - startTime)
|
val elapsed = TimeUnit.NANOSECONDS.toMillis(endTime - startTime)
|
||||||
@@ -201,7 +199,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
log.info("Elapsed time: $elapsed ms (thread user: $elapsedThreadUser ms sys: ${elapsedThread - elapsedThreadUser} ms)")
|
log.info("Elapsed time: $elapsed ms (thread user: $elapsedThreadUser ms sys: ${elapsedThread - elapsedThreadUser} ms)")
|
||||||
log.info("Used memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)")
|
log.info("Used memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)")
|
||||||
System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let {
|
System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let {
|
||||||
serviceOut.println("PERF: TOTAL: $elapsed ms (thread user: $elapsedThreadUser ms sys: ${elapsedThread - elapsedThreadUser} ms); memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)")
|
serviceOut.println("PERF: Compile on daemon: $elapsed ms (thread user: $elapsedThreadUser ms sys: ${elapsedThread - elapsedThreadUser} ms); memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)")
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -214,12 +212,12 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
|
|
||||||
fun<R> ifAlive(body: () -> R): R = rwlock.read {
|
fun<R> ifAlive(body: () -> R): R = rwlock.read {
|
||||||
if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state")
|
if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state")
|
||||||
else body()
|
body()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun<R> ifAliveExclusive(body: () -> R): R = rwlock.write {
|
fun<R> ifAliveExclusive(body: () -> R): R = rwlock.write {
|
||||||
if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state")
|
if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state")
|
||||||
else body()
|
body()
|
||||||
}
|
}
|
||||||
|
|
||||||
// sometimes used for debugging
|
// sometimes used for debugging
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
|||||||
|
|
||||||
data class CompilerResults(val resultCode: Int, val out: String)
|
data class CompilerResults(val resultCode: Int, val out: String)
|
||||||
|
|
||||||
val daemonOptions = DaemonOptions(runFilesPath = tmpdir.absolutePath)
|
val daemonOptions by lazy { DaemonOptions(runFilesPath = tmpdir.absolutePath) }
|
||||||
val daemonJVMOptions = DaemonJVMOptions()
|
val daemonJVMOptions by lazy { DaemonJVMOptions() }
|
||||||
val compilerId by lazy { CompilerId.makeCompilerId( File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler.jar"),
|
val compilerId by lazy { CompilerId.makeCompilerId( File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler.jar"),
|
||||||
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-runtime.jar"),
|
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-runtime.jar"),
|
||||||
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-reflect.jar")) }
|
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-reflect.jar")) }
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public class KotlinCompilerRunner {
|
|||||||
|
|
||||||
ArrayList<DaemonReportMessage> daemonReportMessages = new ArrayList<DaemonReportMessage>();
|
ArrayList<DaemonReportMessage> daemonReportMessages = new ArrayList<DaemonReportMessage>();
|
||||||
|
|
||||||
CompileService daemon = KotlinCompilerClient.Companion.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, new DaemonReportingTargets(null, daemonReportMessages), true, true);
|
CompileService daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, new DaemonReportingTargets(null, daemonReportMessages), true, true);
|
||||||
|
|
||||||
for (DaemonReportMessage msg: daemonReportMessages) {
|
for (DaemonReportMessage msg: daemonReportMessages) {
|
||||||
if (msg.getCategory() == DaemonReportCategory.EXCEPTION && daemon == null) {
|
if (msg.getCategory() == DaemonReportCategory.EXCEPTION && daemon == null) {
|
||||||
@@ -182,7 +182,7 @@ public class KotlinCompilerRunner {
|
|||||||
ByteArrayOutputStream compilerOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream compilerOut = new ByteArrayOutputStream();
|
||||||
ByteArrayOutputStream daemonOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream daemonOut = new ByteArrayOutputStream();
|
||||||
|
|
||||||
Integer res = KotlinCompilerClient.Companion.incrementalCompile(daemon, argsArray, incrementalCaches, compilerOut, daemonOut);
|
Integer res = KotlinCompilerClient.incrementalCompile(daemon, argsArray, incrementalCaches, compilerOut, daemonOut);
|
||||||
|
|
||||||
ProcessCompilerOutput(messageCollector, collector, compilerOut, res.toString());
|
ProcessCompilerOutput(messageCollector, collector, compilerOut, res.toString());
|
||||||
BufferedReader reader = new BufferedReader(new StringReader(daemonOut.toString()));
|
BufferedReader reader = new BufferedReader(new StringReader(daemonOut.toString()));
|
||||||
|
|||||||
Reference in New Issue
Block a user