diff --git a/compiler/compiler-runner/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt b/compiler/compiler-runner/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt index 8bd12479ed5..a6d48da9ebd 100644 --- a/compiler/compiler-runner/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt +++ b/compiler/compiler-runner/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt @@ -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 { 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() @@ -71,13 +76,19 @@ abstract class KotlinCompilerRunner { 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 { environment: Env ): ExitCode? - protected fun 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 diff --git a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt index 837e761c838..797a4918a77 100644 --- a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt +++ b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinCompilerClient.kt @@ -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") diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 234336f8f89..031973c608c 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -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]) diff --git a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/JpsKotlinCompilerRunner.kt b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/JpsKotlinCompilerRunner.kt index 7326e908566..c5dda295025 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/JpsKotlinCompilerRunner.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/JpsKotlinCompilerRunner.kt @@ -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() { } 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() { 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() { } } - @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) + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinDaemonAdvaced.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinDaemonAdvaced.kt new file mode 100644 index 00000000000..b9fa3dba0f3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinDaemonAdvaced.kt @@ -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 { + val result = arrayListOf() + 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()) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt index 80684359f07..1719567a6b0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt @@ -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() { 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, @@ -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 { 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 { 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 = @@ -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") } } - diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt index 779a7420ee9..b243da2a248 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt @@ -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 -> diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/incremental/fileUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/incremental/fileUtils.kt index 0129e427e03..ca187adfe23 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/incremental/fileUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/incremental/fileUtils.kt @@ -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.pathsAsStringRelativeTo(base: File): String = map { it.relativeOrCanonical(base) }.sorted().joinToString() + +internal fun File.relativeToRoot(project: Project): String = + relativeOrCanonical(project.rootProject.rootDir) \ No newline at end of file diff --git a/plugins/source-sections/source-sections-compiler/tests/org/jetbrains/kotlin/sourceSections/SourceSectionsTest.kt b/plugins/source-sections/source-sections-compiler/tests/org/jetbrains/kotlin/sourceSections/SourceSectionsTest.kt index 0954f54bca0..4be24d1870d 100644 --- a/plugins/source-sections/source-sections-compiler/tests/org/jetbrains/kotlin/sourceSections/SourceSectionsTest.kt +++ b/plugins/source-sections/source-sections-compiler/tests/org/jetbrains/kotlin/sourceSections/SourceSectionsTest.kt @@ -197,7 +197,7 @@ class SourceSectionsTest : TestCaseWithTmpdir() { messageCollector.clear() val outputs = arrayListOf() - 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() } } }