From ac86ad252f1a3c3c986c8c30b432868d44edcd1a Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Thu, 22 Apr 2021 17:38:58 +0200 Subject: [PATCH] Optionally make java executable path contribute to the daemon id. This change will allow to start Kotlin daemon instances using different from the current user JDK version and use it to compile Kotlin files. Old behaviour, when java executable path is not set, is still working and still,by default, using current user JDK version. For example, user for some reason wants to use JDK 1.8 as active one, but compile current project using JDK 16. Main goal is to support Gradle toolchains. ^KT-45611 In Progress --- .../daemon/client/KotlinCompilerClient.kt | 12 +- .../kotlin/daemon/common/DaemonParams.kt | 36 ++++- .../kotlin/daemon/CompilerDaemonTest.kt | 124 ++++++++++++++++++ 3 files changed, 166 insertions(+), 6 deletions(-) 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)