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 4ec95bfacf3..23ab923b825 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 @@ -367,8 +367,14 @@ object KotlinCompilerClient { } - private fun startDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, reportingTargets: DaemonReportingTargets): Boolean { - val javaExecutable = File(File(CompilerSystemProperties.JAVA_HOME.safeValue, "bin"), "java") + private fun startDaemon( + compilerId: CompilerId, + daemonJVMOptions: DaemonJVMOptions, + daemonOptions: DaemonOptions, + reportingTargets: DaemonReportingTargets + ): Boolean { + val daemonJavaExecutable = compilerId.javaExecutable + ?: File(File(CompilerSystemProperties.JAVA_HOME.safeValue, "bin"), "java") val serverHostname = CompilerSystemProperties.JAVA_RMI_SERVER_HOSTNAME.value ?: error("${CompilerSystemProperties.JAVA_RMI_SERVER_HOSTNAME.property} is not set!") val platformSpecificOptions = listOf( // hide daemon window @@ -380,7 +386,7 @@ object KotlinCompilerClient { listOf("--illegal-access=permit") else emptyList() val args = listOf( - javaExecutable.absolutePath, "-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) + + daemonJavaExecutable.absolutePath, "-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) + platformSpecificOptions + daemonJVMOptions.mappers.flatMap { it.toArgs("-") } + javaIllegalAccessWorkaround + diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/DaemonParams.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/DaemonParams.kt index a2bf98aa99a..5bfb0c96b6d 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/DaemonParams.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/DaemonParams.kt @@ -231,15 +231,36 @@ fun ByteArray.toHexString(): String = joinToString("", transform = { "%02x".form data class CompilerId( var compilerClasspath: List = listOf(), - var compilerVersion: String = "" + var compilerVersion: String = "", + var javaExecutable: File? = null ) : 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::compilerVersion)) + get() = listOf( + PropMapper( + dest = this, + prop = CompilerId::compilerClasspath, + toString = { it.joinToString(File.pathSeparator) }, + fromString = { it.trimQuotes().split(File.pathSeparator) } + ), + StringPropMapper( + dest = this, + prop = CompilerId::compilerVersion + ), + PropMapper( + dest = this, + prop = CompilerId::javaExecutable, + toString = { it?.absolutePath }, + fromString = { File(it.trimQuotes()) }, + skipIf = { it == null } + ) + ) fun digest(): String = compilerClasspath .map { File(it).absolutePath } + .run { + javaExecutable?.let { plus(it.absolutePath) } ?: this + } .distinctStringsDigest() .toHexString() @@ -250,6 +271,15 @@ data class CompilerId( @JvmStatic fun makeCompilerId(paths: Iterable): CompilerId = CompilerId(compilerClasspath = paths.map { it.absolutePath }) + + @JvmStatic + fun makeCompilerId( + paths: Iterable, + javaExecutable: File + ): CompilerId = CompilerId( + compilerClasspath = paths.map { it.absolutePath }, + javaExecutable = javaExecutable + ) } } diff --git a/compiler/daemon/daemon-tests/test/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/daemon/daemon-tests/test/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 05df17701cf..3af0e057806 100644 --- a/compiler/daemon/daemon-tests/test/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/daemon/daemon-tests/test/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.daemon +import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtilRt import junit.framework.TestCase @@ -277,6 +278,129 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { } } + private fun getJdk8Location() = System.getenv("JDK_18") ?: System.getenv("JAVA_HOME") + + fun testNewDaemonIsNotStartedForSameJavaExecutable() { + withFlagFile(getTestName(true), "-client1.alive") { flagFile1 -> + withFlagFile(getTestName(true), "-client2.alive") { flagFile2 -> + val daemonOptions = makeTestDaemonOptions(getTestName(true)) + val compilerIdJdk8 = CompilerId.makeCompilerId( + compilerClassPath + + File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar"), + File(getJdk8Location()).resolve("bin/java") + ) + + withLogFile("kotlin-daemon-test-1") { logFile -> + val daemonJVMOptions = makeTestDaemonJvmOptions(logFile) + assertTrue(logFile.length() == 0L) + + val daemon1 = KotlinCompilerClient.connectToCompileService( + compilerIdJdk8, + flagFile1, + daemonJVMOptions, + daemonOptions, + DaemonReportingTargets(out = System.err), + autostart = true + ) + assertNotNull("failed to connect daemon", daemon1) + logFile.assertLogContainsSequence("INFO: starting daemon") + + + val daemon2 = KotlinCompilerClient.connectToCompileService( + compilerIdJdk8, + flagFile2, + daemonJVMOptions, + daemonOptions, + DaemonReportingTargets(out = System.err), + autostart = true + ) + assertNotNull("failed to connect daemon", daemon2) + + val logContent = logFile.readText().lines() + assert( + logContent.filter { it.contains("INFO: starting daemon") }.size == 1 + ) { + "Second daemon instance was started!" + } + assert( + logContent.filter { + it.contains("INFO: Registered a client alive file: ${flagFile2.absolutePath}") + }.size == 1 + ) { + "Second client was not connected to the same instance!" + } + + KotlinCompilerClient.shutdownCompileService(compilerIdJdk8, daemonOptions) + + Thread.sleep(100) + + logFile.assertLogContainsSequence("Shutdown started") + } + } + } + } + + // Ignored on Windows OS due to https://bugs.openjdk.java.net/browse/JDK-8189953 bug in JDK 9 + // Should be unignored once JDK10+ will be available by default on CI agents + fun testNewDaemonIsStartedOnJavaExecutableChange() { + if (SystemInfo.isWindows) return + + withFlagFile(getTestName(true), "-client1.alive") { flagFile1 -> + withFlagFile(getTestName(true), "-client2.alive") { flagFile2 -> + val daemonOptions = makeTestDaemonOptions(getTestName(true)) + val compilerIdJdk8 = CompilerId.makeCompilerId( + compilerClassPath + + File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar"), + File(getJdk8Location()).resolve("bin/java") + ) + val compilerIdJdk9 = CompilerId.makeCompilerId( + compilerClassPath + + File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar"), + File(System.getenv("JDK_9")).resolve("bin/java") + ) + + withLogFile("kotlin-daemon-test-1") { logFile1 -> + withLogFile("kotlin-daemon-test-2") { logFile2 -> + val daemonJdk8JVMOptions = makeTestDaemonJvmOptions(logFile1) + assertTrue(logFile1.length() == 0L) + val daemonJdk9JVMOptions = makeTestDaemonJvmOptions(logFile2) + assertTrue(logFile2.length() == 0L) + + val daemonJdk7 = KotlinCompilerClient.connectToCompileService( + compilerIdJdk8, + flagFile1, + daemonJdk8JVMOptions, + daemonOptions, + DaemonReportingTargets(out = System.err), + autostart = true + ) + assertNotNull("failed to connect daemon", daemonJdk7) + logFile1.assertLogContainsSequence("INFO: starting daemon") + + val daemonJdk9 = KotlinCompilerClient.connectToCompileService( + compilerIdJdk9, + flagFile2, + daemonJdk9JVMOptions, + daemonOptions, + DaemonReportingTargets(out = System.err), + autostart = true + ) + assertNotNull("failed to connect daemon", daemonJdk9) + logFile2.assertLogContainsSequence("INFO: starting daemon") + + KotlinCompilerClient.shutdownCompileService(compilerIdJdk8, daemonOptions) + KotlinCompilerClient.shutdownCompileService(compilerIdJdk9, daemonOptions) + + Thread.sleep(100) + + logFile1.assertLogContainsSequence("Shutdown started") + logFile2.assertLogContainsSequence("Shutdown started") + } + } + } + } + } + fun testDaemonRunError() { withFlagFile(getTestName(true), ".alive") { flagFile -> val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 1, verbose = true, runFilesPath = File(testTempDir, getTestName(true)).absolutePath)