diff --git a/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt index cc7aa7f342d..c44482d72e9 100644 --- a/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt +++ b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt @@ -16,8 +16,9 @@ package org.jetbrains.kotlin.rmi.kotlinr -import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache +import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents import org.jetbrains.kotlin.modules.TargetId +import org.jetbrains.kotlin.progress.CompilationCanceledStatus import org.jetbrains.kotlin.rmi.* import java.io.* import java.rmi.ConnectException @@ -27,6 +28,12 @@ import java.util.concurrent.TimeUnit import kotlin.concurrent.thread +public class CompilationServices( + val incrementalCompilationComponents: IncrementalCompilationComponents? = null, + val compilationCanceledStatus: CompilationCanceledStatus? = null +) + + public object KotlinCompilerClient { val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L @@ -93,14 +100,14 @@ public object KotlinCompilerClient { } - public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit { - KotlinCompilerClient.connectToCompileService(CompilerId(), DaemonJVMOptions(), daemonOptions, DaemonReportingTargets(out = System.out), autostart = false, checkId = false) + public fun shutdownCompileService(compilerId: CompilerId, daemonOptions: DaemonOptions): Unit { + KotlinCompilerClient.connectToCompileService(compilerId, DaemonJVMOptions(), daemonOptions, DaemonReportingTargets(out = System.out), autostart = false, checkId = false) ?.shutdown() } - public fun shutdownCompileService(): Unit { - shutdownCompileService(DaemonOptions()) + public fun shutdownCompileService(compilerId: CompilerId): Unit { + shutdownCompileService(compilerId, DaemonOptions()) } @@ -108,7 +115,7 @@ public object KotlinCompilerClient { val outStrm = RemoteOutputStreamServer(out) try { - return compiler.remoteCompile(args, outStrm, CompileService.OutputFormat.PLAIN, outStrm) + return compiler.remoteCompile(args, makeRemoteServices(CompilationServices()), outStrm, CompileService.OutputFormat.PLAIN, outStrm) } finally { outStrm.disconnect() @@ -118,14 +125,13 @@ public object KotlinCompilerClient { // TODO: remove jvmStatic after all use sites will switch to kotlin @JvmStatic - public fun incrementalCompile(compiler: CompileService, args: Array, caches: Map, compilerOut: OutputStream, daemonOut: OutputStream): Int { + public fun incrementalCompile(compiler: CompileService, args: Array, services: CompilationServices, compilerOut: OutputStream, daemonOut: OutputStream): Int { val compilerOutStreamServer = RemoteOutputStreamServer(compilerOut) val daemonOutStreamServer = RemoteOutputStreamServer(daemonOut) val cacheServers = hashMapOf() try { - caches.mapValuesTo(cacheServers, { RemoteIncrementalCacheServer(it.getValue()) }) - return compiler.remoteIncrementalCompile(args, cacheServers, compilerOutStreamServer, CompileService.OutputFormat.XML, daemonOutStreamServer) + return compiler.remoteIncrementalCompile(args, makeRemoteServices(services), compilerOutStreamServer, CompileService.OutputFormat.XML, daemonOutStreamServer) } finally { cacheServers.forEach { it.getValue().disconnect() } @@ -135,6 +141,12 @@ public object KotlinCompilerClient { } + fun makeRemoteServices(services: CompilationServices): CompileService.RemoteCompilationServices = + CompileService.RemoteCompilationServices( + incrementalCompilationComponents = if (services.incrementalCompilationComponents == null) null else RemoteIncrementalCompilationComponentsServer(services.incrementalCompilationComponents), + compilationCanceledStatus = if (services.compilationCanceledStatus == null) null else RemoteCompilationCanceledStatusServer(services.compilationCanceledStatus) + ) + data class ClientOptions( public var stop: Boolean = false ) : OptionsGroup { @@ -196,7 +208,7 @@ public object KotlinCompilerClient { val memBefore = daemon.getUsedMemory() / 1024 val startTime = System.nanoTime() - val res = daemon.remoteCompile(filteredArgs.toArrayList().toTypedArray(), outStrm, CompileService.OutputFormat.PLAIN, outStrm) + val res = daemon.remoteCompile(filteredArgs.toArrayList().toTypedArray(), makeRemoteServices(CompilationServices()), outStrm, CompileService.OutputFormat.PLAIN, outStrm) val endTime = System.nanoTime() println("Compilation result code: $res") diff --git a/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteCompilationCanceledStatusServer.kt b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteCompilationCanceledStatusServer.kt new file mode 100644 index 00000000000..1557d6dee22 --- /dev/null +++ b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteCompilationCanceledStatusServer.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2015 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.rmi.kotlinr + +import org.jetbrains.kotlin.progress.CompilationCanceledStatus +import org.jetbrains.kotlin.rmi.CompileService +import org.jetbrains.kotlin.rmi.LoopbackNetworkInterface +import org.jetbrains.kotlin.rmi.SOCKET_ANY_FREE_PORT +import java.rmi.server.UnicastRemoteObject + + +public class RemoteCompilationCanceledStatusServer(val base: CompilationCanceledStatus, port: Int = SOCKET_ANY_FREE_PORT) : CompileService.RemoteCompilationCanceledStatus { + + init { + UnicastRemoteObject.exportObject(this, port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory) + } + + override fun checkCanceled() { + base.checkCanceled() + } +} \ No newline at end of file diff --git a/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteIncrementalCompilationComponentsServer.kt b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteIncrementalCompilationComponentsServer.kt new file mode 100644 index 00000000000..52eddc4c2a8 --- /dev/null +++ b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteIncrementalCompilationComponentsServer.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2015 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.rmi.kotlinr + +import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents +import org.jetbrains.kotlin.modules.TargetId +import org.jetbrains.kotlin.rmi.CompileService +import org.jetbrains.kotlin.rmi.LoopbackNetworkInterface +import org.jetbrains.kotlin.rmi.SOCKET_ANY_FREE_PORT +import java.rmi.server.UnicastRemoteObject + + +public class RemoteIncrementalCompilationComponentsServer(val base: IncrementalCompilationComponents, val port: Int = SOCKET_ANY_FREE_PORT) : CompileService.RemoteIncrementalCompilationComponents { + + init { + UnicastRemoteObject.exportObject(this, port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory) + } + + private val cacheServers = hashMapOf() + private val lookupTrackerServer by lazy { RemoteLookupTrackerServer(base.getLookupTracker()) } + + override fun getIncrementalCache(target: TargetId): CompileService.RemoteIncrementalCache = + cacheServers.get(target) ?: { + val newServer = RemoteIncrementalCacheServer(base.getIncrementalCache(target), port) + cacheServers.put(target, newServer) + newServer + }() + + override fun getLookupTracker(): CompileService.RemoteLookupTracker = lookupTrackerServer +} diff --git a/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteLookupTrackerServer.kt b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteLookupTrackerServer.kt new file mode 100644 index 00000000000..197d8e817e6 --- /dev/null +++ b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/RemoteLookupTrackerServer.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2015 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.rmi.kotlinr + +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.incremental.components.ScopeKind +import org.jetbrains.kotlin.rmi.CompileService +import org.jetbrains.kotlin.rmi.LoopbackNetworkInterface +import org.jetbrains.kotlin.rmi.SOCKET_ANY_FREE_PORT +import java.rmi.server.UnicastRemoteObject + + +public class RemoteLookupTrackerServer(val base: LookupTracker, port: Int = SOCKET_ANY_FREE_PORT) : CompileService.RemoteLookupTracker { + + init { + UnicastRemoteObject.exportObject(this, port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory) + } + + override fun record(lookupContainingFile: String, lookupLine: Int?, lookupColumn: Int?, scopeFqName: String, scopeKind: ScopeKind, name: String) { + base.record(lookupContainingFile, lookupLine, lookupColumn, scopeFqName, scopeKind, name) + } + + private val _isDoNothing: Boolean = base == LookupTracker.DO_NOTHING + override fun isDoNothing(): Boolean = _isDoNothing +} + diff --git a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/CompileService.kt b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/CompileService.kt index 6c01ffba748..302d2281c9a 100644 --- a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/CompileService.kt +++ b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/CompileService.kt @@ -16,7 +16,9 @@ package org.jetbrains.kotlin.rmi +import org.jetbrains.kotlin.incremental.components.ScopeKind import org.jetbrains.kotlin.modules.TargetId +import java.io.Serializable import java.rmi.Remote import java.rmi.RemoteException @@ -47,6 +49,38 @@ public interface CompileService : Remote { public fun close() } + public interface RemoteLookupTracker : Remote { + @Throws(RemoteException::class) + fun record( + lookupContainingFile: String, + lookupLine: Int?, + lookupColumn: Int?, + scopeFqName: String, + scopeKind: ScopeKind, + name: String + ) + @Throws(RemoteException::class) + fun isDoNothing(): Boolean + } + + public interface RemoteIncrementalCompilationComponents : Remote { + @Throws(RemoteException::class) + public fun getIncrementalCache(target: TargetId): RemoteIncrementalCache + + @Throws(RemoteException::class) + public fun getLookupTracker(): RemoteLookupTracker + } + + public interface RemoteCompilationCanceledStatus : Remote { + @Throws(RemoteException::class) + fun checkCanceled(): Unit + } + + public data class RemoteCompilationServices( + public val incrementalCompilationComponents: RemoteIncrementalCompilationComponents? = null, + public val compilationCanceledStatus: RemoteCompilationCanceledStatus? = null + ) : Serializable + @Throws(RemoteException::class) public fun getCompilerId(): CompilerId @@ -59,6 +93,7 @@ public interface CompileService : Remote { @Throws(RemoteException::class) public fun remoteCompile( args: Array, + services: RemoteCompilationServices, compilerOutputStream: RemoteOutputStream, outputFormat: OutputFormat, serviceOutputStream: RemoteOutputStream @@ -67,7 +102,7 @@ public interface CompileService : Remote { @Throws(RemoteException::class) public fun remoteIncrementalCompile( args: Array, - caches: Map, + services: RemoteCompilationServices, compilerOutputStream: RemoteOutputStream, compilerOutputFormat: OutputFormat, serviceOutputStream: RemoteOutputStream diff --git a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt index 48e8f164c45..73c573edab1 100644 --- a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt +++ b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt @@ -36,6 +36,7 @@ public val COMPILE_DAEMON_ENABLED_PROPERTY: String = "kotlin.daemon.enabled" public val COMPILE_DAEMON_JVM_OPTIONS_PROPERTY: String = "kotlin.daemon.jvm.options" public val COMPILE_DAEMON_OPTIONS_PROPERTY: String = "kotlin.daemon.options" public val COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY: String = "kotlin.daemon.client.alive.path" +public val COMPILE_DAEMON_LOG_PATH_PROPERTY: String = "kotlin.daemon.log.path" public val COMPILE_DAEMON_REPORT_PERF_PROPERTY: String = "kotlin.daemon.perf" public val COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY: String = "kotlin.daemon.verbose" public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String = "--daemon-" @@ -284,7 +285,7 @@ public data class CompilerId( public fun isDaemonEnabled(): Boolean = System.getProperty(COMPILE_DAEMON_ENABLED_PROPERTY) != null -public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits: Boolean): DaemonJVMOptions { +public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits: Boolean, vararg additionalParams: String): DaemonJVMOptions { // note: sequence matters, explicit override in COMPILE_DAEMON_JVM_OPTIONS_PROPERTY should be done after inputArguments processing if (inheritMemoryLimits) { ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-") @@ -299,12 +300,14 @@ public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let { opts.jvmParams.add("D" + COMPILE_DAEMON_REPORT_PERF_PROPERTY) } System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY)?.let { opts.jvmParams.add("D" + COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) } + System.getProperty(COMPILE_DAEMON_LOG_PATH_PROPERTY)?.let { opts.jvmParams.add("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"$it\"" ) } + opts.jvmParams.addAll(additionalParams) return opts } -public fun configureDaemonJVMOptions(inheritMemoryLimits: Boolean): DaemonJVMOptions = - configureDaemonJVMOptions(DaemonJVMOptions(), inheritMemoryLimits = inheritMemoryLimits) +public fun configureDaemonJVMOptions(inheritMemoryLimits: Boolean, vararg additionalParams: String): DaemonJVMOptions = + configureDaemonJVMOptions(DaemonJVMOptions(), inheritMemoryLimits = inheritMemoryLimits, additionalParams = *additionalParams) public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions { diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt index a8354de5a8f..c27218e41f6 100644 --- a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileDaemon.kt @@ -59,18 +59,19 @@ class LogStream(name: String) : OutputStream() { public object CompileDaemon { init { - val logPath: String = System.getProperty("kotlin.daemon.log.path")?.trimEnd('/','\\') ?: "%t" val logTime: String = SimpleDateFormat("yyyy-MM-dd.HH-mm-ss-SSS").format(Date()) + val (logPath: String, fileIsGiven: Boolean) = + System.getProperty(COMPILE_DAEMON_LOG_PATH_PROPERTY)?.trimQuotes()?.let { Pair(it, File(it).isFile) } ?: Pair("%t", false) val cfg: String = - "handlers = java.util.logging.FileHandler\n" + - "java.util.logging.FileHandler.level = ALL\n" + - "java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" + - "java.util.logging.FileHandler.encoding = UTF-8\n" + - "java.util.logging.FileHandler.limit = 1073741824\n" + // 1Mb - "java.util.logging.FileHandler.count = 3\n" + - "java.util.logging.FileHandler.append = false\n" + - "java.util.logging.FileHandler.pattern = $logPath/$COMPILE_DAEMON_DEFAULT_FILES_PREFIX.$logTime.%u%g.log\n" + - "java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s\\n\n" + "handlers = java.util.logging.FileHandler\n" + + "java.util.logging.FileHandler.level = ALL\n" + + "java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" + + "java.util.logging.FileHandler.encoding = UTF-8\n" + + "java.util.logging.FileHandler.limit = ${if (fileIsGiven) 0 else (1 shl 20)}\n" + // if file is provided - disabled, else - 1Mb + "java.util.logging.FileHandler.count = ${if (fileIsGiven) 1 else 3}\n" + + "java.util.logging.FileHandler.append = $fileIsGiven\n" + + "java.util.logging.FileHandler.pattern = ${if (fileIsGiven) logPath else (logPath + File.separator + "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX.$logTime.%u%g.log")}\n" + + "java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s\\n\n" LogManager.getLogManager().readConfiguration(cfg.byteInputStream()) } diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt index 872f3a3beed..2a239d0fdca 100644 --- a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt @@ -19,22 +19,17 @@ package org.jetbrains.kotlin.rmi.service import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.config.Services -import org.jetbrains.kotlin.incremental.components.LookupTracker -import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents -import org.jetbrains.kotlin.modules.TargetId +import org.jetbrains.kotlin.progress.CompilationCanceledStatus import org.jetbrains.kotlin.rmi.* -import java.io.IOException import java.io.PrintStream import java.lang.management.ManagementFactory import java.lang.management.ThreadMXBean -import java.net.URLClassLoader import java.rmi.NoSuchObjectException import java.rmi.registry.Registry import java.rmi.server.UnicastRemoteObject import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantReadWriteLock -import java.util.jar.Manifest import java.util.logging.Logger import kotlin.concurrent.read import kotlin.concurrent.write @@ -65,6 +60,7 @@ class CompileServiceImpl>( } override fun remoteCompile(args: Array, + services: CompileService.RemoteCompilationServices, compilerOutputStream: RemoteOutputStream, outputFormat: CompileService.OutputFormat, serviceOutputStream: RemoteOutputStream @@ -77,7 +73,7 @@ class CompileServiceImpl>( } override fun remoteIncrementalCompile(args: Array, - caches: Map, + services: CompileService.RemoteCompilationServices, compilerOutputStream: RemoteOutputStream, compilerOutputFormat: CompileService.OutputFormat, serviceOutputStream: RemoteOutputStream @@ -85,7 +81,7 @@ class CompileServiceImpl>( doCompile(args, compilerOutputStream, serviceOutputStream) { printStream -> when (compilerOutputFormat) { CompileService.OutputFormat.PLAIN -> throw NotImplementedError("Only XML output is supported in remote incremental compilation") - CompileService.OutputFormat.XML -> compiler.execAndOutputXml(printStream, createCompileServices(caches), *args) + CompileService.OutputFormat.XML -> compiler.execAndOutputXml(printStream, createCompileServices(services), *args) } } @@ -127,13 +123,6 @@ class CompileServiceImpl>( alive = true } - private class IncrementalCompilationComponentsImpl(val targetToCache: Map): IncrementalCompilationComponents { - // perf: cheap object, but still the pattern may be costly if there are too many calls to cache with the same id (which seems not to be the case now) - override fun getIncrementalCache(target: TargetId): IncrementalCache = RemoteIncrementalCacheClient(targetToCache[target]!!) - // TODO: add appropriate proxy into interaction when lookup tracker is needed - override fun getLookupTracker(): LookupTracker = LookupTracker.DO_NOTHING - } - private fun doCompile(args: Array, compilerMessagesStreamProxy: RemoteOutputStream, serviceOutputStreamProxy: RemoteOutputStream, body: (PrintStream) -> ExitCode): Int = ifAlive { val compilerMessagesStream = PrintStream(RemoteOutputStreamClient(compilerMessagesStreamProxy)) @@ -145,14 +134,12 @@ class CompileServiceImpl>( } } - private fun createCompileServices(incrementalCaches: Map): Services = - Services.Builder() - .register(IncrementalCompilationComponents::class.java, IncrementalCompilationComponentsImpl(incrementalCaches)) - // TODO: add remote proxy for cancellation status tracking -// .register(javaClass(), object: CompilationCanceledStatus { -// override fun checkCanceled(): Unit = if (context.getCancelStatus().isCanceled()) throw CompilationCanceledException() -// }) - .build() + private fun createCompileServices(services: CompileService.RemoteCompilationServices): Services { + val builder = Services.Builder() + services.incrementalCompilationComponents?.let { builder.register(IncrementalCompilationComponents::class.java, RemoteIncrementalCompilationComponentsClient(it)) } + services.compilationCanceledStatus?.let { builder.register(CompilationCanceledStatus::class.java, RemoteCompilationCanceledStatusClient(it)) } + return builder.build() + } fun usedMemory(): Long { diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteCompilationCanceledStatusClient.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteCompilationCanceledStatusClient.kt new file mode 100644 index 00000000000..423def3e8c0 --- /dev/null +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteCompilationCanceledStatusClient.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2015 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.rmi.service + +import org.jetbrains.kotlin.progress.CompilationCanceledStatus +import org.jetbrains.kotlin.rmi.CompileService + + +class RemoteCompilationCanceledStatusClient(val proxy: CompileService.RemoteCompilationCanceledStatus): CompilationCanceledStatus { + override fun checkCanceled() { + proxy.checkCanceled() + } +} diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteIncrementalCompilationComponentsClient.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteIncrementalCompilationComponentsClient.kt new file mode 100644 index 00000000000..e6ce460e0c3 --- /dev/null +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteIncrementalCompilationComponentsClient.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2015 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.rmi.service + +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache +import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents +import org.jetbrains.kotlin.modules.TargetId +import org.jetbrains.kotlin.rmi.CompileService + + +class RemoteIncrementalCompilationComponentsClient(val proxy: CompileService.RemoteIncrementalCompilationComponents) : IncrementalCompilationComponents { + + override fun getIncrementalCache(target: TargetId): IncrementalCache = RemoteIncrementalCacheClient(proxy.getIncrementalCache(target)) + + override fun getLookupTracker(): LookupTracker = RemoteLookupTrackerClient(proxy.getLookupTracker()) +} diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteLookupTrackerClient.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteLookupTrackerClient.kt new file mode 100644 index 00000000000..e3d1fd2c0aa --- /dev/null +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/RemoteLookupTrackerClient.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2015 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.rmi.service + +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.incremental.components.ScopeKind +import org.jetbrains.kotlin.rmi.CompileService + + +class RemoteLookupTrackerClient(val proxy: CompileService.RemoteLookupTracker) : LookupTracker { + + private val isDoNothing = proxy.isDoNothing() + + override fun record(lookupContainingFile: String, lookupLine: Int?, lookupColumn: Int?, scopeFqName: String, scopeKind: ScopeKind, name: String) { + if (!isDoNothing) + proxy.record(lookupContainingFile, lookupLine, lookupColumn, scopeFqName, scopeKind, name) + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 8ac0f4709fd..17df44c2cbd 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -30,54 +30,78 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() { data class CompilerResults(val resultCode: Int, val out: String) - val daemonOptions by lazy(LazyThreadSafetyMode.NONE) { DaemonOptions(runFilesPath = tmpdir.absolutePath) } - val daemonJVMOptions by lazy(LazyThreadSafetyMode.NONE) { DaemonJVMOptions() } - val compilerId by lazy(LazyThreadSafetyMode.NONE) { - CompilerId.makeCompilerId( - File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler.jar"), - File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-runtime.jar"), - File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-reflect.jar")) + val compilerClassPath = listOf( + File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler.jar")) + val compilerId by lazy(LazyThreadSafetyMode.NONE) { CompilerId.makeCompilerId(compilerClassPath) } + + private fun compileOnDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, vararg args: String): CompilerResults { + val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true, checkId = true) + TestCase.assertNotNull("failed to connect daemon", daemon) + val strm = ByteArrayOutputStream() + val code = KotlinCompilerClient.compile(daemon!!, args, strm) + return CompilerResults(code, strm.toString()) } - private fun compileOnDaemon(args: Array): CompilerResults { - System.setProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY, "") - try { - val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true, checkId = true) - TestCase.assertNotNull("failed to connect daemon", daemon) - val strm = ByteArrayOutputStream() - val code = KotlinCompilerClient.compile(daemon!!, args, strm) - return CompilerResults(code, strm.toString()) - } - finally { - System.clearProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) - } - } - - private fun runDaemonCompilerTwice(logName: String, vararg arguments: String): Unit { - KotlinCompilerClient.shutdownCompileService(daemonOptions) - - try { - val res1 = compileOnDaemon(arguments) + private fun runDaemonCompilerTwice(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, vararg args: String): Unit { + val res1 = compileOnDaemon(compilerId, daemonJVMOptions, daemonOptions, *args) TestCase.assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode) - val res2 = compileOnDaemon(arguments) + val res2 = compileOnDaemon(compilerId, daemonJVMOptions, daemonOptions, *args) TestCase.assertEquals("second compilation failed:\n${res2.out}", 0, res2.resultCode) TestCase.assertEquals("build results differ", CliBaseTest.removePerfOutput(res1.out), CliBaseTest.removePerfOutput(res2.out)) - // TODO: add performance comparison assert - } - finally { - KotlinCompilerClient.shutdownCompileService(daemonOptions) - } } private fun getTestBaseDir(): String = JetTestUtils.getTestDataPathBase() + "/integration/smoke/" + getTestName(true) + private fun getHelloAppBaseDir(): String = JetTestUtils.getTestDataPathBase() + "/integration/smoke/helloApp" private fun run(logName: String, vararg args: String): Int = runJava(getTestBaseDir(), logName, *args) - public fun testHelloApp() { - val jar = tmpdir.absolutePath + File.separator + "hello.jar" - runDaemonCompilerTwice("hello.compile", "-include-runtime", File(getTestBaseDir(), "hello.kt").absolutePath, "-d", jar) - run("hello.run", "-cp", jar, "Hello.HelloPackage") + public fun testHelloApp() { + val flagFile = createTempFile(getTestName(true), ".alive") + flagFile.deleteOnExit() + val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath, + clientAliveFlagPath = flagFile.absolutePath) + + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + + val logFile = createTempFile("kotlin-daemon-test.", ".log") + + val daemonJVMOptions = configureDaemonJVMOptions(false, + "D$COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY", + "D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.absolutePath}\"") + var daemonShotDown = false + + try { + val jar = tmpdir.absolutePath + File.separator + "hello.jar" + runDaemonCompilerTwice(compilerId, daemonJVMOptions, daemonOptions, + "-include-runtime", File(getTestBaseDir(), "hello.kt").absolutePath, "-d", jar) + + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + daemonShotDown = true + var compileTime1 = 0L + var compileTime2 = 0L + logFile.reader().useLines { + it.ifNotContainsSequence( LinePattern("Kotlin compiler daemon version"), + LinePattern("Starting compilation with args: "), + LinePattern("Elapsed time: (\\d+) ms", { it.groups.get(1)?.value?.toLong()?.let { compileTime1 = it }; true } ), + LinePattern("Starting compilation with args: "), + LinePattern("Elapsed time: (\\d+) ms", { it.groups.get(1)?.value?.toLong()?.let { compileTime2 = it }; true } ), + LinePattern("Shutdown complete")) + { unmatchedPattern, lineNo -> + TestCase.fail("pattern not found in the input: " + unmatchedPattern.regex + + "\nunmatched part of the log file (" + logFile.absolutePath + + ") from line " + lineNo + ":\n\n" + logFile.reader().useLines { it.drop(lineNo).joinToString("\n") }) + } + } + TestCase.assertTrue("Expecting that compilation 1 ($compileTime1 ms) is at least two times longer than compilation 2 ($compileTime2 ms)", + compileTime1 > compileTime2 * 2) + logFile.delete() + run("hello.run", "-cp", jar, "Hello.HelloPackage") + } + finally { + if (!daemonShotDown) + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + } } public fun testDaemonJvmOptionsParsing() { @@ -91,12 +115,12 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() { TestCase.assertEquals(arrayListOf("aaa", "bbb,ccc", "ddd", "xxx,yyy"), opts.jvmParams) } finally { - backupJvmOptions?.let { System.setProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY, it) } + restoreSystemProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY, backupJvmOptions) } } public fun testDaemonOptionsParsing() { - val backupJvmOptions = System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY) + val backupOptions = System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY) try { System.setProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, "runFilesPath=abcd,clientAliveFlagPath=efgh,autoshutdownIdleSeconds=1111") val opts = configureDaemonOptions() @@ -105,7 +129,71 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() { TestCase.assertEquals(1111, opts.autoshutdownIdleSeconds) } finally { - backupJvmOptions?.let { System.setProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, it) } + restoreSystemProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, backupOptions) + } + } + + public fun testDaemonInstances() { + val jar = tmpdir.absolutePath + File.separator + "hello1.jar" + val flagFile = createTempFile(getTestName(true), ".alive") + flagFile.deleteOnExit() + val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath, + clientAliveFlagPath = flagFile.absolutePath) + val compilerId2 = CompilerId.makeCompilerId(compilerClassPath + + File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar")) + + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions) + + val logFile1 = createTempFile("kotlin-daemon1-test", ".log") + val logFile2 = createTempFile("kotlin-daemon2-test", ".log") + val daemonJVMOptions1 = + configureDaemonJVMOptions(false, + "D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile1.absolutePath}\"") + val daemonJVMOptions2 = + configureDaemonJVMOptions(false, + "D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile2.absolutePath}\"") + + TestCase.assertTrue(logFile1.length() == 0L && logFile2.length() == 0L) + + val res1 = compileOnDaemon(compilerId, daemonJVMOptions1, daemonOptions, + "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar) + TestCase.assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode) + + logFile1.assertLogContainsSequence("Starting compilation with args: ") + TestCase.assertEquals("expecting '${logFile2.absolutePath}' to be empty", 0L, logFile2.length()) + + val res2 = compileOnDaemon(compilerId2, daemonJVMOptions2, daemonOptions, + "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar) + TestCase.assertEquals("second compilation failed:\n${res2.out}", 0, res1.resultCode) + + logFile2.assertLogContainsSequence("Starting compilation with args: ") + + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + logFile1.assertLogContainsSequence("Shutdown complete") + logFile1.delete() + + KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions) + logFile2.assertLogContainsSequence("Shutdown complete") + logFile2.delete() + } +} + + +fun File.assertLogContainsSequence(vararg patterns: String) { + reader().useLines { + it.ifNotContainsSequence( patterns.map { LinePattern(it) }) + { + pattern,lineNo -> TestCase.fail("Pattern '${pattern.regex}' is not found in the log file '$absolutePath'") } } } + +fun restoreSystemProperty(propertyName: String, backupValue: String?) { + if (backupValue == null) { + System.clearProperty(propertyName) + } + else { + System.setProperty(propertyName, backupValue) + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/LogCheckUtil.kt b/compiler/tests/org/jetbrains/kotlin/daemon/LogCheckUtil.kt new file mode 100644 index 00000000000..6b04f1f4c40 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/daemon/LogCheckUtil.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2015 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.daemon + +import kotlin.text.MatchResult +import kotlin.text.Regex + +/** + * holder of a [regex] and optional [matchCheck] for additional checks on match result + */ +internal class LinePattern(val regex: Regex, val matchCheck: (MatchResult) -> Boolean = { true }) +internal fun LinePattern(regex: String, matchCheck: (MatchResult) -> Boolean = { true }) = LinePattern(regex.toRegex(), matchCheck) + +/** + * calls [body] if receiver does not contain complete sequence of lines matched by [patternsIter], separated by any number of other lines + * [body] receives first unmatched pattern and index of last matched line in the sequence + */ +internal fun Sequence.ifNotContainsSequence(patternsIter: Iterator, + body: (LinePattern, Int) -> Unit) : Unit { + class Accumulator(it: Iterator) { + val iter = EndBoundIteratorWithValue(it) + var lineNo = 1 + var lastMatchedLineNo = 0 + fun nextLineAndPattern(): Accumulator { iter.traverseNext(); lastMatchedLineNo = lineNo; return nextLine() } + fun nextLine(): Accumulator { lineNo++; return this } + } + val res = fold(Accumulator(patternsIter)) + { acc, line -> + when { + !acc.iter.isValid() -> return@fold acc + acc.iter.value.regex.match(line)?.let { acc.iter.value.matchCheck(it) } ?: false -> acc.nextLineAndPattern() + else -> acc.nextLine() + } + } + if (res.iter.isValid()) { + body(res.iter.value, res.lastMatchedLineNo) + } +} + + +/** + * calls [body] if receiver does not contain complete sequence of lines matched by [patterns], separated by any number of other lines + * [body] receives first unmatched pattern and index of last matched line in the sequence + */ +internal fun Sequence.ifNotContainsSequence(patterns: List, + body: (LinePattern, Int) -> Unit): Unit { + ifNotContainsSequence(patterns.iterator(), body) +} + + +/** + * calls [body] if receiver does not contain complete sequence of lines matched by [patterns], separated by any number of other lines + * [body] receives first unmatched pattern and index of last matched line in the sequence + */ +internal fun Sequence.ifNotContainsSequence(vararg patterns: LinePattern, + body: (LinePattern, Int) -> Unit): Unit { + ifNotContainsSequence(patterns.iterator(), body) +} + + +// emulates Stepanov's / STL iterator, but with "embedded" end check via isValid: +// iterator points to a current value and upon init points to the first element or is invalid +// allows to express some algorithms more concisely +private class EndBoundIteratorWithValue>(val base: Iter) { + private var _value: T? = base.nextOrNull() + + val value: T get() = _value ?: throw Exception("Dereferencing invalid iterator") + + fun isValid(): Boolean = _value != null + + fun traverseNext(): EndBoundIteratorWithValue { + _value = base.nextOrNull() + return this + } +} + +private fun Iterator.nextOrNull(): T? = if (hasNext()) next() else null diff --git a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java deleted file mode 100644 index 3710979bd86..00000000000 --- a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2010-2015 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.compilerRunner; - -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.xmlb.XmlSerializerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.cli.common.ExitCode; -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.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.MessageCollectorUtil; -import org.jetbrains.kotlin.config.CompilerSettings; -import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache; -import org.jetbrains.kotlin.modules.TargetId; -import org.jetbrains.kotlin.rmi.*; -import org.jetbrains.kotlin.rmi.kotlinr.DaemonReportCategory; -import org.jetbrains.kotlin.rmi.kotlinr.DaemonReportMessage; -import org.jetbrains.kotlin.rmi.kotlinr.DaemonReportingTargets; -import org.jetbrains.kotlin.rmi.kotlinr.KotlinCompilerClient; -import org.jetbrains.kotlin.utils.UtilsPackage; - -import java.io.*; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION; -import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR; -import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.INFO; - -public class KotlinCompilerRunner { - private static final String K2JVM_COMPILER = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler"; - private static final String K2JS_COMPILER = "org.jetbrains.kotlin.cli.js.K2JSCompiler"; - private static final String INTERNAL_ERROR = ExitCode.INTERNAL_ERROR.toString(); - - public static void runK2JvmCompiler( - CommonCompilerArguments commonArguments, - K2JVMCompilerArguments k2jvmArguments, - CompilerSettings compilerSettings, - MessageCollector messageCollector, - CompilerEnvironment environment, - Map incrementalCaches, - File moduleFile, - OutputItemsCollector collector - ) { - K2JVMCompilerArguments arguments = mergeBeans(commonArguments, k2jvmArguments); - setupK2JvmArguments(moduleFile, arguments); - - runCompiler(K2JVM_COMPILER, arguments, compilerSettings.getAdditionalArguments(), messageCollector, collector, environment, - incrementalCaches); - } - - public static void runK2JsCompiler( - @NotNull CommonCompilerArguments commonArguments, - @NotNull K2JSCompilerArguments k2jsArguments, - @NotNull CompilerSettings compilerSettings, - @NotNull MessageCollector messageCollector, - @NotNull CompilerEnvironment environment, - Map incrementalCaches, - @NotNull OutputItemsCollector collector, - @NotNull Collection sourceFiles, - @NotNull List libraryFiles, - @NotNull File outputFile - ) { - K2JSCompilerArguments arguments = mergeBeans(commonArguments, k2jsArguments); - setupK2JsArguments(outputFile, sourceFiles, libraryFiles, arguments); - - runCompiler(K2JS_COMPILER, arguments, compilerSettings.getAdditionalArguments(), messageCollector, collector, environment, - incrementalCaches); - } - - private static void ProcessCompilerOutput( - MessageCollector messageCollector, - OutputItemsCollector collector, - ByteArrayOutputStream stream, - String exitCode - ) { - BufferedReader reader = new BufferedReader(new StringReader(stream.toString())); - CompilerOutputParser.parseCompilerMessagesFromReader(messageCollector, reader, collector); - - if (INTERNAL_ERROR.equals(exitCode)) { - reportInternalCompilerError(messageCollector); - } - } - - private static void reportInternalCompilerError(MessageCollector messageCollector) { - messageCollector.report(ERROR, "Compiler terminated with internal error", NO_LOCATION); - } - - private static void runCompiler( - String compilerClassName, - CommonCompilerArguments arguments, - String additionalArguments, - MessageCollector messageCollector, - OutputItemsCollector collector, - CompilerEnvironment environment, - Map incrementalCaches - ) { - try { - messageCollector.report(INFO, "Using kotlin-home = " + environment.getKotlinPaths().getHomePath(), NO_LOCATION); - - List argumentsList = ArgumentUtils.convertArgumentsToStringList(arguments); - argumentsList.addAll(StringUtil.split(additionalArguments, " ")); - - String[] argsArray = ArrayUtil.toStringArray(argumentsList); - - if (!tryCompileWithDaemon(messageCollector, collector, environment, incrementalCaches, argsArray)) { - // otherwise fallback to in-process - - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(stream); - - Object rc = CompilerRunnerUtil.invokeExecMethod( compilerClassName, argsArray, environment, messageCollector, out); - - // exec() returns an ExitCode object, class of which is loaded with a different class loader, - // so we take it's contents through reflection - ProcessCompilerOutput(messageCollector, collector, stream, getReturnCodeFromObject(rc)); - } - } - catch (Throwable e) { - MessageCollectorUtil.reportException(messageCollector, e); - reportInternalCompilerError(messageCollector); - } - } - - private static boolean tryCompileWithDaemon( - MessageCollector messageCollector, - OutputItemsCollector collector, - CompilerEnvironment environment, - Map incrementalCaches, - String[] argsArray - ) throws IOException { - if (incrementalCaches != null && RmiPackage.isDaemonEnabled()) { - File libPath = CompilerRunnerUtil.getLibPath(environment.getKotlinPaths(), messageCollector); - // TODO: it may be a good idea to cache the compilerId, since making it means calculating digest over jar(s) and if \\ - // the lifetime of JPS process is small anyway, we can neglect the probability of changed compiler - CompilerId compilerId = CompilerId.makeCompilerId(new File(libPath, "kotlin-compiler.jar")); - DaemonOptions daemonOptions = RmiPackage.configureDaemonOptions(); - DaemonJVMOptions daemonJVMOptions = RmiPackage.configureDaemonJVMOptions(true); - - ArrayList daemonReportMessages = new ArrayList(); - - CompileService daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, new DaemonReportingTargets(null, daemonReportMessages), true, true); - - for (DaemonReportMessage msg: daemonReportMessages) { - if (msg.getCategory() == DaemonReportCategory.EXCEPTION && daemon == null) { - messageCollector.report(CompilerMessageSeverity.INFO, - "Falling back to compilation without daemon due to error: " + msg.getMessage(), - CompilerMessageLocation.NO_LOCATION); - } - else { - messageCollector.report(CompilerMessageSeverity.INFO, msg.getMessage(), CompilerMessageLocation.NO_LOCATION); - } - } - - if (daemon != null) { - ByteArrayOutputStream compilerOut = new ByteArrayOutputStream(); - ByteArrayOutputStream daemonOut = new ByteArrayOutputStream(); - - Integer res = KotlinCompilerClient.incrementalCompile(daemon, argsArray, incrementalCaches, compilerOut, daemonOut); - - ProcessCompilerOutput(messageCollector, collector, compilerOut, res.toString()); - BufferedReader reader = new BufferedReader(new StringReader(daemonOut.toString())); - String line = null; - while ((line = reader.readLine()) != null) { - messageCollector.report(CompilerMessageSeverity.INFO, line, CompilerMessageLocation.NO_LOCATION); - } - return true; - } - } - return false; - } - - @NotNull - private static String getReturnCodeFromObject(@Nullable Object rc) throws Exception { - if (rc == null) { - return INTERNAL_ERROR; - } - else if (ExitCode.class.getName().equals(rc.getClass().getName())) { - return rc.toString(); - } - else { - throw new IllegalStateException("Unexpected return: " + rc); - } - } - - private static T mergeBeans(CommonCompilerArguments from, T to) { - // TODO: rewrite when updated version of com.intellij.util.xmlb is available on TeamCity - try { - T copy = XmlSerializerUtil.createCopy(to); - - List fromFields = collectFieldsToCopy(from.getClass()); - for (Field fromField : fromFields) { - Field toField = copy.getClass().getField(fromField.getName()); - toField.set(copy, fromField.get(from)); - } - - return copy; - } - catch (NoSuchFieldException e) { - throw UtilsPackage.rethrow(e); - } - catch (IllegalAccessException e) { - throw UtilsPackage.rethrow(e); - } - } - - private static List collectFieldsToCopy(Class clazz) { - List fromFields = new ArrayList(); - - Class currentClass = clazz; - do { - for (Field field : currentClass.getDeclaredFields()) { - int modifiers = field.getModifiers(); - if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) { - fromFields.add(field); - } - } - } - while ((currentClass = currentClass.getSuperclass()) != null); - - return fromFields; - } - - private static void setupK2JvmArguments(File moduleFile, K2JVMCompilerArguments settings) { - settings.module = moduleFile.getAbsolutePath(); - settings.noStdlib = true; - settings.noJdkAnnotations = true; - settings.noJdk = true; - } - - private static void setupK2JsArguments( - @NotNull File outputFile, - @NotNull Collection sourceFiles, - @NotNull List libraryFiles, - @NotNull K2JSCompilerArguments settings - ) { - settings.noStdlib = true; - settings.freeArgs = ContainerUtil.map(sourceFiles, new Function() { - @Override - public String fun(File file) { - return file.getPath(); - } - }); - settings.outputFile = outputFile.getPath(); - settings.metaInfo = true; - settings.libraryFiles = ArrayUtil.toStringArray(libraryFiles); - } -} diff --git a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt new file mode 100644 index 00000000000..0dd541b1b9a --- /dev/null +++ b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt @@ -0,0 +1,239 @@ +/* + * Copyright 2010-2015 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.compilerRunner + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.util.ArrayUtil +import com.intellij.util.Function +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.xmlb.XmlSerializerUtil +import org.jetbrains.kotlin.cli.common.ExitCode +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.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.INFO +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorUtil +import org.jetbrains.kotlin.config.CompilerSettings +import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents +import org.jetbrains.kotlin.progress.CompilationCanceledStatus +import org.jetbrains.kotlin.rmi.CompilerId +import org.jetbrains.kotlin.rmi.configureDaemonJVMOptions +import org.jetbrains.kotlin.rmi.configureDaemonOptions +import org.jetbrains.kotlin.rmi.isDaemonEnabled +import org.jetbrains.kotlin.rmi.kotlinr.* +import org.jetbrains.kotlin.utils.rethrow +import java.io.* +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.util.* + +public object KotlinCompilerRunner { + private val K2JVM_COMPILER = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler" + private val K2JS_COMPILER = "org.jetbrains.kotlin.cli.js.K2JSCompiler" + private val INTERNAL_ERROR = ExitCode.INTERNAL_ERROR.toString() + + public fun runK2JvmCompiler( + commonArguments: CommonCompilerArguments, + k2jvmArguments: K2JVMCompilerArguments, + compilerSettings: CompilerSettings, + messageCollector: MessageCollector, + environment: CompilerEnvironment, + moduleFile: File, + collector: OutputItemsCollector) { + val arguments = mergeBeans(commonArguments, k2jvmArguments) + setupK2JvmArguments(moduleFile, arguments) + + runCompiler(K2JVM_COMPILER, arguments, compilerSettings.additionalArguments, messageCollector, collector, environment) + } + + public fun runK2JsCompiler( + commonArguments: CommonCompilerArguments, + k2jsArguments: K2JSCompilerArguments, + compilerSettings: CompilerSettings, + messageCollector: MessageCollector, + environment: CompilerEnvironment, + collector: OutputItemsCollector, + sourceFiles: Collection, + libraryFiles: List, + outputFile: File) { + val arguments = mergeBeans(commonArguments, k2jsArguments) + setupK2JsArguments(outputFile, sourceFiles, libraryFiles, arguments) + + runCompiler(K2JS_COMPILER, arguments, compilerSettings.additionalArguments, messageCollector, collector, environment) + } + + private fun processCompilerOutput( + messageCollector: MessageCollector, + collector: OutputItemsCollector, + stream: ByteArrayOutputStream, + exitCode: String) { + val reader = BufferedReader(StringReader(stream.toString())) + CompilerOutputParser.parseCompilerMessagesFromReader(messageCollector, reader, collector) + + if (INTERNAL_ERROR == exitCode) { + reportInternalCompilerError(messageCollector) + } + } + + private fun reportInternalCompilerError(messageCollector: MessageCollector) { + messageCollector.report(ERROR, "Compiler terminated with internal error", CompilerMessageLocation.NO_LOCATION) + } + + private fun runCompiler( + compilerClassName: String, + arguments: CommonCompilerArguments, + additionalArguments: String, + messageCollector: MessageCollector, + collector: OutputItemsCollector, + environment: CompilerEnvironment) { + try { + messageCollector.report(INFO, "Using kotlin-home = " + environment.kotlinPaths.homePath, CompilerMessageLocation.NO_LOCATION) + + val argumentsList = ArgumentUtils.convertArgumentsToStringList(arguments) + argumentsList.addAll(additionalArguments.split(" ")) + + val argsArray = argumentsList.toTypedArray() + + if (!tryCompileWithDaemon(messageCollector, collector, environment, argsArray)) { + // otherwise fallback to in-process + + val stream = ByteArrayOutputStream() + val out = PrintStream(stream) + + val rc = CompilerRunnerUtil.invokeExecMethod(compilerClassName, argsArray, environment, messageCollector, out) + + // exec() returns an ExitCode object, class of which is loaded with a different class loader, + // so we take it's contents through reflection + processCompilerOutput(messageCollector, collector, stream, getReturnCodeFromObject(rc)) + } + } + catch (e: Throwable) { + MessageCollectorUtil.reportException(messageCollector, e) + reportInternalCompilerError(messageCollector) + } + + } + + private fun tryCompileWithDaemon(messageCollector: MessageCollector, + collector: OutputItemsCollector, + environment: CompilerEnvironment, + argsArray: Array): Boolean { + + if (isDaemonEnabled()) { + val libPath = CompilerRunnerUtil.getLibPath(environment.kotlinPaths, messageCollector) + // TODO: it may be a good idea to cache the compilerId, since making it means calculating digest over jar(s) and if \\ + // the lifetime of JPS process is small anyway, we can neglect the probability of changed compiler + val compilerId = CompilerId.makeCompilerId(File(libPath, "kotlin-compiler.jar")) + val daemonOptions = configureDaemonOptions() + val daemonJVMOptions = configureDaemonJVMOptions(true) + + val daemonReportMessages = ArrayList() + + val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true) + + for (msg in daemonReportMessages) { + if (msg.category === DaemonReportCategory.EXCEPTION && daemon == null) { + messageCollector.report(CompilerMessageSeverity.INFO, + "Falling back to compilation without daemon due to error: " + msg.message, + CompilerMessageLocation.NO_LOCATION) + } + else { + messageCollector.report(CompilerMessageSeverity.INFO, msg.message, CompilerMessageLocation.NO_LOCATION) + } + } + + if (daemon != null) { + val compilerOut = ByteArrayOutputStream() + val daemonOut = ByteArrayOutputStream() + + val services = CompilationServices( + incrementalCompilationComponents = environment.services.get(IncrementalCompilationComponents::class.java), + compilationCanceledStatus = environment.services.get(CompilationCanceledStatus::class.java)) + + val res = KotlinCompilerClient.incrementalCompile(daemon, argsArray, services, compilerOut, daemonOut) + + processCompilerOutput(messageCollector, collector, compilerOut, res.toString()) + BufferedReader(StringReader(daemonOut.toString())).forEachLine { + messageCollector.report(CompilerMessageSeverity.INFO, it, CompilerMessageLocation.NO_LOCATION) + } + return true + } + } + return false + } + + private fun getReturnCodeFromObject(rc: Any?): String { + when { + rc == null -> return INTERNAL_ERROR + ExitCode::class.java.name == rc.javaClass.name -> return rc.toString() + else -> throw IllegalStateException("Unexpected return: " + rc) + } + } + + private fun mergeBeans(from: CommonCompilerArguments, to: T): T { + // TODO: rewrite when updated version of com.intellij.util.xmlb is available on TeamCity + val copy = XmlSerializerUtil.createCopy(to) + + val fromFields = collectFieldsToCopy(from.javaClass) + for (fromField in fromFields) { + val toField = copy.javaClass.getField(fromField.name) + toField.set(copy, fromField.get(from)) + } + + return copy + } + + private fun collectFieldsToCopy(clazz: Class<*>): List { + val fromFields = ArrayList() + + var currentClass: Class<*>? = clazz + while (currentClass != null) { + for (field in currentClass.declaredFields) { + val modifiers = field.modifiers + if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) { + fromFields.add(field) + } + } + currentClass = currentClass.superclass + } + + return fromFields + } + + private fun setupK2JvmArguments(moduleFile: File, settings: K2JVMCompilerArguments) { + with(settings) { + module = moduleFile.absolutePath + noStdlib = true + noJdkAnnotations = true + noJdk = true + } + } + + private fun setupK2JsArguments( _outputFile: File, sourceFiles: Collection, _libraryFiles: List, settings: K2JSCompilerArguments) { + with(settings) { + noStdlib = true + freeArgs = sourceFiles.map { it.path } + outputFile = _outputFile.path + metaInfo = true + libraryFiles = _libraryFiles.toTypedArray() + } + } +} diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt index 3c10754f873..1c028a21d29 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt @@ -47,8 +47,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil import org.jetbrains.kotlin.compilerRunner.CompilerEnvironment -import org.jetbrains.kotlin.compilerRunner.KotlinCompilerRunner.runK2JsCompiler -import org.jetbrains.kotlin.compilerRunner.KotlinCompilerRunner.runK2JvmCompiler +import org.jetbrains.kotlin.compilerRunner.KotlinCompilerRunner import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl import org.jetbrains.kotlin.config.CompilerRunnerConstants import org.jetbrains.kotlin.config.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX @@ -319,10 +318,7 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR ) } - return compileToJvm(allCompiledFiles, chunk, commonArguments, context, dirtyFilesHolder, environment, - incrementalCaches.mapKeysTo(HashMap(incrementalCaches.size()), - { TargetId(it.getKey()) }), - filesToCompile, messageCollector) + return compileToJvm(allCompiledFiles, chunk, commonArguments, context, dirtyFilesHolder, environment, filesToCompile, messageCollector) } private fun createCompileEnvironment( @@ -520,7 +516,7 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(project) val k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(project) - runK2JsCompiler(commonArguments, k2JsArguments, compilerSettings, messageCollector, environment, incrementalCaches, outputItemCollector, sourceFiles, libraryFiles, outputFile) + KotlinCompilerRunner.runK2JsCompiler(commonArguments, k2JsArguments, compilerSettings, messageCollector, environment, outputItemCollector, sourceFiles, libraryFiles, outputFile) return outputItemCollector } @@ -543,7 +539,6 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR context: CompileContext, dirtyFilesHolder: DirtyFilesHolder, environment: CompilerEnvironment, - incrementalCaches: MutableMap?, filesToCompile: MultiMap, messageCollector: MessageCollectorAdapter ): OutputItemsCollectorImpl? { val outputItemCollector = OutputItemsCollectorImpl() @@ -587,7 +582,7 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR + (if (totalRemovedFiles == 0) "" else " ($totalRemovedFiles removed files)") + " in " + filesToCompile.keySet().map { it.getPresentableName() }.join()) - runK2JvmCompiler(commonArguments, k2JvmArguments, compilerSettings, messageCollector, environment, incrementalCaches, moduleFile, outputItemCollector) + KotlinCompilerRunner.runK2JvmCompiler(commonArguments, k2JvmArguments, compilerSettings, messageCollector, environment, moduleFile, outputItemCollector) moduleFile.delete() return outputItemCollector diff --git a/jps-plugin/test/org/jetbrains/kotlin/jps/build/SimpleKotlinJpsBuildTest.kt b/jps-plugin/test/org/jetbrains/kotlin/jps/build/SimpleKotlinJpsBuildTest.kt index 3b1a7f395b5..f32820d9b48 100644 --- a/jps-plugin/test/org/jetbrains/kotlin/jps/build/SimpleKotlinJpsBuildTest.kt +++ b/jps-plugin/test/org/jetbrains/kotlin/jps/build/SimpleKotlinJpsBuildTest.kt @@ -20,6 +20,7 @@ import com.intellij.util.PathUtil import org.jetbrains.jps.model.java.JpsJavaExtensionService import org.jetbrains.kotlin.rmi.COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY import org.jetbrains.kotlin.rmi.COMPILE_DAEMON_ENABLED_PROPERTY +import org.jetbrains.kotlin.rmi.COMPILE_DAEMON_LOG_PATH_PROPERTY import org.jetbrains.kotlin.rmi.COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY import org.jetbrains.kotlin.test.JetTestUtils import java.io.File @@ -71,12 +72,15 @@ public class SimpleKotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() { System.setProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY, "") // spaces in the name to test proper file name handling val flagFile = File.createTempFile("kotlin-jps - tests-", "-is-running"); + val logFile = File.createTempFile("kotlin-daemon", ".log") + System.setProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY, flagFile.absolutePath) + System.setProperty(COMPILE_DAEMON_LOG_PATH_PROPERTY, logFile.absolutePath) try { - System.setProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY, flagFile.absolutePath) testLoadingKotlinFromDifferentModules() } finally { flagFile.delete() + System.clearProperty(COMPILE_DAEMON_LOG_PATH_PROPERTY) System.clearProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY) System.clearProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) System.clearProperty(COMPILE_DAEMON_ENABLED_PROPERTY)