Use connect-and-lease when using daemon in JPS & Gradle
Relates to KT-15562 "Service is dying". This commit includes multiple changes: 1. JPS & Gradle daemon clients are refactored to use `connectAndLease` from `KotlinCompilerClient`. `connectAndLease` was introduced in previous commits 2. `withKotlin` was removed because `connectAndLease` already covers retrying on connection error 3. Gradle flag files creation is changed: * client-alive flag file lives as long as Gradle instance lives, * session-alive flag file lives until the end of a build.
This commit is contained in:
+18
-38
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorUtil
|
||||
import org.jetbrains.kotlin.daemon.client.CompileServiceSession
|
||||
import org.jetbrains.kotlin.daemon.client.CompilationServices
|
||||
import org.jetbrains.kotlin.daemon.client.DaemonReportMessage
|
||||
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
|
||||
@@ -54,12 +55,16 @@ abstract class KotlinCompilerRunner<in Env : CompilerEnvironment> {
|
||||
|
||||
protected abstract val log: KotlinLogger
|
||||
|
||||
class DaemonConnection(val daemon: CompileService?, val sessionId: Int = CompileService.NO_SESSION)
|
||||
|
||||
protected abstract fun getDaemonConnection(environment: Env): DaemonConnection
|
||||
protected abstract fun getDaemonConnection(environment: Env): CompileServiceSession?
|
||||
|
||||
@Synchronized
|
||||
protected fun newDaemonConnection(compilerId: CompilerId, flagFile: File, environment: Env, daemonOptions: DaemonOptions = configureDaemonOptions()): DaemonConnection {
|
||||
protected fun newDaemonConnection(
|
||||
compilerId: CompilerId,
|
||||
clientAliveFlagFile: File,
|
||||
sessionAliveFlagFile: File,
|
||||
environment: Env,
|
||||
daemonOptions: DaemonOptions = configureDaemonOptions()
|
||||
): CompileServiceSession? {
|
||||
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = true, inheritAdditionalProperties = true)
|
||||
|
||||
val daemonReportMessages = ArrayList<DaemonReportMessage>()
|
||||
@@ -71,13 +76,19 @@ abstract class KotlinCompilerRunner<in Env : CompilerEnvironment> {
|
||||
val profiler = if (daemonOptions.reportPerf) WallAndThreadAndMemoryTotalProfiler(withGC = false) else DummyProfiler()
|
||||
|
||||
val connection = profiler.withMeasure(null) {
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, daemonReportingTargets, true, true)
|
||||
DaemonConnection(daemon, daemon?.leaseCompileSession(flagFile.absolutePath)?.get() ?: CompileService.NO_SESSION)
|
||||
KotlinCompilerClient.connectAndLease(compilerId,
|
||||
clientAliveFlagFile,
|
||||
daemonJVMOptions,
|
||||
daemonOptions,
|
||||
daemonReportingTargets,
|
||||
autostart = true,
|
||||
leaseSession = true,
|
||||
sessionAliveFlagFile = sessionAliveFlagFile)
|
||||
}
|
||||
|
||||
for (msg in daemonReportMessages) {
|
||||
environment.messageCollector.report(CompilerMessageSeverity.INFO,
|
||||
(if (msg.category == DaemonReportCategory.EXCEPTION && connection.daemon == null) "Falling back to compilation without daemon due to error: " else "") + msg.message,
|
||||
(if (msg.category == DaemonReportCategory.EXCEPTION && connection == null) "Falling back to compilation without daemon due to error: " else "") + msg.message,
|
||||
CompilerMessageLocation.NO_LOCATION)
|
||||
}
|
||||
|
||||
@@ -141,37 +152,6 @@ abstract class KotlinCompilerRunner<in Env : CompilerEnvironment> {
|
||||
environment: Env
|
||||
): ExitCode?
|
||||
|
||||
protected fun <T> withDaemon(environment: Env, retryOnConnectionError: Boolean, fn: (CompileService, sessionId: Int)->T): T? {
|
||||
fun retryOrFalse(e: Exception): T? {
|
||||
if (retryOnConnectionError) {
|
||||
log.debug("retrying once on daemon connection error: ${e.message}")
|
||||
return withDaemon(environment, retryOnConnectionError = false, fn = fn)
|
||||
}
|
||||
log.info("daemon connection error: ${e.message}")
|
||||
return null
|
||||
}
|
||||
|
||||
log.debug("Try to connect to daemon")
|
||||
val connection = getDaemonConnection(environment)
|
||||
|
||||
if (connection.daemon != null) {
|
||||
log.info("Connected to daemon")
|
||||
|
||||
try {
|
||||
return fn(connection.daemon, connection.sessionId)
|
||||
}
|
||||
catch (e: java.rmi.ConnectException) {
|
||||
return retryOrFalse(e)
|
||||
}
|
||||
catch (e: java.rmi.UnmarshalException) {
|
||||
return retryOrFalse(e)
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Daemon not found")
|
||||
return null
|
||||
}
|
||||
|
||||
protected fun exitCodeFromProcessExitCode(code: Int): ExitCode {
|
||||
val exitCode = ExitCode.values().find { it.code == code }
|
||||
if (exitCode != null) return exitCode
|
||||
|
||||
+14
-11
@@ -42,6 +42,7 @@ class CompilationServices(
|
||||
val compilationCanceledStatus: CompilationCanceledStatus? = null
|
||||
)
|
||||
|
||||
data class CompileServiceSession(val compileService: CompileService, val sessionId: Int)
|
||||
|
||||
object KotlinCompilerClient {
|
||||
|
||||
@@ -50,7 +51,14 @@ object KotlinCompilerClient {
|
||||
|
||||
val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null
|
||||
|
||||
data class ServiceWithSession(val service: CompileService, val sessionId: Int)
|
||||
fun getOrCreateClientFlagFile(daemonOptions: DaemonOptions): File =
|
||||
// for jps property is passed from IDEA to JPS in KotlinBuildProcessParametersProvider
|
||||
System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)
|
||||
?.let(String::trimQuotes)
|
||||
?.takeUnless(String::isBlank)
|
||||
?.let(::File)
|
||||
?.takeIf(File::exists)
|
||||
?: makeAutodeletingFlagFile(baseDir = File(daemonOptions.runFilesPathOrDefault))
|
||||
|
||||
fun connectToCompileService(compilerId: CompilerId,
|
||||
daemonJVMOptions: DaemonJVMOptions,
|
||||
@@ -59,12 +67,7 @@ object KotlinCompilerClient {
|
||||
autostart: Boolean = true,
|
||||
checkId: Boolean = true
|
||||
): CompileService? {
|
||||
val flagFile = System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)
|
||||
?.let(String::trimQuotes)
|
||||
?.takeUnless(String::isBlank)
|
||||
?.let(::File)
|
||||
?.takeIf(File::exists)
|
||||
?: makeAutodeletingFlagFile(baseDir = File(daemonOptions.runFilesPathOrDefault))
|
||||
val flagFile = getOrCreateClientFlagFile(daemonOptions)
|
||||
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart)
|
||||
}
|
||||
|
||||
@@ -82,7 +85,7 @@ object KotlinCompilerClient {
|
||||
reportingTargets,
|
||||
autostart,
|
||||
leaseSession = false,
|
||||
sessionAliveFlagFile = null)?.service
|
||||
sessionAliveFlagFile = null)?.compileService
|
||||
|
||||
|
||||
fun connectAndLease(compilerId: CompilerId,
|
||||
@@ -93,20 +96,20 @@ object KotlinCompilerClient {
|
||||
autostart: Boolean,
|
||||
leaseSession: Boolean,
|
||||
sessionAliveFlagFile: File? = null
|
||||
): ServiceWithSession? = connectLoop(reportingTargets) {
|
||||
): CompileServiceSession? = connectLoop(reportingTargets) {
|
||||
setupServerHostName()
|
||||
val (service, newJVMOptions) = tryFindSuitableDaemonOrNewOpts(File(daemonOptions.runFilesPath), compilerId, daemonJVMOptions, { cat, msg -> reportingTargets.report(cat, msg) })
|
||||
if (service != null) {
|
||||
// the newJVMOptions could be checked here for additional parameters, if needed
|
||||
service.registerClient(clientAliveFlagFile.absolutePath)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
|
||||
if (!leaseSession) ServiceWithSession(service, CompileService.NO_SESSION)
|
||||
if (!leaseSession) CompileServiceSession(service, CompileService.NO_SESSION)
|
||||
else {
|
||||
val sessionId = service.leaseCompileSession(sessionAliveFlagFile?.absolutePath)
|
||||
if (sessionId is CompileService.CallResult.Dying)
|
||||
null
|
||||
else
|
||||
ServiceWithSession(service, sessionId.get())
|
||||
CompileServiceSession(service, sessionId.get())
|
||||
}
|
||||
} else {
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found")
|
||||
|
||||
@@ -415,16 +415,14 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
configureDaemonJVMOptions(DaemonJVMOptions(maxMemory = "2048m"),
|
||||
"D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"",
|
||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
val daemonWithSession = KotlinCompilerClient.connectAndLease(compilerId, flagFile, daemonJVMOptions, daemonOptions,
|
||||
val compileServiceSession = KotlinCompilerClient.connectAndLease(compilerId, flagFile, daemonJVMOptions, daemonOptions,
|
||||
DaemonReportingTargets(out = System.err), autostart = true,
|
||||
leaseSession = true, sessionAliveFlagFile = sessionFlagFile)
|
||||
if (daemonWithSession?.service == null) {
|
||||
fail("failed to connect daemon:\n${logFile.readLines().joinToString("\n")}\n------")
|
||||
}
|
||||
assertNotNull("failed to connect daemon", compileServiceSession?.compileService)
|
||||
val jar = tmpdir.absolutePath + File.separator + "hello.$threadNo.jar"
|
||||
val res = KotlinCompilerClient.compile(
|
||||
daemonWithSession!!.service,
|
||||
daemonWithSession.sessionId,
|
||||
compileServiceSession!!.compileService,
|
||||
compileServiceSession.sessionId,
|
||||
CompileService.TargetPlatform.JVM,
|
||||
arrayOf(File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar),
|
||||
outStreams[threadNo])
|
||||
|
||||
@@ -25,6 +25,8 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.config.CompilerSettings
|
||||
import org.jetbrains.kotlin.config.additionalArgumentsAsList
|
||||
import org.jetbrains.kotlin.daemon.client.CompileServiceSession
|
||||
import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient
|
||||
import org.jetbrains.kotlin.daemon.common.*
|
||||
import org.jetbrains.kotlin.jps.build.KotlinBuilder
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -48,7 +50,17 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private @Volatile var jpsDaemonConnection: DaemonConnection? = null
|
||||
@Volatile
|
||||
private var _jpsCompileServiceSession: CompileServiceSession? = null
|
||||
|
||||
@Synchronized
|
||||
private fun getOrCreateDaemonConnection(newConnection: ()-> CompileServiceSession?): CompileServiceSession? {
|
||||
if (_jpsCompileServiceSession == null) {
|
||||
_jpsCompileServiceSession = newConnection()
|
||||
}
|
||||
|
||||
return _jpsCompileServiceSession
|
||||
}
|
||||
}
|
||||
|
||||
fun runK2JvmCompiler(
|
||||
@@ -116,14 +128,19 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
|
||||
else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName")
|
||||
}
|
||||
|
||||
val res = withDaemon(environment, retryOnConnectionError = true) { daemon, sessionId ->
|
||||
val compilerMode = CompilerMode.JPS_COMPILER
|
||||
val verbose = compilerArgs.verbose
|
||||
val options = CompilationOptions(compilerMode, targetPlatform, reportCategories(verbose), reportSeverity(verbose), requestedCompilationResults = emptyArray())
|
||||
daemon.compile(sessionId, serializeWithAdditionalCompilerArgs(compilerArgs), options, JpsCompilerServicesFacadeImpl(environment), null)
|
||||
log.debug("Try to connect to daemon")
|
||||
val connection = getDaemonConnection(environment)
|
||||
if (connection == null) {
|
||||
log.info("Could not connect to daemon")
|
||||
return null
|
||||
}
|
||||
|
||||
return res?.get()?.let { exitCodeFromProcessExitCode(it) }
|
||||
val (daemon, sessionId) = connection
|
||||
val compilerMode = CompilerMode.JPS_COMPILER
|
||||
val verbose = compilerArgs.verbose
|
||||
val options = CompilationOptions(compilerMode, targetPlatform, reportCategories(verbose), reportSeverity(verbose), requestedCompilationResults = emptyArray())
|
||||
val res = daemon.compile(sessionId, serializeWithAdditionalCompilerArgs(compilerArgs), options, JpsCompilerServicesFacadeImpl(environment), null)
|
||||
return exitCodeFromProcessExitCode(res.get())
|
||||
}
|
||||
|
||||
private fun withAdditionalArguments(compilerArgs: CommonCompilerArguments): CommonCompilerArguments {
|
||||
@@ -213,17 +230,15 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getDaemonConnection(environment: JpsCompilerEnvironment): DaemonConnection {
|
||||
if (jpsDaemonConnection == null) {
|
||||
val libPath = CompilerRunnerUtil.getLibPath(environment.kotlinPaths, environment.messageCollector)
|
||||
val compilerPath = File(libPath, "kotlin-compiler.jar")
|
||||
val compilerId = CompilerId.makeCompilerId(compilerPath)
|
||||
// TODO: pass daemon options to newDaemonConnection
|
||||
val daemonOptions = configureDaemonOptions()
|
||||
val flagFile = makeAutodeletingFlagFile("compiler-jps-session-", File(daemonOptions.runFilesPathOrDefault))
|
||||
jpsDaemonConnection = newDaemonConnection(compilerId, flagFile, environment, daemonOptions)
|
||||
}
|
||||
return jpsDaemonConnection!!
|
||||
}
|
||||
override fun getDaemonConnection(environment: JpsCompilerEnvironment): CompileServiceSession? =
|
||||
getOrCreateDaemonConnection {
|
||||
val libPath = CompilerRunnerUtil.getLibPath(environment.kotlinPaths, environment.messageCollector)
|
||||
val compilerPath = File(libPath, "kotlin-compiler.jar")
|
||||
val compilerId = CompilerId.makeCompilerId(compilerPath)
|
||||
val daemonOptions = configureDaemonOptions()
|
||||
|
||||
val clientFlagFile = KotlinCompilerClient.getOrCreateClientFlagFile(daemonOptions)
|
||||
val sessionFlagFile = makeAutodeletingFlagFile("compiler-jps-session-", File(daemonOptions.runFilesPathOrDefault))
|
||||
newDaemonConnection(compilerId, clientFlagFile, sessionFlagFile, environment, daemonOptions)
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.gradle
|
||||
|
||||
import org.jetbrains.kotlin.compilerRunner.COULD_NOT_CONNECT_TO_DAEMON_MESSAGE
|
||||
import org.jetbrains.kotlin.compilerRunner.CREATED_SESSION_FILE_PREFIX
|
||||
import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX
|
||||
import org.jetbrains.kotlin.compilerRunner.EXISTING_SESSION_FILE_PREFIX
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
// todo: test client file creation/deletion
|
||||
// todo: test daemon start (does not start every build)
|
||||
// todo: test daemon shutdown when gradle daemon dies
|
||||
class KotlinDaemonAdvaced : BaseGradleIT() {
|
||||
companion object {
|
||||
private const val GRADLE_VERSION = "2.10"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDaemonMultiproject() {
|
||||
fun String.findAllStringsPrefixed(prefix: String): Array<String> {
|
||||
val result = arrayListOf<String>()
|
||||
val regex = ("$prefix([^\\r\\n]*)").toRegex()
|
||||
for (match in regex.findAll(this)) {
|
||||
result.add(match.groupValues[1])
|
||||
}
|
||||
return result.toTypedArray()
|
||||
}
|
||||
|
||||
val project = Project("multiprojectWithDependency", GRADLE_VERSION)
|
||||
val strategyCLIArg = "-Dkotlin.compiler.execution.strategy=daemon"
|
||||
|
||||
fun checkAfterNonIncrementalBuild(output: String) {
|
||||
val createdSessions = output.findAllStringsPrefixed(CREATED_SESSION_FILE_PREFIX)
|
||||
assert(createdSessions.size == 1) { "Created multiple sessions per build ${createdSessions.joinToString()}" }
|
||||
|
||||
val existingSessions = output.findAllStringsPrefixed(EXISTING_SESSION_FILE_PREFIX)
|
||||
Assert.assertArrayEquals("Existing sessions don't match created sessions for two module projects", createdSessions, existingSessions)
|
||||
|
||||
val deletedSessions = output.findAllStringsPrefixed(DELETED_SESSION_FILE_PREFIX)
|
||||
Assert.assertArrayEquals("Deleted sessions don't match created sessions", createdSessions, deletedSessions)
|
||||
}
|
||||
|
||||
project.build("build", strategyCLIArg) {
|
||||
assertNotContains(COULD_NOT_CONNECT_TO_DAEMON_MESSAGE)
|
||||
checkAfterNonIncrementalBuild(output)
|
||||
}
|
||||
|
||||
project.build("clean", "build", strategyCLIArg) {
|
||||
assertNotContains(COULD_NOT_CONNECT_TO_DAEMON_MESSAGE)
|
||||
checkAfterNonIncrementalBuild(output)
|
||||
}
|
||||
|
||||
project.build("build", strategyCLIArg) {
|
||||
val createdSessions = output.findAllStringsPrefixed(CREATED_SESSION_FILE_PREFIX)
|
||||
Assert.assertArrayEquals("Sessions should not be created (incremental build)", createdSessions, emptyArray())
|
||||
|
||||
val existingSessions = output.findAllStringsPrefixed(EXISTING_SESSION_FILE_PREFIX)
|
||||
Assert.assertArrayEquals("Sessions should not be existing (incremental build)", existingSessions, emptyArray())
|
||||
|
||||
val deletedSessions = output.findAllStringsPrefixed(DELETED_SESSION_FILE_PREFIX)
|
||||
Assert.assertArrayEquals("Sessions should not be deleted (incremental build)", deletedSessions, emptyArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
+103
-53
@@ -24,33 +24,30 @@ import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.config.Services
|
||||
import org.jetbrains.kotlin.daemon.client.CompileServiceSession
|
||||
import org.jetbrains.kotlin.daemon.common.*
|
||||
import org.jetbrains.kotlin.daemon.common.CompilationResultCategory
|
||||
import org.jetbrains.kotlin.gradle.plugin.ParentLastURLClassLoader
|
||||
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
|
||||
import org.jetbrains.kotlin.incremental.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
|
||||
import java.io.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
internal const val KOTLIN_COMPILER_EXECUTION_STRATEGY_PROPERTY = "kotlin.compiler.execution.strategy"
|
||||
internal const val DAEMON_EXECUTION_STRATEGY = "daemon"
|
||||
internal const val IN_PROCESS_EXECUTION_STRATEGY = "in-process"
|
||||
internal const val OUT_OF_PROCESS_EXECUTION_STRATEGY = "out-of-process"
|
||||
const val CREATED_CLIENT_FILE_PREFIX = "Created client-is-alive flag file: "
|
||||
const val EXISTING_CLIENT_FILE_PREFIX = "Existing client-is-alive flag file: "
|
||||
const val CREATED_SESSION_FILE_PREFIX = "Created session-is-alive flag file: "
|
||||
const val EXISTING_SESSION_FILE_PREFIX = "Existing session-is-alive flag file: "
|
||||
const val DELETED_SESSION_FILE_PREFIX = "Deleted session-is-alive flag file: "
|
||||
const val COULD_NOT_CONNECT_TO_DAEMON_MESSAGE = "Could not connect to Kotlin compile daemon"
|
||||
|
||||
internal class GradleCompilerRunner(private val project: Project) : KotlinCompilerRunner<GradleCompilerEnvironment>() {
|
||||
override val log = GradleKotlinLogger(project.logger)
|
||||
private val flagFile = run {
|
||||
val dir = File(project.rootProject.buildDir, "kotlin").apply { mkdirs() }
|
||||
File(dir, "daemon-is-alive").apply {
|
||||
if (!exists()) {
|
||||
createNewFile()
|
||||
deleteOnExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun runJvmCompiler(
|
||||
sourcesToCompile: List<File>,
|
||||
@@ -132,25 +129,43 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
|
||||
}
|
||||
|
||||
override fun compileWithDaemon(compilerClassName: String, compilerArgs: CommonCompilerArguments, environment: GradleCompilerEnvironment): ExitCode? {
|
||||
val exitCode = if (environment is GradleIncrementalCompilerEnvironment) {
|
||||
incrementalCompilationWithDaemon(environment)
|
||||
}
|
||||
else {
|
||||
nonIncrementalCompilationWithDaemon(compilerClassName, environment)
|
||||
}
|
||||
exitCode?.let {
|
||||
withDaemon(environment, retryOnConnectionError = true) { daemon, sessionId ->
|
||||
daemon.clearJarCache()
|
||||
val connection = getDaemonConnection(environment)
|
||||
if (connection == null) {
|
||||
if (environment is GradleIncrementalCompilerEnvironment) {
|
||||
log.warn("Could not perform incremental compilation: $COULD_NOT_CONNECT_TO_DAEMON_MESSAGE")
|
||||
}
|
||||
logFinish(DAEMON_EXECUTION_STRATEGY)
|
||||
else {
|
||||
log.warn(COULD_NOT_CONNECT_TO_DAEMON_MESSAGE)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val (daemon, sessionId) = connection
|
||||
val exitCode = try {
|
||||
val res = if (environment is GradleIncrementalCompilerEnvironment) {
|
||||
incrementalCompilationWithDaemon(daemon, sessionId, environment)
|
||||
} else {
|
||||
nonIncrementalCompilationWithDaemon(daemon, sessionId, compilerClassName, environment)
|
||||
}
|
||||
exitCodeFromProcessExitCode(res.get())
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
log.warn("Compilation with Kotlin compile daemon was not successful")
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
// todo: can we clear cache on the end of session?
|
||||
daemon.clearJarCache()
|
||||
logFinish(DAEMON_EXECUTION_STRATEGY)
|
||||
return exitCode
|
||||
}
|
||||
|
||||
private fun nonIncrementalCompilationWithDaemon(
|
||||
daemon: CompileService,
|
||||
sessionId: Int,
|
||||
compilerClassName: String,
|
||||
environment: GradleCompilerEnvironment
|
||||
): ExitCode? {
|
||||
): CompileService.CallResult<Int> {
|
||||
val targetPlatform = when (compilerClassName) {
|
||||
K2JVM_COMPILER -> CompileService.TargetPlatform.JVM
|
||||
K2JS_COMPILER -> CompileService.TargetPlatform.JS
|
||||
@@ -166,21 +181,15 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
|
||||
reportSeverity = reportSeverity(verbose),
|
||||
requestedCompilationResults = emptyArray())
|
||||
val servicesFacade = GradleCompilerServicesFacadeImpl(project, environment.messageCollector)
|
||||
|
||||
val res = withDaemon(environment, retryOnConnectionError = true) { daemon, sessionId ->
|
||||
val argsArray = ArgumentUtils.convertArgumentsToStringList(environment.compilerArgs).toTypedArray()
|
||||
daemon.compile(sessionId, argsArray, compilationOptions, servicesFacade, compilationResults = null)
|
||||
}
|
||||
|
||||
val exitCode = res?.get()?.let { exitCodeFromProcessExitCode(it) }
|
||||
if (exitCode == null) {
|
||||
log.warn("Could not perform non-incremental compilation with Kotlin compile daemon, " +
|
||||
"non-incremental compilation in fallback mode will be performed")
|
||||
}
|
||||
return exitCode
|
||||
val argsArray = ArgumentUtils.convertArgumentsToStringList(environment.compilerArgs).toTypedArray()
|
||||
return daemon.compile(sessionId, argsArray, compilationOptions, servicesFacade, compilationResults = null)
|
||||
}
|
||||
|
||||
private fun incrementalCompilationWithDaemon(environment: GradleIncrementalCompilerEnvironment): ExitCode? {
|
||||
private fun incrementalCompilationWithDaemon(
|
||||
daemon: CompileService,
|
||||
sessionId: Int,
|
||||
environment: GradleIncrementalCompilerEnvironment
|
||||
): CompileService.CallResult<Int> {
|
||||
val knownChangedFiles = environment.changedFiles as? ChangedFiles.Known
|
||||
|
||||
val verbose = environment.compilerArgs.verbose
|
||||
@@ -198,18 +207,8 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
|
||||
targetPlatform = CompileService.TargetPlatform.JVM
|
||||
)
|
||||
val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(project, environment)
|
||||
|
||||
val res = withDaemon(environment, retryOnConnectionError = true) { daemon, sessionId ->
|
||||
val argsArray = ArgumentUtils.convertArgumentsToStringList(environment.compilerArgs).toTypedArray()
|
||||
daemon.compile(sessionId, argsArray, compilationOptions, servicesFacade, GradleCompilationResults(project))
|
||||
}
|
||||
|
||||
val exitCode = res?.get()?.let { exitCodeFromProcessExitCode(it) }
|
||||
if (exitCode == null) {
|
||||
log.warn("Could not perform incremental compilation with Kotlin compile daemon, " +
|
||||
"non-incremental compilation in fallback mode will be performed")
|
||||
}
|
||||
return exitCode
|
||||
val argsArray = ArgumentUtils.convertArgumentsToStringList(environment.compilerArgs).toTypedArray()
|
||||
return daemon.compile(sessionId, argsArray, compilationOptions, servicesFacade, GradleCompilationResults(project))
|
||||
}
|
||||
|
||||
private fun reportCategories(verbose: Boolean): Array<Int> =
|
||||
@@ -286,10 +285,61 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
|
||||
log.debug("Finished executing kotlin compiler using $strategy strategy")
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getDaemonConnection(environment: GradleCompilerEnvironment): DaemonConnection {
|
||||
override fun getDaemonConnection(environment: GradleCompilerEnvironment): CompileServiceSession? {
|
||||
val compilerId = CompilerId.makeCompilerId(environment.compilerClasspath)
|
||||
return newDaemonConnection(compilerId, flagFile, environment)
|
||||
val clientIsAliveFlagFile = getOrCreateClientFlagFile(project)
|
||||
val sessionIsAliveFlagFile = getOrCreateSessionFlagFile(project)
|
||||
return newDaemonConnection(compilerId, clientIsAliveFlagFile, sessionIsAliveFlagFile, environment)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// created once per gradle instance
|
||||
// when gradle daemon dies, kotlin daemon should die too
|
||||
// however kotlin daemon (if it idles enough) can die before gradle daemon dies
|
||||
@Volatile
|
||||
private var clientIsAliveFlagFile: File? = null
|
||||
|
||||
@Synchronized
|
||||
private fun getOrCreateClientFlagFile(project: Project): File {
|
||||
val log = project.logger
|
||||
if (clientIsAliveFlagFile == null || !clientIsAliveFlagFile!!.exists()) {
|
||||
val projectName = project.rootProject.name.normalizeForFlagFile()
|
||||
clientIsAliveFlagFile = FileUtil.createTempFile("kotlin-compiler-in-$projectName-", ".alive")
|
||||
log.kotlinDebug { CREATED_CLIENT_FILE_PREFIX + clientIsAliveFlagFile!!.relativeToRoot(project) }
|
||||
}
|
||||
else {
|
||||
log.kotlinDebug { EXISTING_CLIENT_FILE_PREFIX + clientIsAliveFlagFile!!.relativeToRoot(project) }
|
||||
}
|
||||
|
||||
return clientIsAliveFlagFile!!
|
||||
}
|
||||
|
||||
private fun String.normalizeForFlagFile(): String {
|
||||
val validChars = ('a'..'z') + ('0'..'9') + "-_"
|
||||
return filter { it in validChars }
|
||||
}
|
||||
|
||||
// session is created per build
|
||||
@Volatile
|
||||
private var sessionFlagFile: File? = null
|
||||
|
||||
// session files are deleted at org.jetbrains.kotlin.gradle.plugin.KotlinGradleBuildServices.buildFinished
|
||||
@Synchronized
|
||||
private fun getOrCreateSessionFlagFile(project: Project): File {
|
||||
val log = project.logger
|
||||
if (sessionFlagFile == null || !sessionFlagFile!!.exists()) {
|
||||
val sessionFilesDir = sessionsDir(project).apply { mkdirs() }
|
||||
sessionFlagFile = FileUtil.createTempFile(sessionFilesDir, "kotlin-compiler-", ".salive")
|
||||
log.kotlinDebug { CREATED_SESSION_FILE_PREFIX + sessionFlagFile!!.relativeToRoot(project) }
|
||||
}
|
||||
else {
|
||||
log.kotlinDebug { EXISTING_SESSION_FILE_PREFIX + sessionFlagFile!!.relativeToRoot(project) }
|
||||
}
|
||||
|
||||
return sessionFlagFile!!
|
||||
}
|
||||
|
||||
internal fun sessionsDir(project: Project): File =
|
||||
File(File(project.rootProject.buildDir, "kotlin"), "sessions")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
@@ -24,9 +24,12 @@ import org.gradle.api.logging.Logging
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.vfs.impl.ZipHandler
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
|
||||
import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX
|
||||
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
import org.jetbrains.kotlin.incremental.BuildCacheStorage
|
||||
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
|
||||
import org.jetbrains.kotlin.incremental.relativeToRoot
|
||||
import java.io.File
|
||||
|
||||
internal class KotlinGradleBuildServices private constructor(gradle: Gradle): BuildAdapter() {
|
||||
@@ -91,6 +94,22 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle): Bu
|
||||
log.kotlinDebug("Skipping kotlin cleanup since compiler wasn't called")
|
||||
}
|
||||
|
||||
val rootProject = gradle.rootProject
|
||||
val sessionsDir = GradleCompilerRunner.sessionsDir(rootProject)
|
||||
if (sessionsDir.exists()) {
|
||||
val sessionFiles = sessionsDir.listFiles()
|
||||
|
||||
// it is expected that only one session file per build exists
|
||||
// afaik is is not possible to run multiple gradle builds in one project since gradle locks some dirs
|
||||
if (sessionFiles.size > 1) {
|
||||
log.warn("w: Detected multiple Kotlin daemon sessions at ${sessionsDir.relativeToRoot(rootProject)}")
|
||||
}
|
||||
for (file in sessionFiles) {
|
||||
file.delete()
|
||||
log.kotlinDebug { DELETED_SESSION_FILE_PREFIX + file.relativeToRoot(rootProject) }
|
||||
}
|
||||
}
|
||||
|
||||
if (kotlinCompilerCalled) {
|
||||
startMemory?.let { startMemoryCopy ->
|
||||
getUsedMemoryKb()?.let { endMemory ->
|
||||
|
||||
+4
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
|
||||
internal fun File.isJavaFile() =
|
||||
@@ -38,3 +39,6 @@ internal fun File.relativeOrCanonical(base: File): String =
|
||||
|
||||
internal fun Iterable<File>.pathsAsStringRelativeTo(base: File): String =
|
||||
map { it.relativeOrCanonical(base) }.sorted().joinToString()
|
||||
|
||||
internal fun File.relativeToRoot(project: Project): String =
|
||||
relativeOrCanonical(project.rootProject.rootDir)
|
||||
+2
-2
@@ -197,7 +197,7 @@ class SourceSectionsTest : TestCaseWithTmpdir() {
|
||||
messageCollector.clear()
|
||||
val outputs = arrayListOf<OutputMessageUtil.Output>()
|
||||
|
||||
val code = KotlinCompilerClient.compile(daemonWithSession!!.service, daemonWithSession.sessionId, CompileService.TargetPlatform.JVM,
|
||||
val code = KotlinCompilerClient.compile(daemonWithSession!!.compileService, daemonWithSession.sessionId, CompileService.TargetPlatform.JVM,
|
||||
args, messageCollector,
|
||||
{ outFile, srcFiles -> outputs.add(OutputMessageUtil.Output(srcFiles, outFile)) },
|
||||
reportSeverity = ReportSeverity.DEBUG)
|
||||
@@ -211,7 +211,7 @@ class SourceSectionsTest : TestCaseWithTmpdir() {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
daemonWithSession!!.service.shutdown()
|
||||
daemonWithSession!!.compileService.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user