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.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
@@ -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<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 var compilerClasspath: List<String> = listOf(),
public var compilerDigest: String = "",
public var compilerVersion: String = ""
// TODO: checksum
) : CmdlineParams {
@@ -107,17 +145,25 @@ public data class CompilerId(
override val asParams: Iterable<String>
get() =
propToParams(::compilerClasspath, { it.joinToString(File.pathSeparator) }) +
propToParams(::compilerDigest) +
propToParams(::compilerVersion)
override val parsers: List<PropParser<*,*,*>>
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<File>): CompilerId =
// 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)
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)
@@ -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<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 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
@@ -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();