All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.rmi.DaemonParams.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.kotlin.rmi

import java.io.File
import java.io.Serializable
import java.lang.management.ManagementFactory
import java.security.DigestInputStream
import java.security.MessageDigest
import kotlin.reflect.KMutableProperty1


public val COMPILER_JAR_NAME: String = "kotlin-compiler.jar"
public val COMPILER_SERVICE_RMI_NAME: String = "KotlinJvmCompilerService"
public val COMPILER_DAEMON_CLASS_FQN: String = "org.jetbrains.kotlin.rmi.service.CompileDaemon"
public val COMPILE_DAEMON_FIND_PORT_ATTEMPTS: Int = 10
public val COMPILE_DAEMON_PORTS_RANGE_START: Int = 17001
public val COMPILE_DAEMON_PORTS_RANGE_END: Int = 18000
public val COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_MS: Long = 10000L
public val COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS: Long = 100L
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_OPTIONS_PROPERTY: String = "kotlin.daemon.options"
public val COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY: String = "kotlin.daemon.client.alive.path"
public val COMPILE_DAEMON_LOG_PATH_PROPERTY: String = "kotlin.daemon.log.path"
public val COMPILE_DAEMON_REPORT_PERF_PROPERTY: String = "kotlin.daemon.perf"
public val COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY: String = "kotlin.daemon.verbose"
public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String = "--daemon-"
public val COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY: String = "kotlin.daemon.startup.timeout"
public val COMPILE_DAEMON_DEFAULT_FILES_PREFIX: String = "kotlin-daemon"
public val COMPILE_DAEMON_DATA_DIRECTORY_NAME: String = "." + COMPILE_DAEMON_DEFAULT_FILES_PREFIX
public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
public val COMPILE_DAEMON_DEFAULT_IDLE_TIMEOUT_S: Int = 7200 // 2 hours
public val COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE: Long = 0L
public val COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS: Long = 10000L // 10 secs
public val COMPILE_DAEMON_FORCE_SHUTDOWN_TIMEOUT_INFINITE: Long = 0L

public val COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH: String get() =
    // TODO consider special case for windows - local appdata
    File(System.getProperty("user.home"), COMPILE_DAEMON_DATA_DIRECTORY_NAME).absolutePath

val COMPILER_ID_DIGEST = "MD5"


