Adding compiler id digest calculation and check to detect replaced compiler jar, minor fixes and refactorings
This commit is contained in:
@@ -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)
|
||||
|
||||
+21
-17
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user