diff --git a/compiler/rmi/kotlinr/src/KotlinCompilerClient.kt b/compiler/rmi/kotlinr/src/KotlinCompilerClient.kt index ef19a22b6cb..e752f82a901 100644 --- a/compiler/rmi/kotlinr/src/KotlinCompilerClient.kt +++ b/compiler/rmi/kotlinr/src/KotlinCompilerClient.kt @@ -31,13 +31,22 @@ import kotlin.concurrent.thread import kotlin.concurrent.write import kotlin.platform.platformStatic -val DAEMON_STARTUP_TIMEOUT_MS = 10000L -val DAEMON_STARTUP_CHECK_INTERVAL_MS = 100L +fun Process.isAlive() = + try { + this.exitValue() + false + } + catch (e: IllegalThreadStateException) { + true + } public class KotlinCompilerClient { companion object { + val DAEMON_STARTUP_TIMEOUT_MS = 10000L + val DAEMON_STARTUP_CHECK_INTERVAL_MS = 100L + private fun connectToService(compilerId: CompilerId, daemonOptions: DaemonOptions, errStream: PrintStream): CompileService? { val compilerObj = connectToDaemon(compilerId, daemonOptions, errStream) ?: return null // no registry - no daemon running @@ -61,16 +70,6 @@ public class KotlinCompilerClient { } - fun Process.isAlive() = - try { - this.exitValue() - false - } - catch (e: IllegalThreadStateException) { - true - } - - private fun startDaemon(compilerId: CompilerId, daemonOptions: DaemonOptions, errStream: PrintStream) { val javaExecutable = listOf(System.getProperty("java.home"), "bin", "java").joinToString(File.separator) // TODO: add some specific environment variables to the cp and may be command line, to allow some specific startup configs @@ -128,7 +127,8 @@ public class KotlinCompilerClient { errStream.println("[daemon client] remoteId = " + remoteId.toString()) errStream.println("[daemon client] localId = " + localId.toString()) return (localId.compilerVersion.isEmpty() || localId.compilerVersion == remoteId.compilerVersion) && - (localId.compilerClasspath.all { remoteId.compilerClasspath.contains(it) }) + (localId.compilerClasspath.all { remoteId.compilerClasspath.contains(it) }) && + (localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest) } public fun connectToCompileService(compilerId: CompilerId, daemonOptions: DaemonOptions, errStream: PrintStream, autostart: Boolean = true, checkId: Boolean = true): CompileService? { @@ -210,6 +210,8 @@ public class KotlinCompilerClient { throw IllegalArgumentException("Cannot find compiler jar") else println("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator)) + + compilerId.updateDigest() } connectToCompileService(compilerId, daemonOptions, System.out, autostart = !clientOptions.stop, checkId = !clientOptions.stop)?.let { @@ -220,12 +222,12 @@ public class KotlinCompilerClient { println("Daemon shut down successfully") } else -> { - println("Executing daemon compilation with args: " + args.joinToString(" ")) + println("Executing daemon compilation with args: " + filteredArgs.joinToString(" ")) val outStrm = RemoteOutputStreamServer(System.out) try { val memBefore = it.getUsedMemory() / 1024 val startTime = System.nanoTime() - val res = it.remoteCompile(args, outStrm, CompileService.OutputFormat.PLAIN) + val res = it.remoteCompile(filteredArgs.toArrayList().toTypedArray(), outStrm, CompileService.OutputFormat.PLAIN) val endTime = System.nanoTime() println("Compilation result code: $res") val memAfter = it.getUsedMemory() / 1024 diff --git a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt index 03ed6fc9ab9..36fa8ad067f 100644 --- a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt +++ b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.rmi import java.io.File import java.io.Serializable +import java.security.DigestInputStream +import java.security.MessageDigest import kotlin.platform.platformStatic import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 @@ -98,8 +100,44 @@ public data class DaemonOptions( } +val COMPILER_ID_DIGEST = "MD5" + + +fun updateSingleFileDigest(file: File, md: MessageDigest) { + val stream = DigestInputStream(file.inputStream(), md) + val buf = ByteArray(1024) + while (stream.read(buf) == buf.size()) {} + stream.close() +} + +fun updateForAllClasses(dir: File, md: MessageDigest) { + dir.walk().forEach { updateEntryDigest(it, md) } +} + +fun updateEntryDigest(entry: File, md: MessageDigest) { + when { + entry.isDirectory + -> updateForAllClasses(entry, md) + entry.isFile && + (entry.getName().endsWith(".class", ignoreCase = true) || + entry.getName().endsWith(".jar", ignoreCase = true)) + -> updateSingleFileDigest(entry, md) + // else skip + } +} + +fun Iterable.getFilesClasspathDigest(): String { + val md = MessageDigest.getInstance(COMPILER_ID_DIGEST) + this.forEach { updateEntryDigest(it, md) } + return md.digest().joinToString("", transform = { "%02x".format(it) }) +} + +fun Iterable.getClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest() + + public data class CompilerId( public var compilerClasspath: List = listOf(), + public var compilerDigest: String = "", public var compilerVersion: String = "" // TODO: checksum ) : CmdlineParams { @@ -107,17 +145,25 @@ public data class CompilerId( override val asParams: Iterable get() = propToParams(::compilerClasspath, { it.joinToString(File.pathSeparator) }) + + propToParams(::compilerDigest) + propToParams(::compilerVersion) override val parsers: List> get() = listOf( PropParser(this, ::compilerClasspath, { it.trim('"').split(File.pathSeparator)}), + PropParser(this, ::compilerDigest, { it.trim('"') }), PropParser(this, ::compilerVersion, { it.trim('"') })) + public fun updateDigest() { + compilerDigest = compilerClasspath.getClasspathDigest() + } + companion object { - public platformStatic fun makeCompilerId(libPath: File): CompilerId = + public platformStatic fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable()) + + public platformStatic fun makeCompilerId(paths: Iterable): CompilerId = // TODO consider reading version and calculating checksum here - CompilerId(compilerClasspath = listOf(libPath.absolutePath)) + CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest()) } } diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt index 2d6b560dce0..e6252f09853 100644 --- a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt @@ -48,8 +48,9 @@ public class CompileDaemon { // setDaemonPpermissions(daemonOptions.port) val registry = LocateRegistry.createRegistry(daemonOptions.port); + val compiler = K2JVMCompiler() - val server = CompileServiceImpl(registry, K2JVMCompiler()) + val server = CompileServiceImpl(registry, compiler, compilerId, daemonOptions) if (daemonOptions.startEcho.isNotEmpty()) println(daemonOptions.startEcho) diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt index 244f957ebec..6ec830a659e 100644 --- a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt @@ -17,14 +17,12 @@ package org.jetbrains.kotlin.service import org.jetbrains.kotlin.cli.common.CLICompiler +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents -import org.jetbrains.kotlin.rmi.COMPILER_SERVICE_RMI_NAME -import org.jetbrains.kotlin.rmi.CompileService -import org.jetbrains.kotlin.rmi.CompilerId -import org.jetbrains.kotlin.rmi.RemoteOutputStream +import org.jetbrains.kotlin.rmi.* import org.jetbrains.kotlin.rmi.service.RemoteIncrementalCacheClient import org.jetbrains.kotlin.rmi.service.RemoteOutputStreamClient import java.io.File @@ -43,22 +41,28 @@ import kotlin.concurrent.read import kotlin.concurrent.write -class CompileServiceImpl>(val registry: Registry, val compiler: Compiler) : CompileService, UnicastRemoteObject() { +class CompileServiceImpl>( + val registry: Registry, + val compiler: Compiler, + val selfCompilerId: CompilerId, + val daemonOptions: DaemonOptions +) : CompileService, UnicastRemoteObject() { private val rwlock = ReentrantReadWriteLock() private var alive = false - private val selfCompilerId by lazy { - // TODO: add classpath checksum calculated on init, add it to the interface and use to decide whether it was changed during the lifetime of the daemon - CompilerId( - compilerClasspath = System.getProperty("java.class.path") - ?.split(File.pathSeparator) - ?.map { File(it) } - ?.filter { it.exists() } - ?.map { it.absolutePath } - ?: listOf(), - compilerVersion = loadKotlinVersionFromResource() - ) - } + + // TODO: consider matching compilerId coming from outside with actual one +// private val selfCompilerId by lazy { +// CompilerId( +// compilerClasspath = System.getProperty("java.class.path") +// ?.split(File.pathSeparator) +// ?.map { File(it) } +// ?.filter { it.exists() } +// ?.map { it.absolutePath } +// ?: listOf(), +// compilerVersion = loadKotlinVersionFromResource() +// ) +// } init { // assuming logically synchronized diff --git a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java index 4ce09052a0f..4921e35768b 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java +++ b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java @@ -132,10 +132,12 @@ public class KotlinCompilerRunner { // trying the daemon first if (incrementalCaches != null && KotlinCompilerClient.Companion.isDaemonEnabled()) { File libPath = CompilerRunnerUtil.getLibPath(environment.getKotlinPaths(), messageCollector); + // TODO: it may be a good idea to cache the compilerId, since making it means calculating digest over jar(s) and if \\ + // the lifetime of JPS process is small anyway, we can neglect the probability of changed compiler CompilerId compilerId = CompilerId.makeCompilerId(new File(libPath, "kotlin-compiler.jar")); DaemonOptions daemonOptions = new DaemonOptions(); // TODO: find proper stream to report daemon connection progress - CompileService daemon = KotlinCompilerClient.Companion.connectToCompileService(compilerId, daemonOptions, System.out); + CompileService daemon = KotlinCompilerClient.Companion.connectToCompileService(compilerId, daemonOptions, System.out, true, true); if (daemon != null) { Integer res = KotlinCompilerClient.Companion.incrementalCompile(daemon, argsArray, incrementalCaches, out); return res.toString();