public fun makeRunFilenameString(timestamp: String, digest: String, port: String, escapeSequence: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$escapeSequence.$timestamp$escapeSequence.$digest$escapeSequence.$port$escapeSequence.run"


open class PropMapper>(val dest: C,
                                                         val prop: P,
                                                         val names: List = listOf(prop.name),
                                                         val fromString: (String) -> V,
                                                         val toString: ((V) -> String?) = { it.toString() },
                                                         val skipIf: ((V) -> Boolean) = { false },
                                                         val mergeDelimiter: String? = null) {
    open fun toArgs(prefix: String = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX): List =
            when {
                skipIf(prop.get(dest)) -> listOf()
                mergeDelimiter != null -> listOf(listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull().joinToString(mergeDelimiter))
                else -> listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull()
            }

    open fun apply(s: String) = prop.set(dest, fromString(s))
}


class NullablePropMapper>(dest: C,
                                                                   prop: P,
                                                                   names: List = listOf(),
                                                                   fromString: ((String) -> V),
                                                                   toString: ((V) -> String?) = { it.toString() },
                                                                   skipIf: ((V) -> Boolean) = { it == null },
                                                                   mergeDelimiter: String? = null)
: PropMapper(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
                      fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)


class StringPropMapper>(dest: C,
                                                            prop: P,
                                                            names: List = listOf(),
                                                            fromString: ((String) -> String) = { it },
                                                            toString: ((String) -> String?) = { it.toString() },
                                                            skipIf: ((String) -> Boolean) = { it.isEmpty() },
                                                            mergeDelimiter: String? = null)
: PropMapper(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
                           fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)


class BoolPropMapper>(dest: C, prop: P, names: List = listOf())
: PropMapper(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
                            fromString = { true }, toString = { null }, skipIf = { !prop.get(dest) })


class RestPropMapper>>(dest: C, prop: P)
: PropMapper, P>(dest = dest, prop = prop, toString = { null }, fromString = { arrayListOf() }) {
    override fun toArgs(prefix: String): List = prop.get(dest).map { prefix + it }
    override fun apply(s: String) = add(s)
    fun add(s: String) {
        prop.get(dest).add(s)
    }
}


// helper function combining find with map, useful for the cases then there is a calculation performed in find, which is nice to return along with
// found value; mappingPredicate should return the pair of boolean compare predicate result and transformation value, we want to get along with found value
inline fun  Iterable.findWithTransform(mappingPredicate: (T) -> Pair): R? {
    for (element in this) {
        val (found, mapped) = mappingPredicate(element)
        if (found) return mapped
    }
    return null
}


// filter-like function, takes list of propmappers, bound to properties of concrete objects, iterates over receiver, extract matching values via appropriate
// mappers into bound properties; if restParser is given, adds all non-matching elements to it, otherwise return them as an iterable
// note bound properties mutation!
fun Iterable.filterExtractProps(propMappers: List>, prefix: String, restParser: RestPropMapper<*, *>? = null): Iterable {

    val iter = iterator()
    val rest = arrayListOf()

    while (iter.hasNext()) {
        val param = iter.next()
        val (propMapper, matchingOption) = propMappers.findWithTransform { mapper ->
            mapper.names
                    .firstOrNull { param.startsWith(prefix + it) }
                    .let { Pair(it != null, Pair(mapper, it)) }
        } ?: Pair(null, null)

        when {
            propMapper != null -> {
                val optionLength = prefix.length() + matchingOption!!.length()
                when {
                    propMapper is BoolPropMapper<*, *> -> {
                        if (param.length() > optionLength)
                            throw IllegalArgumentException("Invalid switch option '$param', expecting $prefix$matchingOption without arguments")
                        propMapper.apply("")
                    }
                    param.length() > optionLength ->
                        if (param[optionLength] != '=') {
                            if (propMapper.mergeDelimiter == null)
                                throw IllegalArgumentException("Invalid option syntax '$param', expecting $prefix$matchingOption[= ]")
                            propMapper.apply(param.substring(optionLength))
                        }
                        else {
                            propMapper.apply(param.substring(optionLength + 1))
                        }
                    else -> {
                        if (!iter.hasNext()) throw IllegalArgumentException("Expecting argument for the option $prefix$matchingOption")
                        propMapper.apply(iter.next())
                    }
                }
            }
            restParser != null && param.startsWith(prefix) ->
                restParser.add(param.removePrefix(prefix))
            else -> rest.add(param)
        }
    }
    return rest
}


public fun String.trimQuotes() = trim('"','\'')


public interface OptionsGroup : Serializable {
    public val mappers: List>
}

public fun Iterable.filterExtractProps(vararg groups: OptionsGroup, prefix: String): Iterable =
        filterExtractProps(groups.flatMap { it.mappers }, prefix)


public data class DaemonJVMOptions(
        public var maxMemory: String = "",
        public var maxPermSize: String = "",
        public var reservedCodeCacheSize: String = "",
        public var jvmParams: MutableCollection = arrayListOf()
) : OptionsGroup {

    override val mappers: List>
        get() = listOf(StringPropMapper(this, DaemonJVMOptions::maxMemory, listOf("Xmx"), mergeDelimiter = ""),
                       StringPropMapper(this, DaemonJVMOptions::maxPermSize, listOf("XX:MaxPermSize"), mergeDelimiter = "="),
                       StringPropMapper(this, DaemonJVMOptions::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="),
                       restMapper)

    val restMapper: RestPropMapper<*, *>
        get() = RestPropMapper(this, DaemonJVMOptions::jvmParams)
}


public data class DaemonOptions(
        public var runFilesPath: String = COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH,
        public var autoshutdownMemoryThreshold: Long = COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE,
        public var autoshutdownIdleSeconds: Int = COMPILE_DAEMON_DEFAULT_IDLE_TIMEOUT_S,
        public var forceShutdownTimeoutMilliseconds: Long = COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS,
        public var clientAliveFlagPath: String? = null,
        public var verbose: Boolean = false,
        public var reportPerf: Boolean = false
) : OptionsGroup {

    override val mappers: List>
        get() = listOf(PropMapper(this, DaemonOptions::runFilesPath, fromString = { it.trimQuotes() }),
                       PropMapper(this, DaemonOptions::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }, mergeDelimiter = "="),
                       PropMapper(this, DaemonOptions::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }, mergeDelimiter = "="),
                       PropMapper(this, DaemonOptions::forceShutdownTimeoutMilliseconds, fromString = { it.toLong() }, skipIf = { it == COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS }, mergeDelimiter = "="),
                       NullablePropMapper(this, DaemonOptions::clientAliveFlagPath, fromString = { it }, toString = { "${it?.trimQuotes()}" }, mergeDelimiter = "="),
                       BoolPropMapper(this, DaemonOptions::verbose),
                       BoolPropMapper(this, DaemonOptions::reportPerf))
}


