From f00149ad054fae20b20f327b72f63b59517fac3b Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Thu, 27 Apr 2017 09:36:12 +0200 Subject: [PATCH] Reuse daemon launching logic for out-of-process compilation in gradle --- .../daemon/client/KotlinCompilerClient.kt | 52 +++++++------------ .../daemon/client/NativePlatformUtil.kt | 38 +++++++++----- .../GradleKotlinCompilerRunner.kt | 47 +++++++++++++++-- 3 files changed, 89 insertions(+), 48 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 efdcf2f18cd..954c7f676b8 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 @@ -338,25 +338,6 @@ object KotlinCompilerClient { return null } - private fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String = "daemon client") { - out?.println("[$source] ${category.name}: $message") - messages?.add(DaemonReportMessage(category, "[$source] $message")) - messageCollector?.let { - when (category) { - DaemonReportCategory.DEBUG -> it.report(CompilerMessageSeverity.LOGGING, message) - DaemonReportCategory.INFO -> it.report(CompilerMessageSeverity.INFO, message) - DaemonReportCategory.EXCEPTION -> it.report(CompilerMessageSeverity.EXCEPTION, message) - } - } - compilerServices?.let { - when (category) { - DaemonReportCategory.DEBUG -> it.report(ReportCategory.DAEMON_MESSAGE, ReportSeverity.DEBUG, message, source) - DaemonReportCategory.INFO -> it.report(ReportCategory.DAEMON_MESSAGE, ReportSeverity.INFO, message, source) - DaemonReportCategory.EXCEPTION -> it.report(ReportCategory.EXCEPTION, ReportSeverity.ERROR, message, source) - } - } - } - private fun tryFindSuitableDaemonOrNewOpts(registryDir: File, compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, report: (DaemonReportCategory, String) -> Unit): Pair { registryDir.mkdirs() val timestampMarker = createTempFile("kotlin-daemon-client-tsmarker", directory = registryDir) @@ -397,19 +378,7 @@ object KotlinCompilerClient { val processBuilder = ProcessBuilder(args) processBuilder.redirectErrorStream(true) // assuming daemon process is deaf and (mostly) silent, so do not handle streams - val daemon = - try { - launchWithNativePlatformLauncher(processBuilder) - } - catch (e: IOException) { - reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start daemon with native process launcher, falling back to ProcessBuilder#start (${e.cause})") - null - } - catch (e: NoClassDefFoundError) { - reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start") - null - } - ?: processBuilder.start() + val daemon = launchProcessWithFallback(processBuilder, reportingTargets, "daemon client") val isEchoRead = Semaphore(1) isEchoRead.acquire() @@ -478,6 +447,25 @@ class DaemonReportingTargets(val out: PrintStream? = null, val messageCollector: MessageCollector? = null, val compilerServices: CompilerServicesFacadeBase? = null) +internal fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String? = null) { + val sourceMessage: String by lazy { source?.let { "[$it] $message" } ?: message } + out?.println("${category.name}: $sourceMessage") + messages?.add(DaemonReportMessage(category, sourceMessage)) + messageCollector?.let { + when (category) { + DaemonReportCategory.DEBUG -> it.report(CompilerMessageSeverity.LOGGING, sourceMessage) + DaemonReportCategory.INFO -> it.report(CompilerMessageSeverity.INFO, sourceMessage) + DaemonReportCategory.EXCEPTION -> it.report(CompilerMessageSeverity.EXCEPTION, sourceMessage) + } + } + compilerServices?.let { + when (category) { + DaemonReportCategory.DEBUG -> it.report(ReportCategory.DAEMON_MESSAGE, ReportSeverity.DEBUG, message, source) + DaemonReportCategory.INFO -> it.report(ReportCategory.DAEMON_MESSAGE, ReportSeverity.INFO, message, source) + DaemonReportCategory.EXCEPTION -> it.report(ReportCategory.EXCEPTION, ReportSeverity.ERROR, message, source) + } + } +} internal fun isProcessAlive(process: Process) = try { diff --git a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/NativePlatformUtil.kt b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/NativePlatformUtil.kt index 21e3004b587..7119bc99e9d 100644 --- a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/NativePlatformUtil.kt +++ b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/NativePlatformUtil.kt @@ -16,17 +16,31 @@ package org.jetbrains.kotlin.daemon.client -import net.rubygrapefruit.platform.Native -import net.rubygrapefruit.platform.NativeException -import net.rubygrapefruit.platform.ProcessLauncher +import org.jetbrains.kotlin.daemon.common.DaemonReportCategory import java.io.IOException -internal fun launchWithNativePlatformLauncher(processBuilder: ProcessBuilder): Process { - return try { - val nativeLauncher = Native.get(ProcessLauncher::class.java) - nativeLauncher.start(processBuilder) - } - catch (e: NativeException) { - throw IOException(e) - } -} \ No newline at end of file +private class NativePlatformLauncherWrapper { + fun launch(processBuilder: ProcessBuilder): Process = + try { + val nativeLauncher = net.rubygrapefruit.platform.Native.get(net.rubygrapefruit.platform.ProcessLauncher::class.java) + nativeLauncher.start(processBuilder) + } + catch (e: net.rubygrapefruit.platform.NativeException) { + throw IOException(e) + } +} + + +fun launchProcessWithFallback(processBuilder: ProcessBuilder, reportingTargets: DaemonReportingTargets, reportingSource: String = "process launcher"): Process = + try { + NativePlatformLauncherWrapper().launch(processBuilder) + } + catch (e: IOException) { + reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start (${e.cause})") + null + } + catch (e: NoClassDefFoundError) { + reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start") + null + } + ?: processBuilder.start() 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 a2ac5a98b03..69b112a3465 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,9 +24,15 @@ 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.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer 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.client.DaemonReportingTargets +import org.jetbrains.kotlin.daemon.client.launchProcessWithFallback import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.gradle.plugin.ParentLastURLClassLoader import org.jetbrains.kotlin.gradle.plugin.kotlinDebug @@ -49,6 +55,34 @@ const val COULD_NOT_CONNECT_TO_DAEMON_MESSAGE = "Could not connect to Kotlin com internal class GradleCompilerRunner(private val project: Project) : KotlinCompilerRunner() { override val log = GradleKotlinLogger(project.logger) + // used only for process launching so far, but implements unused proper contract + private val loggingMessageCollector: MessageCollector by lazy { + object : MessageCollector { + private var hasErrors = false + private val messageRenderer = MessageRenderer.PLAIN_FULL_PATHS + + override fun clear() { + hasErrors = false + } + + override fun hasErrors(): Boolean = hasErrors + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) { + val locMessage = messageRenderer.render(severity, message, location) + when (severity) { + CompilerMessageSeverity.EXCEPTION -> log.error(locMessage) + CompilerMessageSeverity.ERROR, + CompilerMessageSeverity.STRONG_WARNING, + CompilerMessageSeverity.WARNING, + CompilerMessageSeverity.INFO -> log.info(locMessage) + CompilerMessageSeverity.LOGGING -> log.debug(locMessage) + CompilerMessageSeverity.OUTPUT -> { + } + } + } + } + } + fun runJvmCompiler( sourcesToCompile: List, javaSourceRoots: Iterable, @@ -69,11 +103,17 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil args.destination = null } + var deleteModuleFile = true + try { - return runCompiler(K2JVM_COMPILER, args, environment) + val res = runCompiler(K2JVM_COMPILER, args, environment) + deleteModuleFile = (res == ExitCode.OK || System.getProperty("kotlin.compiler.leave.module.file.on.error") == null) + return res } finally { - moduleFile.delete() + if (deleteModuleFile) { + moduleFile.delete() + } } } @@ -249,8 +289,7 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil val javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java" val classpathString = environment.compilerClasspath.map {it.absolutePath}.joinToString(separator = File.pathSeparator) val builder = ProcessBuilder(javaBin, "-cp", classpathString, compilerClassName, *argsArray) - val processLauncher = Native.get(ProcessLauncher::class.java) - val process = processLauncher.start(builder) + val process = launchProcessWithFallback(builder, DaemonReportingTargets(messageCollector = loggingMessageCollector)) // important to read inputStream, otherwise the process may hang on some systems val readErrThread = thread {