Merge pull request #752 from JetBrains/rr/compile-service-3

compile service 3
This commit is contained in:
Michael Nedzelsky
2015-09-24 16:50:09 +03:00
17 changed files with 760 additions and 370 deletions
@@ -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<out String>, caches: Map<TargetId, IncrementalCache>, compilerOut: OutputStream, daemonOut: OutputStream): Int {
public fun incrementalCompile(compiler: CompileService, args: Array<out String>, services: CompilationServices, compilerOut: OutputStream, daemonOut: OutputStream): Int {
val compilerOutStreamServer = RemoteOutputStreamServer(compilerOut)
val daemonOutStreamServer = RemoteOutputStreamServer(daemonOut)
val cacheServers = hashMapOf<TargetId, RemoteIncrementalCacheServer>()
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")
@@ -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()
}
}
@@ -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<TargetId, RemoteIncrementalCacheServer>()
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
}
@@ -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
}
@@ -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<out String>,
services: RemoteCompilationServices,
compilerOutputStream: RemoteOutputStream,
outputFormat: OutputFormat,
serviceOutputStream: RemoteOutputStream
@@ -67,7 +102,7 @@ public interface CompileService : Remote {
@Throws(RemoteException::class)
public fun remoteIncrementalCompile(
args: Array<out String>,
caches: Map<TargetId, RemoteIncrementalCache>,
services: RemoteCompilationServices,
compilerOutputStream: RemoteOutputStream,
compilerOutputFormat: OutputFormat,
serviceOutputStream: RemoteOutputStream
@@ -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 {
@@ -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())
}
@@ -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<Compiler: CLICompiler<*>>(
}
override fun remoteCompile(args: Array<out String>,
services: CompileService.RemoteCompilationServices,
compilerOutputStream: RemoteOutputStream,
outputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream
@@ -77,7 +73,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
}
override fun remoteIncrementalCompile(args: Array<out String>,
caches: Map<TargetId, CompileService.RemoteIncrementalCache>,
services: CompileService.RemoteCompilationServices,
compilerOutputStream: RemoteOutputStream,
compilerOutputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream
@@ -85,7 +81,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
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<Compiler: CLICompiler<*>>(
alive = true
}
private class IncrementalCompilationComponentsImpl(val targetToCache: Map<TargetId, CompileService.RemoteIncrementalCache>): 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<out String>, compilerMessagesStreamProxy: RemoteOutputStream, serviceOutputStreamProxy: RemoteOutputStream, body: (PrintStream) -> ExitCode): Int =
ifAlive {
val compilerMessagesStream = PrintStream(RemoteOutputStreamClient(compilerMessagesStreamProxy))
@@ -145,14 +134,12 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
}
}
private fun createCompileServices(incrementalCaches: Map<TargetId, CompileService.RemoteIncrementalCache>): Services =
Services.Builder()
.register(IncrementalCompilationComponents::class.java, IncrementalCompilationComponentsImpl(incrementalCaches))
// TODO: add remote proxy for cancellation status tracking
// .register(javaClass<CompilationCanceledStatus>(), 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 {
@@ -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()
}
}
@@ -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())
}
@@ -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)
}
}
@@ -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<out String>): 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)
}
}
@@ -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<String>.ifNotContainsSequence(patternsIter: Iterator<LinePattern>,
body: (LinePattern, Int) -> Unit) : Unit {
class Accumulator(it: Iterator<LinePattern>) {
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<String>.ifNotContainsSequence(patterns: List<LinePattern>,
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<String>.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<T: Any, Iter: Iterator<T>>(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<T, Iter> {
_value = base.nextOrNull()
return this
}
}
private fun<T: Any> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else 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<TargetId, IncrementalCache> 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<TargetId, IncrementalCache> incrementalCaches,
@NotNull OutputItemsCollector collector,
@NotNull Collection<File> sourceFiles,
@NotNull List<String> 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<TargetId, IncrementalCache> incrementalCaches
) {
try {
messageCollector.report(INFO, "Using kotlin-home = " + environment.getKotlinPaths().getHomePath(), NO_LOCATION);
List<String> 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<TargetId, IncrementalCache> 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<DaemonReportMessage> daemonReportMessages = new ArrayList<DaemonReportMessage>();
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 extends CommonCompilerArguments> 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<Field> 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<Field> collectFieldsToCopy(Class<?> clazz) {
List<Field> fromFields = new ArrayList<Field>();
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<File> sourceFiles,
@NotNull List<String> libraryFiles,
@NotNull K2JSCompilerArguments settings
) {
settings.noStdlib = true;
settings.freeArgs = ContainerUtil.map(sourceFiles, new Function<File, String>() {
@Override
public String fun(File file) {
return file.getPath();
}
});
settings.outputFile = outputFile.getPath();
settings.metaInfo = true;
settings.libraryFiles = ArrayUtil.toStringArray(libraryFiles);
}
}
@@ -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<File>,
libraryFiles: List<String>,
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<String>): 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<DaemonReportMessage>()
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 <T : CommonCompilerArguments> 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<Field> {
val fromFields = ArrayList<Field>()
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<File>, _libraryFiles: List<String>, settings: K2JSCompilerArguments) {
with(settings) {
noStdlib = true
freeArgs = sourceFiles.map { it.path }
outputFile = _outputFile.path
metaInfo = true
libraryFiles = _libraryFiles.toTypedArray()
}
}
}
@@ -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<TargetId, IncrementalCache>(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<JavaSourceRootDescriptor, ModuleBuildTarget>,
environment: CompilerEnvironment,
incrementalCaches: MutableMap<TargetId, IncrementalCache>?,
filesToCompile: MultiMap<ModuleBuildTarget, File>, 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
@@ -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)