Adding compiler id digest calculation and check to detect replaced compiler jar, minor fixes and refactorings

This commit is contained in:
ligee
2015-08-17 19:13:03 +02:00
committed by Ilya Chernikov
parent f08476cba9
commit 39d6592e1f
5 changed files with 91 additions and 36 deletions
@@ -31,13 +31,22 @@ import kotlin.concurrent.thread
import kotlin.concurrent.write import kotlin.concurrent.write
import kotlin.platform.platformStatic import kotlin.platform.platformStatic
val DAEMON_STARTUP_TIMEOUT_MS = 10000L fun Process.isAlive() =
val DAEMON_STARTUP_CHECK_INTERVAL_MS = 100L try {
this.exitValue()
false
}
catch (e: IllegalThreadStateException) {
true
}
public class KotlinCompilerClient { public class KotlinCompilerClient {
companion object { 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? { private fun connectToService(compilerId: CompilerId, daemonOptions: DaemonOptions, errStream: PrintStream): CompileService? {
val compilerObj = connectToDaemon(compilerId, daemonOptions, errStream) ?: return null // no registry - no daemon running 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) { private fun startDaemon(compilerId: CompilerId, daemonOptions: DaemonOptions, errStream: PrintStream) {
val javaExecutable = listOf(System.getProperty("java.home"), "bin", "java").joinToString(File.separator) 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 // 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] remoteId = " + remoteId.toString())
errStream.println("[daemon client] localId = " + localId.toString()) errStream.println("[daemon client] localId = " + localId.toString())
return (localId.compilerVersion.isEmpty() || localId.compilerVersion == remoteId.compilerVersion) && 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? { 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") throw IllegalArgumentException("Cannot find compiler jar")
else else
println("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator)) println("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator))
compilerId.updateDigest()
} }
connectToCompileService(compilerId, daemonOptions, System.out, autostart = !clientOptions.stop, checkId = !clientOptions.stop)?.let { connectToCompileService(compilerId, daemonOptions, System.out, autostart = !clientOptions.stop, checkId = !clientOptions.stop)?.let {
@@ -220,12 +222,12 @@ public class KotlinCompilerClient {
println("Daemon shut down successfully") println("Daemon shut down successfully")
} }
else -> { else -> {
println("Executing daemon compilation with args: " + args.joinToString(" ")) println("Executing daemon compilation with args: " + filteredArgs.joinToString(" "))
val outStrm = RemoteOutputStreamServer(System.out) val outStrm = RemoteOutputStreamServer(System.out)
try { try {
val memBefore = it.getUsedMemory() / 1024 val memBefore = it.getUsedMemory() / 1024
val startTime = System.nanoTime() 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() val endTime = System.nanoTime()
println("Compilation result code: $res") println("Compilation result code: $res")
val memAfter = it.getUsedMemory() / 1024 val memAfter = it.getUsedMemory() / 1024
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.rmi
import java.io.File import java.io.File
import java.io.Serializable import java.io.Serializable
import java.security.DigestInputStream
import java.security.MessageDigest
import kotlin.platform.platformStatic import kotlin.platform.platformStatic
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1 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<File>.getFilesClasspathDigest(): String {
val md = MessageDigest.getInstance(COMPILER_ID_DIGEST)
this.forEach { updateEntryDigest(it, md) }
return md.digest().joinToString("", transform = { "%02x".format(it) })
}
fun Iterable<String>.getClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest()
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 compilerVersion: String = "" public var compilerVersion: String = ""
// TODO: checksum // TODO: checksum
) : CmdlineParams { ) : CmdlineParams {
@@ -107,17 +145,25 @@ public data class CompilerId(
override val asParams: Iterable<String> override val asParams: Iterable<String>
get() = get() =
propToParams(::compilerClasspath, { it.joinToString(File.pathSeparator) }) + propToParams(::compilerClasspath, { it.joinToString(File.pathSeparator) }) +
propToParams(::compilerDigest) +
propToParams(::compilerVersion) propToParams(::compilerVersion)
override val parsers: List<PropParser<*,*,*>> override val parsers: List<PropParser<*,*,*>>
get() = get() =
listOf( PropParser(this, ::compilerClasspath, { it.trim('"').split(File.pathSeparator)}), listOf( PropParser(this, ::compilerClasspath, { it.trim('"').split(File.pathSeparator)}),
PropParser(this, ::compilerDigest, { it.trim('"') }),
PropParser(this, ::compilerVersion, { it.trim('"') })) PropParser(this, ::compilerVersion, { it.trim('"') }))
public fun updateDigest() {
compilerDigest = compilerClasspath.getClasspathDigest()
}
companion object { 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<File>): CompilerId =
// TODO consider reading version and calculating checksum here // TODO consider reading version and calculating checksum here
CompilerId(compilerClasspath = listOf(libPath.absolutePath)) CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest())
} }
} }
@@ -48,8 +48,9 @@ public class CompileDaemon {
// setDaemonPpermissions(daemonOptions.port) // setDaemonPpermissions(daemonOptions.port)
val registry = LocateRegistry.createRegistry(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()) if (daemonOptions.startEcho.isNotEmpty())
println(daemonOptions.startEcho) println(daemonOptions.startEcho)
@@ -17,14 +17,12 @@
package org.jetbrains.kotlin.service package org.jetbrains.kotlin.service
import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache 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.rmi.COMPILER_SERVICE_RMI_NAME import org.jetbrains.kotlin.rmi.*
import org.jetbrains.kotlin.rmi.CompileService
import org.jetbrains.kotlin.rmi.CompilerId
import org.jetbrains.kotlin.rmi.RemoteOutputStream
import org.jetbrains.kotlin.rmi.service.RemoteIncrementalCacheClient import org.jetbrains.kotlin.rmi.service.RemoteIncrementalCacheClient
import org.jetbrains.kotlin.rmi.service.RemoteOutputStreamClient import org.jetbrains.kotlin.rmi.service.RemoteOutputStreamClient
import java.io.File import java.io.File
@@ -43,22 +41,28 @@ import kotlin.concurrent.read
import kotlin.concurrent.write import kotlin.concurrent.write
class CompileServiceImpl<Compiler: CLICompiler<*>>(val registry: Registry, val compiler: Compiler) : CompileService, UnicastRemoteObject() { class CompileServiceImpl<Compiler: CLICompiler<*>>(
val registry: Registry,
val compiler: Compiler,
val selfCompilerId: CompilerId,
val daemonOptions: DaemonOptions
) : CompileService, UnicastRemoteObject() {
private val rwlock = ReentrantReadWriteLock() private val rwlock = ReentrantReadWriteLock()
private var alive = false 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 // TODO: consider matching compilerId coming from outside with actual one
CompilerId( // private val selfCompilerId by lazy {
compilerClasspath = System.getProperty("java.class.path") // CompilerId(
?.split(File.pathSeparator) // compilerClasspath = System.getProperty("java.class.path")
?.map { File(it) } // ?.split(File.pathSeparator)
?.filter { it.exists() } // ?.map { File(it) }
?.map { it.absolutePath } // ?.filter { it.exists() }
?: listOf(), // ?.map { it.absolutePath }
compilerVersion = loadKotlinVersionFromResource() // ?: listOf(),
) // compilerVersion = loadKotlinVersionFromResource()
} // )
// }
init { init {
// assuming logically synchronized // assuming logically synchronized
@@ -132,10 +132,12 @@ public class KotlinCompilerRunner {
// trying the daemon first // trying the daemon first
if (incrementalCaches != null && KotlinCompilerClient.Companion.isDaemonEnabled()) { if (incrementalCaches != null && KotlinCompilerClient.Companion.isDaemonEnabled()) {
File libPath = CompilerRunnerUtil.getLibPath(environment.getKotlinPaths(), messageCollector); 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")); CompilerId compilerId = CompilerId.makeCompilerId(new File(libPath, "kotlin-compiler.jar"));
DaemonOptions daemonOptions = new DaemonOptions(); DaemonOptions daemonOptions = new DaemonOptions();
// TODO: find proper stream to report daemon connection progress // 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) { if (daemon != null) {
Integer res = KotlinCompilerClient.Companion.incrementalCompile(daemon, argsArray, incrementalCaches, out); Integer res = KotlinCompilerClient.Companion.incrementalCompile(daemon, argsArray, incrementalCaches, out);
return res.toString(); return res.toString();