Refactoring after review

This commit is contained in:
Ilya Chernikov
2015-08-28 14:18:39 +02:00
parent 2d45a37884
commit 9bee97e810
7 changed files with 196 additions and 164 deletions
+4
View File
@@ -117,6 +117,8 @@
<include name="backend-common/**"/> <include name="backend-common/**"/>
<include name="cli/**"/> <include name="cli/**"/>
<include name="cli-common/**"/> <include name="cli-common/**"/>
<include name="rmi-server/**"/>
<include name="rmi-interface/**"/>
<include name="util/**"/> <include name="util/**"/>
<include name="util.runtime/**"/> <include name="util.runtime/**"/>
<include name="light-classes/**"/> <include name="light-classes/**"/>
@@ -200,6 +202,8 @@
<fileset dir="compiler/builtins-serializer/src"/> <fileset dir="compiler/builtins-serializer/src"/>
<fileset dir="compiler/cli/src"/> <fileset dir="compiler/cli/src"/>
<fileset dir="compiler/cli/cli-common/src"/> <fileset dir="compiler/cli/cli-common/src"/>
<include name="compiler/rmi/rmi-server/src"/>
<include name="compiler/rmi/rmi-interface/src"/>
<fileset dir="compiler/container/src"/> <fileset dir="compiler/container/src"/>
<fileset dir="compiler/frontend/src"/> <fileset dir="compiler/frontend/src"/>
<fileset dir="compiler/frontend.java/src"/> <fileset dir="compiler/frontend.java/src"/>
@@ -26,12 +26,7 @@ import java.rmi.Remote
import java.rmi.registry.LocateRegistry import java.rmi.registry.LocateRegistry
import java.util.concurrent.Semaphore import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.concurrent.write
import kotlin.platform.platformStatic
import kotlin.reflect.KProperty1
fun Process.isAlive() = fun Process.isAlive() =
try { try {
@@ -71,7 +66,7 @@ public class KotlinCompilerClient {
} }
private fun startDaemon(compilerId: CompilerId, daemonLaunchingOptions: DaemonLaunchingOptions, daemonOptions: DaemonOptions, errStream: PrintStream) { private fun startDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, errStream: PrintStream) {
val javaExecutable = File(System.getProperty("java.home"), "bin").let { val javaExecutable = File(System.getProperty("java.home"), "bin").let {
val javaw = File(it, "javaw.exe") val javaw = File(it, "javaw.exe")
if (javaw.exists()) javaw if (javaw.exists()) javaw
@@ -80,10 +75,10 @@ public class KotlinCompilerClient {
// 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
val args = listOf(javaExecutable.absolutePath, val args = listOf(javaExecutable.absolutePath,
"-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) + "-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) +
daemonLaunchingOptions.extractors.flatMap { it.extract("-") } + daemonJVMOptions.mappers.flatMap { it.toArgs("-") } +
COMPILER_DAEMON_CLASS_FQN + COMPILER_DAEMON_CLASS_FQN +
daemonOptions.extractors.flatMap { it.extract(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } + daemonOptions.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } +
compilerId.extractors.flatMap { it.extract(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } compilerId.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) }
errStream.println("[daemon client] starting the daemon as: " + args.joinToString(" ")) errStream.println("[daemon client] starting the daemon as: " + args.joinToString(" "))
val processBuilder = ProcessBuilder(args).redirectErrorStream(true) val processBuilder = ProcessBuilder(args).redirectErrorStream(true)
// assuming daemon process is deaf and (mostly) silent, so do not handle streams // assuming daemon process is deaf and (mostly) silent, so do not handle streams
@@ -135,14 +130,14 @@ public class KotlinCompilerClient {
(localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest) (localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest)
} }
public fun connectToCompileService(compilerId: CompilerId, daemonLaunchingOptions: DaemonLaunchingOptions, daemonOptions: DaemonOptions, errStream: PrintStream, autostart: Boolean = true, checkId: Boolean = true): CompileService? { public fun connectToCompileService(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, errStream: PrintStream, autostart: Boolean = true, checkId: Boolean = true): CompileService? {
val service = connectToService(compilerId, daemonOptions, errStream) val service = connectToService(compilerId, daemonOptions, errStream)
if (service != null) { if (service != null) {
if (!checkId || checkCompilerId(service, compilerId, errStream)) { if (!checkId || checkCompilerId(service, compilerId, errStream)) {
errStream.println("[daemon client] found the suitable daemon") errStream.println("[daemon client] found the suitable daemon")
return service return service
} }
errStream.println("[daemon client] compiler identity don't match: " + compilerId.extractors.flatMap { it.extract("") }.joinToString(" ")) errStream.println("[daemon client] compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" "))
if (!autostart) return null; if (!autostart) return null;
errStream.println("[daemon client] shutdown the daemon") errStream.println("[daemon client] shutdown the daemon")
service.shutdown() service.shutdown()
@@ -155,13 +150,13 @@ public class KotlinCompilerClient {
else errStream.println("[daemon client] cannot connect to Compile Daemon, trying to start") else errStream.println("[daemon client] cannot connect to Compile Daemon, trying to start")
} }
startDaemon(compilerId, daemonLaunchingOptions, daemonOptions, errStream) startDaemon(compilerId, daemonJVMOptions, daemonOptions, errStream)
errStream.println("[daemon client] daemon started, trying to connect") errStream.println("[daemon client] daemon started, trying to connect")
return connectToService(compilerId, daemonOptions, errStream) return connectToService(compilerId, daemonOptions, errStream)
} }
public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit { public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit {
KotlinCompilerClient.connectToCompileService(CompilerId(), DaemonLaunchingOptions(), daemonOptions, System.out, autostart = false, checkId = false) KotlinCompilerClient.connectToCompileService(CompilerId(), DaemonJVMOptions(), daemonOptions, System.out, autostart = false, checkId = false)
?.shutdown() ?.shutdown()
} }
@@ -196,20 +191,17 @@ public class KotlinCompilerClient {
data class ClientOptions( data class ClientOptions(
public var stop: Boolean = false public var stop: Boolean = false
) :CmdlineParams { ) : OptionsGroup {
override val extractors: List<PropExtractor<*, *, *>> override val mappers: List<PropMapper<*, *, *>>
get() = listOf( BoolPropExtractor(this, ::stop)) get() = listOf( BoolPropMapper(this, ::stop))
override val parsers: List<PropParser<*,*,*>>
get() = listOf( BoolPropParser(this, ::stop))
} }
platformStatic public fun main(vararg args: String) { jvmStatic public fun main(vararg args: String) {
val compilerId = CompilerId() val compilerId = CompilerId()
val daemonOptions = DaemonOptions() val daemonOptions = DaemonOptions()
val daemonLaunchingOptions = DaemonLaunchingOptions() val daemonLaunchingOptions = DaemonJVMOptions()
val clientOptions = ClientOptions() val clientOptions = ClientOptions()
val filteredArgs = args.asIterable().filterSetProps(compilerId, daemonOptions, daemonLaunchingOptions, clientOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, daemonLaunchingOptions, clientOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX)
if (!clientOptions.stop) { if (!clientOptions.stop) {
if (compilerId.compilerClasspath.none()) { if (compilerId.compilerClasspath.none()) {
@@ -21,9 +21,7 @@ import java.io.Serializable
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.security.DigestInputStream import java.security.DigestInputStream
import java.security.MessageDigest import java.security.MessageDigest
import kotlin.platform.platformStatic
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
public val COMPILER_JAR_NAME: String = "kotlin-compiler.jar" public val COMPILER_JAR_NAME: String = "kotlin-compiler.jar"
@@ -34,85 +32,147 @@ public val COMPILE_DAEMON_ENABLED_PROPERTY: String ="kotlin.daemon.enabled"
public val COMPILE_DAEMON_JVM_OPTIONS_PROPERTY: String ="kotlin.daemon.jvm.options" public val COMPILE_DAEMON_JVM_OPTIONS_PROPERTY: String ="kotlin.daemon.jvm.options"
public val COMPILE_DAEMON_OPTIONS_PROPERTY: String ="kotlin.daemon.options" public val COMPILE_DAEMON_OPTIONS_PROPERTY: String ="kotlin.daemon.options"
public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String ="--daemon-" public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String ="--daemon-"
public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
public val COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE: Long = 0L
val COMPILER_ID_DIGEST = "MD5"
open class PropExtractor<C, V, P: KProperty1<C, V>>(val dest: C, //open class PropExtractor<C, V, P: KProperty1<C, V>>(val dest: C,
val prop: P, // val prop: P,
val name: String, // val name: String,
val convert: ((v: V) -> String?) = { it.toString() }, // val convert: ((v: V) -> String?) = { it.toString() },
val skipIf: ((v: V) -> Boolean) = { false }, // val skipIf: ((v: V) -> Boolean) = { false },
val mergeWithDelimiter: String? = null) // val mergeWithDelimiter: String? = null)
//{
// constructor(dest: C, prop: P, convert: ((v: V) -> String?) = { it.toString() }, skipIf: ((v: V) -> Boolean) = { false }) : this(dest, prop, prop.name, convert, skipIf)
// open fun extract(prefix: String = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX): List<String> =
// when {
// skipIf(prop.get(dest)) -> listOf<String>()
// mergeWithDelimiter != null -> listOf(prefix + name + mergeWithDelimiter + convert(prop.get(dest))).filterNotNull()
// else -> listOf(prefix + name, convert(prop.get(dest))).filterNotNull()
// }
//}
//
//class BoolPropExtractor<C, P: KMutableProperty1<C, Boolean>>(dest: C, prop: P, name: String? = null)
// : PropExtractor<C, Boolean, P>(dest, prop, name ?: prop.name, convert = { null }, skipIf = { !prop.get(dest) })
//
//class RestPropExtractor<C, P: KMutableProperty1<C, out MutableCollection<String>>>(dest: C, prop: P) : PropExtractor<C, MutableCollection<String>, P>(dest, prop, convert = { null }) {
// override fun extract(prefix: String): List<String> = prop.get(dest).map { prefix + it }
//}
//
//
//open class PropParser<C, V, P: KMutableProperty1<C, V>>(val dest: C,
// val prop: P, alternativeNames: List<String>,
// val parse: (s: String) -> V,
// val allowMergedArg: Boolean = false) {
// val names = listOf(prop.name) + alternativeNames
// constructor(dest: C, prop: P, parse: (s: String) -> V, allowMergedArg: Boolean = false) : this(dest, prop, listOf(), parse, allowMergedArg)
// fun apply(s: String) = prop.set(dest, parse(s))
//}
//
//class BoolPropParser<C, P: KMutableProperty1<C, Boolean>>(dest: C, prop: P): PropParser<C, Boolean, P>(dest, prop, { true })
//
//class RestPropParser<C, P: KMutableProperty1<C, MutableCollection<String>>>(dest: C, prop: P): PropParser<C, MutableCollection<String>, P>(dest, prop, { arrayListOf() }) {
// fun add(s: String) { prop.get(dest).add(s) }
//}
// --------------------------------------------------------
open class PropMapper<C, V, P: KMutableProperty1<C, V>>(val dest: C,
val prop: P,
val names: List<String> = listOf(prop.name),
val fromString: (s: String) -> V,
val toString: ((v: V) -> String?) = { it.toString() },
val skipIf: ((v: V) -> Boolean) = { false },
val mergeDelimiter: String? = null)
{ {
constructor(dest: C, prop: P, convert: ((v: V) -> String?) = { it.toString() }, skipIf: ((v: V) -> Boolean) = { false }) : this(dest, prop, prop.name, convert, skipIf) open fun toArgs(prefix: String = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX): List<String> =
open fun extract(prefix: String = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX): List<String> =
when { when {
skipIf(prop.get(dest)) -> listOf<String>() skipIf(prop.get(dest)) -> listOf<String>()
mergeWithDelimiter != null -> listOf(prefix + name + mergeWithDelimiter + convert(prop.get(dest))).filterNotNull() mergeDelimiter != null -> listOf(prefix + names.first() + mergeDelimiter + toString(prop.get(dest))).filterNotNull()
else -> listOf(prefix + name, convert(prop.get(dest))).filterNotNull() else -> listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull()
} }
fun apply(s: String) = prop.set(dest, fromString(s))
} }
class BoolPropExtractor<C, P: KMutableProperty1<C, Boolean>>(dest: C, prop: P, name: String? = null) class StringPropMapper<C, P: KMutableProperty1<C, String>>(dest: C,
: PropExtractor<C, Boolean, P>(dest, prop, name ?: prop.name, convert = { null }, skipIf = { !prop.get(dest) }) prop: P,
names: List<String> = listOf(),
fromString: ((String) -> String) = { it },
toString: ((String) -> String?) = { it.toString() },
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),
fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)
class RestPropExtractor<C, P: KMutableProperty1<C, out MutableCollection<String>>>(dest: C, prop: P) : PropExtractor<C, MutableCollection<String>, P>(dest, prop, convert = { null }) { class BoolPropMapper<C, P: KMutableProperty1<C, Boolean>>(dest: C, prop: P, names: List<String> = listOf())
override fun extract(prefix: String): List<String> = prop.get(dest).map { prefix + it } : 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) })
class RestPropMapper<C, P: KMutableProperty1<C, MutableCollection<String>>>(dest: C, prop: P)
open class PropParser<C, V, P: KMutableProperty1<C, V>>(val dest: C, : PropMapper<C, MutableCollection<String>, P>(dest = dest, prop = prop, toString = { null }, fromString = { arrayListOf() })
val prop: P, alternativeNames: List<String>, {
val parse: (s: String) -> V, override fun toArgs(prefix: String): List<String> = prop.get(dest).map { prefix + it }
val allowMergedArg: Boolean = false) {
val names = listOf(prop.name) + alternativeNames
constructor(dest: C, prop: P, parse: (s: String) -> V, allowMergedArg: Boolean = false) : this(dest, prop, listOf(), parse, allowMergedArg)
fun apply(s: String) = prop.set(dest, parse(s))
}
class BoolPropParser<C, P: KMutableProperty1<C, Boolean>>(dest: C, prop: P): PropParser<C, Boolean, P>(dest, prop, { true })
class RestPropParser<C, P: KMutableProperty1<C, MutableCollection<String>>>(dest: C, prop: P): PropParser<C, MutableCollection<String>, P>(dest, prop, { arrayListOf() }) {
fun add(s: String) { prop.get(dest).add(s) } fun add(s: String) { prop.get(dest).add(s) }
} }
// ------------------------------------------
fun Iterable<String>.filterSetProps(parsers: List<PropParser<*,*,*>>, prefix: String, restParser: RestPropParser<*,*>? = null) : Iterable<String> { fun Iterable<String>.filterExtractProps(propMappers: List<PropMapper<*,*,*>>, prefix: String, restParser: RestPropMapper<*,*>? = null) : Iterable<String> {
var currentParser: PropParser<*,*,*>? = null var currentPropMapper: PropMapper<*,*,*>? = null
var matchingOption = "" var matchingOption = ""
val res = filter { param -> val res = filter { param ->
if (currentParser == null) { if (currentPropMapper == null) {
val parser = parsers.find { it.names.any { name -> val propMapper = propMappers.find {
if (param.startsWith(prefix + name)) { matchingOption = prefix + name; true } it !is RestPropMapper<*,*> &&
else false } } it.names.any { name ->
if (parser != null) { if (param.startsWith(prefix + name)) {
val optionLength = matchingOption.length() matchingOption = prefix + name
when { true
parser is BoolPropParser<*,*> -> }
if (param.length() > optionLength) throw IllegalArgumentException("Invalid switch option '$param', expecting $matchingOption without arguments") else {
else parser.apply("") false
param.length() > optionLength -> }
if (param[optionLength] != '=') {
if (parser.allowMergedArg) parser.apply(param.substring(optionLength))
else throw IllegalArgumentException("Invalid option syntax '$param', expecting $matchingOption[= ]<arg>")
}
else parser.apply(param.substring(optionLength + 1))
else -> currentParser = parser
} }
false
} }
else if (restParser != null && param.startsWith(prefix)) { when {
restParser.add(param.removePrefix(prefix)) propMapper != null -> {
false val optionLength = matchingOption.length()
when {
propMapper is BoolPropMapper<*,*> -> {
if (param.length() > optionLength)
throw IllegalArgumentException("Invalid switch option '$param', expecting $matchingOption without arguments")
propMapper.apply("")
}
param.length() > optionLength ->
if (param[optionLength] != '=') {
if (propMapper.mergeDelimiter == null)
throw IllegalArgumentException("Invalid option syntax '$param', expecting $matchingOption[= ]<arg>")
propMapper.apply(param.substring(optionLength))
}
else {
propMapper.apply(param.substring(optionLength + 1))
}
else ->
currentPropMapper = propMapper
}
false
}
restParser != null && param.startsWith(prefix) -> {
restParser.add(param.removePrefix(prefix))
false
}
else -> true
} }
else true
} }
else { else {
currentParser!!.apply(param) currentPropMapper!!.apply(param)
currentParser = null currentPropMapper = null
false false
} }
} }
if (currentParser != null) throw IllegalArgumentException("Expecting argument for the option $matchingOption") if (currentPropMapper != null)
throw IllegalArgumentException("Expecting argument for the option $matchingOption")
return res return res
} }
@@ -124,59 +184,46 @@ fun Iterable<String>.filterSetProps(parsers: List<PropParser<*,*,*>>, prefix: St
public interface CmdlineParams : Serializable { public interface OptionsGroup : Serializable {
public val extractors: List<PropExtractor<*,*,*>> public val mappers: List<PropMapper<*,*,*>>
public val parsers: List<PropParser<*,*,*>>
} }
public fun Iterable<String>.filterSetProps(vararg cs: CmdlineParams, prefix: String) : Iterable<String> = public fun Iterable<String>.filterExtractProps(vararg groups: OptionsGroup, prefix: String) : Iterable<String> =
filterSetProps(cs.flatMap { it.parsers }, prefix) filterExtractProps(groups.flatMap { it.mappers }, prefix)
public data class DaemonLaunchingOptions( public data class DaemonJVMOptions(
public var maxMemory: String = "", public var maxMemory: String = "",
public var maxPermSize: String = "", public var maxPermSize: String = "",
public var reservedCodeCacheSize: String = "", public var reservedCodeCacheSize: String = "",
public var otherJvmParams: MutableCollection<String> = arrayListOf() public var otherJvmParams: MutableCollection<String> = arrayListOf()
) : CmdlineParams { ) : OptionsGroup {
override val extractors: List<PropExtractor<*,*,*>> override val mappers: List<PropMapper<*,*,*>>
get() = listOf( PropExtractor(this, ::maxMemory, "Xmx", skipIf = { it.isEmpty() }, mergeWithDelimiter = ""), get() = listOf( StringPropMapper(this, ::maxMemory, listOf("Xmx"), mergeDelimiter = ""),
PropExtractor(this, ::maxPermSize, "XX:MaxPermSize", skipIf = { it.isEmpty() }, mergeWithDelimiter = "="), StringPropMapper(this, ::maxPermSize, listOf("XX:MaxPermSize"), mergeDelimiter = "="),
PropExtractor(this, ::reservedCodeCacheSize, "XX:ReservedCodeCacheSize", skipIf = { it.isEmpty() }, mergeWithDelimiter = "="), StringPropMapper(this, ::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="),
RestPropExtractor(this, ::otherJvmParams)) restMapper)
override val parsers: List<PropParser<*,*,*>> val restMapper: RestPropMapper<*,*>
get() = listOf( PropParser(this, ::maxMemory, listOf("Xmx"), { it }, allowMergedArg = true), get() = RestPropMapper(this, ::otherJvmParams)
PropParser(this, ::maxPermSize, listOf("XX:MaxPermSize"), { it }, allowMergedArg = true),
PropParser(this, ::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), { it }, allowMergedArg = true))
// otherJvmParams is missing here deliberately, it is used explicitly as a restParser param to filterSetProps
} }
public data class DaemonOptions( public data class DaemonOptions(
public var port: Int = COMPILE_DAEMON_DEFAULT_PORT, public var port: Int = COMPILE_DAEMON_DEFAULT_PORT,
public var autoshutdownMemoryThreshold: Long = 0 /* 0 means unchecked */, public var autoshutdownMemoryThreshold: Long = COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE,
public var autoshutdownIdleSeconds: Int = 0 /* 0 means unchecked */, public var autoshutdownIdleSeconds: Int = COMPILE_DAEMON_TIMEOUT_INFINITE_S,
public var startEcho: String = COMPILER_SERVICE_RMI_NAME public var startEcho: String = COMPILER_SERVICE_RMI_NAME
) : CmdlineParams { ) : OptionsGroup {
override val extractors: List<PropExtractor<*, *, *>> override val mappers: List<PropMapper<*, *, *>>
get() = listOf( PropExtractor(this, ::port), get() = listOf( PropMapper(this, ::port, fromString = { it.toInt() }),
PropExtractor(this, ::autoshutdownMemoryThreshold, skipIf = { it == 0L }), PropMapper(this, ::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }),
PropExtractor(this, ::autoshutdownIdleSeconds, skipIf = { it == 0 }), PropMapper(this, ::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }),
PropExtractor(this, ::startEcho)) PropMapper(this, ::startEcho, fromString = { it.trim('"') }))
override val parsers: List<PropParser<*,*,*>>
get() = listOf( PropParser(this, ::port, { it.toInt()}),
PropParser(this, ::autoshutdownMemoryThreshold, { it.toLong()}),
PropParser(this, ::autoshutdownIdleSeconds, { it.toInt()}),
PropParser(this, ::startEcho, { it.trim('"') }))
} }
val COMPILER_ID_DIGEST = "MD5"
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)
@@ -215,27 +262,21 @@ public data class CompilerId(
public var compilerDigest: String = "", public var compilerDigest: String = "",
public var compilerVersion: String = "" public var compilerVersion: String = ""
// TODO: checksum // TODO: checksum
) : CmdlineParams { ) : OptionsGroup {
override val extractors: List<PropExtractor<*, *, *>> override val mappers: List<PropMapper<*, *, *>>
get() = listOf( PropExtractor(this, ::compilerClasspath, convert = { it.joinToString(File.pathSeparator) }), get() = listOf( PropMapper(this, ::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trim('"').split(File.pathSeparator)}),
PropExtractor(this, ::compilerDigest), StringPropMapper(this, ::compilerDigest),
PropExtractor(this, ::compilerVersion, skipIf = { it.isEmpty() })) StringPropMapper(this, ::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() { public fun updateDigest() {
compilerDigest = compilerClasspath.getClasspathDigest() compilerDigest = compilerClasspath.getClasspathDigest()
} }
companion object { companion object {
public platformStatic fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable()) public jvmStatic fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable())
public platformStatic fun makeCompilerId(paths: Iterable<File>): CompilerId = public jvmStatic fun makeCompilerId(paths: Iterable<File>): CompilerId =
// TODO consider reading version here // TODO consider reading version here
CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest()) CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest())
} }
@@ -245,27 +286,27 @@ public data class CompilerId(
public fun isDaemonEnabled(): Boolean = System.getProperty(COMPILE_DAEMON_ENABLED_PROPERTY) != null public fun isDaemonEnabled(): Boolean = System.getProperty(COMPILE_DAEMON_ENABLED_PROPERTY) != null
public fun configureDaemonLaunchingOptions(opts: DaemonLaunchingOptions, inheritMemoryLimits: Boolean): DaemonLaunchingOptions { public fun configureDaemonLaunchingOptions(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.filterSetProps(opts.parsers, "-") ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-")
System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let { System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let {
opts.otherJvmParams.addAll( it.trim('"', '\'').split(",").filterSetProps(opts.parsers, "-", RestPropParser(opts, DaemonLaunchingOptions::otherJvmParams))) opts.otherJvmParams.addAll( it.trim('"', '\'').split(",").filterExtractProps(opts.mappers, "-", opts.restMapper))
} }
return opts return opts
} }
public fun configureDaemonLaunchingOptions(inheritMemoryLimits: Boolean): DaemonLaunchingOptions = public fun configureDaemonLaunchingOptions(inheritMemoryLimits: Boolean): DaemonJVMOptions =
configureDaemonLaunchingOptions(DaemonLaunchingOptions(), inheritMemoryLimits = inheritMemoryLimits) configureDaemonLaunchingOptions(DaemonJVMOptions(), inheritMemoryLimits = inheritMemoryLimits)
jvmOverloads public fun configureDaemonOptions(opts: DaemonOptions = DaemonOptions()): DaemonOptions { jvmOverloads public fun configureDaemonOptions(opts: DaemonOptions = DaemonOptions()): DaemonOptions {
System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)?.let { System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)?.let {
val unrecognized = it.trim('"', '\'').split(",").filterSetProps(opts.parsers, "") val unrecognized = it.trim('"', '\'').split(",").filterExtractProps(opts.mappers, "")
if (unrecognized.any()) if (unrecognized.any())
throw IllegalArgumentException( throw IllegalArgumentException(
"Unrecognized daemon options passed via property $COMPILE_DAEMON_OPTIONS_PROPERTY: " + unrecognized.joinToString(" ") + "Unrecognized daemon options passed via property $COMPILE_DAEMON_OPTIONS_PROPERTY: " + unrecognized.joinToString(" ") +
"\nSupported options: " + opts.extractors.joinToString(", ", transform = { it.name })) "\nSupported options: " + opts.mappers.joinToString(", ", transform = { it.names.first() }))
} }
return opts return opts
} }
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.rmi.service 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.COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX import org.jetbrains.kotlin.rmi.*
import org.jetbrains.kotlin.rmi.CompilerId
import org.jetbrains.kotlin.rmi.DaemonOptions
import org.jetbrains.kotlin.rmi.filterSetProps
import org.jetbrains.kotlin.service.CompileServiceImpl import org.jetbrains.kotlin.service.CompileServiceImpl
import java.io.OutputStream import java.io.OutputStream
import java.io.PrintStream import java.io.PrintStream
@@ -96,7 +93,7 @@ public class CompileDaemon {
val compilerId = CompilerId() val compilerId = CompilerId()
val daemonOptions = DaemonOptions() val daemonOptions = DaemonOptions()
val filteredArgs = args.asIterable().filterSetProps(compilerId, daemonOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX)
if (filteredArgs.any()) { if (filteredArgs.any()) {
val helpLine = "usage: <daemon> <compilerId options> <daemon options>" val helpLine = "usage: <daemon> <compilerId options> <daemon options>"
@@ -17,7 +17,6 @@
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
@@ -25,15 +24,12 @@ import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompil
import org.jetbrains.kotlin.rmi.* import org.jetbrains.kotlin.rmi.*
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.FileNotFoundException
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.net.URLClassLoader import java.net.URLClassLoader
import java.rmi.registry.Registry import java.rmi.registry.Registry
import java.rmi.server.UnicastRemoteObject import java.rmi.server.UnicastRemoteObject
import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.jar.Manifest import java.util.jar.Manifest
@@ -86,12 +82,14 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
public class IncrementalCompilationComponentsImpl(val idToCache: Map<String, CompileService.RemoteIncrementalCache>): IncrementalCompilationComponents { public class IncrementalCompilationComponentsImpl(val idToCache: Map<String, CompileService.RemoteIncrementalCache>): IncrementalCompilationComponents {
// perf: cheap object, but still the pattern may be costly if there are too many calls to cache with the same id (which seems not to be the case now) // perf: cheap object, but still the pattern may be costly if there are too many calls to cache with the same id (which seems not to be the case now)
override fun getIncrementalCache(moduleId: String): IncrementalCache = RemoteIncrementalCacheClient(idToCache[moduleId]!!) override fun getIncrementalCache(moduleId: String): IncrementalCache = RemoteIncrementalCacheClient(idToCache[moduleId]!!)
// TODO: add appropriate proxy into interaction when lookup tracker is needed
override fun getLookupTracker(): LookupTracker = LookupTracker.DO_NOTHING override fun getLookupTracker(): LookupTracker = LookupTracker.DO_NOTHING
} }
private fun createCompileServices(incrementalCaches: Map<String, CompileService.RemoteIncrementalCache>): Services = private fun createCompileServices(incrementalCaches: Map<String, CompileService.RemoteIncrementalCache>): Services =
Services.Builder() Services.Builder()
.register(javaClass<IncrementalCompilationComponents>(), IncrementalCompilationComponentsImpl(incrementalCaches)) .register(IncrementalCompilationComponents::class.java, IncrementalCompilationComponentsImpl(incrementalCaches))
// TODO: add remote proxy for cancellation status tracking
// .register(javaClass<CompilationCanceledStatus>(), object: CompilationCanceledStatus { // .register(javaClass<CompilationCanceledStatus>(), object: CompilationCanceledStatus {
// override fun checkCanceled(): Unit = if (context.getCancelStatus().isCanceled()) throw CompilationCanceledException() // override fun checkCanceled(): Unit = if (context.getCancelStatus().isCanceled()) throw CompilationCanceledException()
// }) // })
@@ -111,6 +109,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
return memHeap.used return memHeap.used
} }
// TODO: consider using version as a part of compiler ID or drop this function
private fun loadKotlinVersionFromResource(): String { private fun loadKotlinVersionFromResource(): String {
(javaClass.classLoader as? URLClassLoader) (javaClass.classLoader as? URLClassLoader)
?.findResource("META-INF/MANIFEST.MF") ?.findResource("META-INF/MANIFEST.MF")
@@ -158,6 +157,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
else body() else body()
} }
// sometimes used for debugging
fun<R> spy(msg: String, body: () -> R): R { fun<R> spy(msg: String, body: () -> R): R {
val res = body() val res = body()
log.info(msg + " = " + res.toString()) log.info(msg + " = " + res.toString())
@@ -178,26 +178,25 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
} }
override fun remoteCompile(args: Array<out String>, errStream: RemoteOutputStream, outputFormat: CompileService.OutputFormat): Int = override fun remoteCompile(args: Array<out String>, errStream: RemoteOutputStream, outputFormat: CompileService.OutputFormat): Int =
ifAlive { doCompile(args, errStream) { printStream ->
checkedCompile(args) { when (outputFormat) {
val strm = RemoteOutputStreamClient(errStream) CompileService.OutputFormat.PLAIN -> compiler.exec(printStream, *args)
val printStrm = PrintStream(strm) CompileService.OutputFormat.XML -> compiler.execAndOutputXml(printStream, Services.EMPTY, *args)
when (outputFormat) { }.code
CompileService.OutputFormat.PLAIN -> compiler.exec(printStrm, *args)
CompileService.OutputFormat.XML -> compiler.execAndOutputXml(printStrm, Services.EMPTY, *args)
}.code
}
} }
override fun remoteIncrementalCompile(args: Array<out String>, caches: Map<String, CompileService.RemoteIncrementalCache>, errStream: RemoteOutputStream, outputFormat: CompileService.OutputFormat): Int = override fun remoteIncrementalCompile(args: Array<out String>, caches: Map<String, CompileService.RemoteIncrementalCache>, outputStream: RemoteOutputStream, outputFormat: CompileService.OutputFormat): Int =
doCompile(args, outputStream) { printStream ->
when (outputFormat) {
CompileService.OutputFormat.PLAIN -> throw NotImplementedError("Only XML output is supported in remote incremental compilation")
CompileService.OutputFormat.XML -> compiler.execAndOutputXml(printStream, createCompileServices(caches), *args)
}.code
}
fun doCompile(args: Array<out String>, errStream: RemoteOutputStream, body: (PrintStream) -> Int): Int =
ifAlive { ifAlive {
checkedCompile(args) { checkedCompile(args) {
val strm = RemoteOutputStreamClient(errStream) body( PrintStream( RemoteOutputStreamClient(errStream)))
val printStrm = PrintStream(strm)
when (outputFormat) {
CompileService.OutputFormat.PLAIN -> throw NotImplementedError("Only XML output is supported in remote incremental compilation")
CompileService.OutputFormat.XML -> compiler.execAndOutputXml(printStrm, createCompileServices(caches), *args)
}.code
} }
} }
} }
@@ -20,7 +20,7 @@ import junit.framework.TestCase
import org.jetbrains.kotlin.cli.CliBaseTest import org.jetbrains.kotlin.cli.CliBaseTest
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
import org.jetbrains.kotlin.rmi.CompilerId import org.jetbrains.kotlin.rmi.CompilerId
import org.jetbrains.kotlin.rmi.DaemonLaunchingOptions import org.jetbrains.kotlin.rmi.DaemonJVMOptions
import org.jetbrains.kotlin.rmi.DaemonOptions import org.jetbrains.kotlin.rmi.DaemonOptions
import org.jetbrains.kotlin.rmi.kotlinr.KotlinCompilerClient import org.jetbrains.kotlin.rmi.kotlinr.KotlinCompilerClient
import org.jetbrains.kotlin.test.JetTestUtils import org.jetbrains.kotlin.test.JetTestUtils
@@ -34,7 +34,7 @@ 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(port = KOTLIN_DAEMON_TEST_PORT) val daemonOptions = DaemonOptions(port = KOTLIN_DAEMON_TEST_PORT)
val daemonLaunchingOptions = DaemonLaunchingOptions() val daemonLaunchingOptions = 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")) }
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.compilerRunner; package org.jetbrains.kotlin.compilerRunner;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtil;
import com.intellij.util.Function; import com.intellij.util.Function;
@@ -136,9 +135,9 @@ public class KotlinCompilerRunner {
// the lifetime of JPS process is small anyway, we can neglect the probability of changed compiler // 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 = RmiPackage.configureDaemonOptions(); DaemonOptions daemonOptions = RmiPackage.configureDaemonOptions();
DaemonLaunchingOptions daemonLaunchingOptions = RmiPackage.configureDaemonLaunchingOptions(true); DaemonJVMOptions daemonJVMOptions = RmiPackage.configureDaemonLaunchingOptions(true);
// TODO: find proper stream to report daemon connection progress // TODO: find proper stream to report daemon connection progress
CompileService daemon = KotlinCompilerClient.Companion.connectToCompileService(compilerId, daemonLaunchingOptions, daemonOptions, System.out, true, true); CompileService daemon = KotlinCompilerClient.Companion.connectToCompileService(compilerId, daemonJVMOptions, 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();