fun updateSingleFileDigest(file: File, md: MessageDigest) {
    DigestInputStream(file.inputStream(), md).use {
        val buf = ByteArray(1024)
        while (it.read(buf) != -1) {}
        it.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.extension.equals("class", ignoreCase = true) ||
         entry.extension.equals("jar", ignoreCase = true))
            -> updateSingleFileDigest(entry, md)
    // else skip
    }
}

@JvmName("getFilesClasspathDigest_Files")
fun Iterable.getFilesClasspathDigest(): String {
    val md = MessageDigest.getInstance(COMPILER_ID_DIGEST)
    this.forEach { updateEntryDigest(it, md) }
    return md.digest().joinToString("", transform = { "%02x".format(it) })
}

@JvmName("getFilesClasspathDigest_Strings")
fun Iterable.getFilesClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest()

fun Iterable.distinctStringsDigest(): String =
        MessageDigest.getInstance(COMPILER_ID_DIGEST)
                .digest(this.distinct().sorted().joinToString("").toByteArray())
                .joinToString("", transform = { "%02x".format(it) })


public data class CompilerId(
        public var compilerClasspath: List = listOf(),
        public var compilerDigest: String = "",
        public var compilerVersion: String = ""
) : OptionsGroup {

    override val mappers: List>
        get() = listOf(PropMapper(this, CompilerId::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trimQuotes().split(File.pathSeparator) }),
                       StringPropMapper(this, CompilerId::compilerDigest),
                       StringPropMapper(this, CompilerId::compilerVersion))

    public fun updateDigest() {
        compilerDigest = compilerClasspath.getFilesClasspathDigest()
    }

    companion object {
        @JvmStatic
        public fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable())

        @JvmStatic
        public fun makeCompilerId(paths: Iterable): CompilerId =
                CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest())
    }
}


public fun isDaemonEnabled(): Boolean = System.getProperty(COMPILE_DAEMON_ENABLED_PROPERTY) != null


public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits: Boolean, vararg additionalParams: String): DaemonJVMOptions {
    // note: sequence matters, explicit override in COMPILE_DAEMON_JVM_OPTIONS_PROPERTY should be done after inputArguments processing
    if (inheritMemoryLimits) {
        ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-")
    }
    System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let {
        opts.jvmParams.addAll(
                it.trimQuotes()
                  .split("(?




© 2015 - 2024 Weber Informatics LLC | Privacy Policy