Refactorings, reformatting code, applying code style and other cleanup

This commit is contained in:
Ilya Chernikov
2015-09-08 17:28:31 +02:00
parent d448602cb2
commit 96558c52ff
7 changed files with 570 additions and 576 deletions
@@ -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,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()));