diff --git a/compiler/compiler.pro b/compiler/compiler.pro
index 323072c06c4..eb98a2cffd3 100644
--- a/compiler/compiler.pro
+++ b/compiler/compiler.pro
@@ -93,7 +93,6 @@ messages/**)
# for gradle plugin and other server tools
-keep class com.intellij.openapi.util.io.ZipFileCache { public *; }
--keep class com.intellij.openapi.util.LowMemoryWatcher { public *; }
# for j2k
-keep class com.intellij.codeInsight.NullableNotNullManager { public protected *; }
diff --git a/libraries/examples/kotlin-gradle-subplugin-example/pom.xml b/libraries/examples/kotlin-gradle-subplugin-example/pom.xml
index d2553cece8f..aed881a7690 100644
--- a/libraries/examples/kotlin-gradle-subplugin-example/pom.xml
+++ b/libraries/examples/kotlin-gradle-subplugin-example/pom.xml
@@ -41,7 +41,7 @@
org.jetbrains.kotlin
- kotlin-compiler
+ kotlin-compiler-embeddable
${project.version}
provided
diff --git a/libraries/pom.xml b/libraries/pom.xml
index c497fef5835..d2047e03b5c 100644
--- a/libraries/pom.xml
+++ b/libraries/pom.xml
@@ -72,6 +72,7 @@
tools/kotlin-compiler
+ tools/kotlin-compiler-embeddable
tools/kotlin-jdk-annotations
tools/kotlin-android-sdk-annotations
tools/kotlin-maven-plugin
diff --git a/libraries/tools/kotlin-compiler-embeddable/pom.xml b/libraries/tools/kotlin-compiler-embeddable/pom.xml
new file mode 100644
index 00000000000..7604f7f374b
--- /dev/null
+++ b/libraries/tools/kotlin-compiler-embeddable/pom.xml
@@ -0,0 +1,165 @@
+
+
+
+ 4.0.0
+
+ 1.4.1
+ 3.0.4
+ org.jetbrains.kotlin.relocated
+
+
+
+ org.jetbrains.kotlin
+ kotlin-project
+ 0.1-SNAPSHOT
+ ../../pom.xml
+
+
+ kotlin-compiler-embeddable
+ jar
+
+ the Kotlin compiler embeddable
+
+
+
+ org.jetbrains.kotlin
+ kotlin-compiler
+ ${project.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-runtime
+ ${project.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ ${project.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${project.version}
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 1.7
+
+
+ attach-artifacts
+ compile
+
+ attach-artifact
+
+
+
+
+ ${kotlin-dist}/kotlin-compiler-sources.jar
+ jar
+ sources
+
+
+ ${kotlin-dist}/kotlin-compiler-javadoc.jar
+ jar
+ javadoc
+
+
+
+
+
+
+ attach-empty-javadoc
+ prepare-package
+
+ attach-artifact
+
+
+ true
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.4.1
+
+
+ package
+
+ shade
+
+
+ false
+ true
+
+
+ *:kotlin-runtime
+
+ META-INF/**
+
+ *:kotlin-reflect
+
+ META-INF/**
+
+ *:kotlin-stdlib
+
+ META-INF/**
+
+
+
+
+
+ com.google
+ ${kotlin.relocated.package}.com.google
+
+
+ com.sampullara
+ ${kotlin.relocated.package}.com.sampullara
+
+
+ org.apache
+ ${kotlin.relocated.package}.org.apache
+
+
+ org.jdom
+ ${kotlin.relocated.package}.org.jdom
+
+
+ org.fusesource
+ ${kotlin.relocated.package}.org.fusesource
+
+
+ org.picocontainer
+ ${kotlin.relocated.package}.org.picocontainer
+
+
+ jline
+ ${kotlin.relocated.package}.jline
+
+
+ gnu
+ ${kotlin.relocated.package}.gnu
+
+
+ javax.inject
+ ${kotlin.relocated.package}.javax.inject
+
+
+
+
+
+
+
+
+
diff --git a/libraries/tools/kotlin-gradle-plugin-core/pom.xml b/libraries/tools/kotlin-gradle-plugin-core/pom.xml
index 8636d5d7c2a..62c431995ac 100644
--- a/libraries/tools/kotlin-gradle-plugin-core/pom.xml
+++ b/libraries/tools/kotlin-gradle-plugin-core/pom.xml
@@ -44,7 +44,7 @@
org.jetbrains.kotlin
- kotlin-compiler
+ kotlin-compiler-embeddable
${project.version}
diff --git a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/com/intellij/openapi/util/LowMemoryWatcher.java b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/com/intellij/openapi/util/LowMemoryWatcher.java
deleted file mode 100644
index 654b563f796..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/com/intellij/openapi/util/LowMemoryWatcher.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2000-2013 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 com.intellij.openapi.util;
-
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.util.ConcurrencyUtil;
-import com.intellij.util.containers.WeakList;
-import org.jetbrains.annotations.NotNull;
-
-import javax.management.ListenerNotFoundException;
-import javax.management.Notification;
-import javax.management.NotificationEmitter;
-import javax.management.NotificationListener;
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryNotificationInfo;
-import java.lang.management.MemoryPoolMXBean;
-import java.lang.management.MemoryType;
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * PATCHED VERSION of com.intellij.openapi.util.LowMemoryWatcher
- * shutdown() method added to stop background thread.
- */
-@SuppressWarnings("UnusedDeclaration")
-public class LowMemoryWatcher {
- private static final long MEM_THRESHOLD = 5 /*MB*/ * 1024 * 1024;
-
- private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.LowMemoryWatcher");
-
- private static final List ourInstances = new WeakList();
- private static final ThreadPoolExecutor ourExecutor = new ThreadPoolExecutor(0, 1, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(2), ConcurrencyUtil.newNamedThreadFactory("LowMemoryWatcher janitor"));
- private static boolean ourSubmitted;
- private static final Runnable ourJanitor = new Runnable() {
- @Override
- public void run() {
- try {
- for (LowMemoryWatcher watcher : ourInstances) {
- try {
- watcher.myRunnable.run();
- }
- catch (Throwable e) {
- LOG.info(e);
- }
- }
- }
- finally {
- synchronized (ourJanitor) {
- //noinspection AssignmentToStaticFieldFromInstanceMethod
- ourSubmitted = false;
- }
- }
- }
- };
-
- private final Runnable myRunnable;
-
- private static final NotificationListener lowMemoryListener;
-
- static {
- for (MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
- if (bean.getType() == MemoryType.HEAP && bean.isUsageThresholdSupported()) {
- long threshold = bean.getUsage().getMax() - MEM_THRESHOLD;
- if (threshold > 0) {
- bean.setUsageThreshold(threshold);
- bean.setCollectionUsageThreshold(threshold);
- }
- }
- }
-
- lowMemoryListener = new NotificationListener() {
- @Override
- public void handleNotification(@NotNull Notification n, Object hb) {
- if (MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED.equals(n.getType()) ||
- MemoryNotificationInfo.MEMORY_COLLECTION_THRESHOLD_EXCEEDED.equals(n.getType())) {
- synchronized (ourJanitor) {
- if (!ourSubmitted) {
- //noinspection AssignmentToStaticFieldFromInstanceMethod
- ourSubmitted = true;
- ourExecutor.submit(ourJanitor);
- }
- }
- }
- }
- };
-
- ((NotificationEmitter)ManagementFactory.getMemoryMXBean()).addNotificationListener(lowMemoryListener, null, null);
- }
-
- /**
- * Registers a runnable to run on low memory events
- * @return a LowMemoryWatcher instance holding the runnable. This instance should be kept in memory while the
- * low memory notification functionality is needed. As soon as it's garbage-collected, the runnable won't receive any further notifications.
- */
- public static LowMemoryWatcher register(Runnable runnable) {
- return new LowMemoryWatcher(runnable);
- }
-
- /**
- * Registers a runnable to run on low memory events. The notifications will be issued until parentDisposable is disposed.
- */
- public static void register(Runnable runnable, Disposable parentDisposable) {
- final Ref watcher = Ref.create(new LowMemoryWatcher(runnable));
- Disposer.register(parentDisposable, new Disposable() {
- @Override
- public void dispose() {
- watcher.get().stop();
- watcher.set(null);
- }
- });
- }
-
- private LowMemoryWatcher(Runnable runnable) {
- myRunnable = runnable;
- ourInstances.add(this);
- }
-
- public void stop() {
- ourInstances.remove(this);
- }
-
- /**
- * LowMemoryWatcher maintains a background thread where all the handlers are invoked.
- * In server environments, this thread may run indefinitely and prevent the class loader from
- * being gc-ed. Thus it's necessary to invoke this method to stop that thread and let the classes be garbage-collected.
- */
- public static void stopAll() throws InterruptedException, ListenerNotFoundException {
- ourInstances.clear();
- ((NotificationEmitter)ManagementFactory.getMemoryMXBean()).removeNotificationListener(lowMemoryListener);
-
- ourExecutor.shutdown();
-
- boolean terminated = ourExecutor.awaitTermination(50, TimeUnit.MICROSECONDS);
- if (!terminated) {
- ourExecutor.shutdownNow();
- }
- }
-}
diff --git a/libraries/tools/kotlin-gradle-plugin/pom.xml b/libraries/tools/kotlin-gradle-plugin/pom.xml
index 9252c989dd8..038e68f14fe 100644
--- a/libraries/tools/kotlin-gradle-plugin/pom.xml
+++ b/libraries/tools/kotlin-gradle-plugin/pom.xml
@@ -41,7 +41,6 @@
org.jetbrains.kotlin
kotlin-gradle-plugin-core
${project.version}
- test
org.jetbrains.kotlin
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/FinishBuildListener.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/FinishBuildListener.kt
index f6039acb826..55591175725 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/FinishBuildListener.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/FinishBuildListener.kt
@@ -18,64 +18,85 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.BuildAdapter
import org.gradle.BuildResult
+import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logging
import java.lang.ref.Reference
import java.util.concurrent.ScheduledExecutorService
+import kotlin.text.MatchGroup
-class FinishBuildListener(var pluginClassLoader: ParentLastURLClassLoader?) : BuildAdapter() {
+internal fun getUsedMemoryKb(): Long {
+ System.gc()
+ val rt = Runtime.getRuntime()
+ return (rt.totalMemory() - rt.freeMemory()) / 1024
+}
+
+
+private fun comparableVersionStr(version: String) =
+ "(\\d+)\\.(\\d+).*"
+ .toRegex()
+ .match(version)
+ ?.groups
+ ?.drop(1)?.take(2)
+ // checking if two subexpression groups are found and length of each is >0 and <4
+ ?.let { if (it.all { (it?.value?.length() ?: 0).let { it > 0 && it < 4 }}) it else null }
+ ?.joinToString(".", transform = { it!!.value.padStart(3, '0') })
+
+
+class FinishBuildListener(pluginClassLoader: ClassLoader?, val startMemory: Long) : BuildAdapter() {
val log = Logging.getLogger(this.javaClass)
private var threadTracker: ThreadTracker? = ThreadTracker()
+ private val cleanup = CompilerServicesCleanup(pluginClassLoader)
+
override fun buildFinished(result: BuildResult?) {
log.kotlinDebug("Build finished listener")
- stopZipFileCache()
- stopLowMemoryWatcher()
- stopJobScheduler()
-
- // TODO: Try to clean up thread locals without this ugly hack
- // TODO: Further investigation of PermGen leak (KT-6451)
- removeThreadLocals()
-
- pluginClassLoader = null
- result?.getGradle()?.removeListener(this)
-
- threadTracker?.checkThreadLeak(result?.getGradle())
- threadTracker = null
- }
-
- public fun removeThreadLocals() {
- try {
- log.kotlinDebug("Remove ChildURLClassLoader thread locals")
-
- val thread = Thread.currentThread()
- val threadLocalsField = javaClass().getDeclaredField("threadLocals")
- threadLocalsField.setAccessible(true)
-
- val threadLocalMapClass = Class.forName("java.lang.ThreadLocal\$ThreadLocalMap")
- val tableField = threadLocalMapClass.getDeclaredField("table")
- tableField.setAccessible(true)
-
- val referentField = javaClass>().getDeclaredField("referent")
- referentField.setAccessible(true)
-
- val table = tableField[threadLocalsField[thread]] as Array<*>
-
- for (entry in table) {
- if (entry != null) {
- val threadLocal = referentField[entry] as ThreadLocal<*>?
- val classLoader = threadLocal?.javaClass?.getClassLoader()
- if (classLoader is ParentLastURLClassLoader.ChildURLClassLoader) {
- threadLocal?.remove()
- }
+ val gradle = result?.getGradle()
+ if (gradle != null) {
+ // making cleanup only on recognized versions of gradle and if version < 2.4
+ // otherwise it may cause problems e.g. with JobScheduler on subsequent runs
+ // this strategy may lead to memory leaks, but prevent crashes due to destroyed JobScheduler
+ // the reason for the strategy is the following:
+ // gradle < 2.4 has problems with plugin reuse in the daemon: new calls to the plugin are made with a new classloader
+ // for every new build. With statically initialized daemons like JobScheduler that leads to big leaks of classloaders and classes,
+ // therefore to reduce leaks JobScheduler (and deprecated ZipFileCache for now) should be stopped)
+ // It should be noted that because of this behavior there are no benefits of using daemon in these versions.
+ // Starting from 2.4 gradle using cached classloaders, that leads to effective class reusing in the daemon, but
+ // in that case premature stopping of the static daemons may lead to crashes.
+ comparableVersionStr(gradle.getGradleVersion())?.let {
+ log.kotlinDebug("detected gradle version $it")
+ if (it < comparableVersionStr("2.4")!!) {
+ cleanup()
+ // checking thread leaks only then cleaning up
+ threadTracker?.checkThreadLeak(gradle)
}
}
- log.kotlinDebug("Removing ChildURLClassLoader thread locals finished successfully")
- } catch (e: Throwable) {
- log.kotlinDebug("Exception during thread locals remove: " + e)
+ threadTracker = null
+ gradle.removeListener(this)
}
+
+ // the value reported here is not necessarily a leak, since it is calculated before collecting the plugin classes
+ // but on subsequent runs in the daemon it should be rather small, then the classes are actually reused by the daemon (see above)
+ getUsedMemoryKb().let { log.kotlinDebug("[PERF] Used memory after build: $it kb (${"%+d".format(it - startMemory)} kb)") }
+ }
+}
+
+
+class CompilerServicesCleanup(private var pluginClassLoader: ClassLoader?) {
+ val log = Logging.getLogger(this.javaClass)
+
+ fun invoke() {
+ assert(pluginClassLoader != null)
+
+ log.kotlinDebug("compiler services cleanup")
+
+ // TODO: remove ZipFileCache cleanup after switching to recent idea libs
+ stopZipFileCache()
+ stopJobScheduler()
+
+ pluginClassLoader = null
}
private fun stopZipFileCache() {
@@ -83,11 +104,6 @@ class FinishBuildListener(var pluginClassLoader: ParentLastURLClassLoader?) : Bu
log.kotlinDebug("ZipFileCache finished successfully")
}
- private fun stopLowMemoryWatcher() {
- callVoidStaticMethod("com.intellij.openapi.util.LowMemoryWatcher", "stopAll")
- log.kotlinDebug("LowMemoryWatcher finished successfully")
- }
-
private fun stopJobScheduler() {
log.kotlinDebug("Stop JobScheduler")
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
index d03daf91acf..5ba80e0f296 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
@@ -13,12 +13,18 @@ import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logger
import java.lang.reflect.Method
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
+import java.net.URLClassLoader
+
+// TODO: simplify: the complicated structure is a leftover from dynamic loading of plugin core, could be significantly simplified now
abstract class KotlinBasePluginWrapper: Plugin {
+
val log = Logging.getLogger(this.javaClass)
public override fun apply(project: Project) {
+ val startMemory = getUsedMemoryKb()
+
val sourceBuildScript = findSourceBuildScript(project)
if (sourceBuildScript == null) {
log.error("Failed to determine source cofiguration of kotlin plugin. Can not download core. Please verify that this or any parent project " +
@@ -29,33 +35,13 @@ abstract class KotlinBasePluginWrapper: Plugin {
val kotlinPluginVersion = loadKotlinVersionFromResource(log)
project.getExtensions().getExtraProperties()?.set("kotlin.gradle.plugin.version", kotlinPluginVersion)
- val pluginClassLoader = createPluginIsolatedClassLoader(kotlinPluginVersion, sourceBuildScript)
- val plugin = getPlugin(pluginClassLoader, sourceBuildScript)
+ val plugin = getPlugin(this.javaClass.getClassLoader(), sourceBuildScript)
plugin.apply(project)
- project.getGradle().addBuildListener(FinishBuildListener(pluginClassLoader))
+ project.getGradle().addBuildListener(FinishBuildListener(this.javaClass.getClassLoader(), startMemory))
}
- protected abstract fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler): Plugin
-
- private fun createPluginIsolatedClassLoader(projectVersion: String, sourceBuildScript: ScriptHandler): ParentLastURLClassLoader {
- val dependencyHandler: DependencyHandler = sourceBuildScript.getDependencies()
- val configurationsContainer: ConfigurationContainer = sourceBuildScript.getConfigurations()
-
- log.kotlinDebug("Creating configuration and dependency")
- val kotlinPluginCoreCoordinates = "org.jetbrains.kotlin:kotlin-gradle-plugin-core:" + projectVersion
- val dependency = dependencyHandler.create(kotlinPluginCoreCoordinates)
- val configuration = configurationsContainer.detachedConfiguration(dependency)
-
- log.kotlinDebug("Resolving [" + kotlinPluginCoreCoordinates + "]")
- val kotlinPluginDependencies: List = configuration.getResolvedConfiguration().getFiles({ true })!!.map { it.toURI().toURL() }
- log.kotlinDebug("Resolved files: [" + kotlinPluginDependencies.toString() + "]")
- log.kotlinDebug("Load plugin in parent-last URL classloader")
- val kotlinPluginClassloader = ParentLastURLClassLoader(kotlinPluginDependencies, this.javaClass.getClassLoader())
- log.kotlinDebug("Class loader created")
-
- return kotlinPluginClassloader
- }
+ protected abstract fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler): Plugin
private fun findSourceBuildScript(project: Project): ScriptHandler? {
log.kotlinDebug("Looking for proper script handler")
@@ -69,25 +55,24 @@ abstract class KotlinBasePluginWrapper: Plugin {
return scriptHandler
}
log.kotlinDebug("not found, switching to parent")
- curProject = curProject.getParent()!!
+ curProject = curProject.getParent() ?: break
}
return null
}
}
open class KotlinPluginWrapper: KotlinBasePluginWrapper() {
- override fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler) = KotlinPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
+ override fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler) = KotlinPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
}
open class KotlinAndroidPluginWrapper : KotlinBasePluginWrapper() {
- override fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler) = KotlinAndroidPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
+ override fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler) = KotlinAndroidPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
}
open class Kotlin2JsPluginWrapper : KotlinBasePluginWrapper() {
- override fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler) = Kotlin2JsPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
+ override fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler) = Kotlin2JsPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
}
fun Logger.kotlinDebug(message: String) {
this.debug("[KOTLIN] $message")
}
-
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt
index dd43023985e..03303d3a65d 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt
@@ -27,18 +27,43 @@ abstract class BaseGradleIT {
deleteRecursively(workingDir)
}
+ class BuildOptions(val withDaemon: Boolean = false)
+
class Project(val projectName: String, val wrapperVersion: String = "1.4", val minLogLevel: LogLevel = LogLevel.DEBUG)
class CompiledProject(val project: Project, val output: String, val resultCode: Int)
- fun Project.build(vararg tasks: String, check: CompiledProject.() -> Unit) {
+ fun Project.setupWorkingDir() {
copyRecursively(File(resourcesRootFile, "testProject/$projectName"), workingDir)
- val projectDir = File(workingDir, projectName)
- copyDirRecursively(File(resourcesRootFile, "GradleWrapper-$wrapperVersion"), projectDir)
- val cmd = createCommand(tasks)
+ copyDirRecursively(File(resourcesRootFile, "GradleWrapper-$wrapperVersion"), File(workingDir, projectName))
+ }
+
+ fun Project.build(vararg tasks: String, options: BuildOptions = BuildOptions(), check: CompiledProject.() -> Unit) {
+ val cmd = createBuildCommand(tasks, options)
println("<=== Test build: ${this.projectName} $cmd ===>")
+ runAndCheck(cmd, check)
+ }
+
+ fun stopDaemon(ver: String) {
+ val wrapperDir = File(resourcesRootFile, "GradleWrapper-$ver")
+ val cmd = createGradleCommand(arrayListOf("-stop"))
+ createProcess(cmd, wrapperDir)
+ }
+
+ fun Project.stopDaemon(check: CompiledProject.() -> Unit) {
+ val cmd = createGradleCommand(arrayListOf("-stop"))
+ println("<=== Stop daemon: $cmd ===>")
+
+ runAndCheck(cmd, check)
+ }
+
+ private fun Project.runAndCheck(cmd: List, check: CompiledProject.() -> Unit) {
+ val projectDir = File(workingDir, projectName)
+ if (!projectDir.exists())
+ setupWorkingDir()
+
val process = createProcess(cmd, projectDir)
val (output, resultCode) = readOutput(process)
@@ -94,14 +119,21 @@ abstract class BaseGradleIT {
return this
}
- private fun Project.createCommand(params: Array): List {
+ private fun Project.createBuildCommand(params: Array, options: BuildOptions): List {
val pathToKotlinPlugin = "-PpathToKotlinPlugin=" + File("local-repo").getAbsolutePath()
val tailParameters = params.asList() +
- listOf(pathToKotlinPlugin, "--no-daemon", "--${minLogLevel.name().toLowerCase()}", "-Pkotlin.gradle.test=true")
+ listOf( pathToKotlinPlugin,
+ if (options.withDaemon) "--daemon" else "--no-daemon",
+ "--${minLogLevel.name().toLowerCase()}",
+ "-Pkotlin.gradle.test=true")
+ return createGradleCommand(tailParameters)
+ }
+
+ private fun createGradleCommand(tailParameters: List): List {
return if (isWindows())
listOf("cmd", "/C", "gradlew.bat") + tailParameters
- else
+ else
listOf("/bin/bash", "./gradlew") + tailParameters
}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt
index 037f19fe7db..851faa80ea9 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt
@@ -1,7 +1,9 @@
package org.jetbrains.kotlin.gradle
-import org.junit.Test
+import org.gradle.api.logging.LogLevel
import org.jetbrains.kotlin.gradle.BaseGradleIT.Project
+import org.junit.Ignore
+import org.junit.Test
class KotlinGradleIT: BaseGradleIT() {
@@ -35,6 +37,34 @@ class KotlinGradleIT: BaseGradleIT() {
}
}
+ // This test isn't safe enough: gradle daemon is a singleton process (for a chosen version) and stopping it may
+ // affect the build environment in an unpredictable way. Therefore it is now disabled.
+ // TODO research the possibility to run isolated daemon build
+ Ignore Test fun testKotlinOnlyDaemonMemory() {
+ val project = Project("kotlinProject", "2.4")
+
+ project.stopDaemon {}
+
+ // build to "warm up" the daemon, if it is not started yet
+ project.build("build", options = BaseGradleIT.BuildOptions(withDaemon = true)) {
+ assertSuccessful()
+ }
+
+ for (i in 1..3) {
+ project.build("build", options = BaseGradleIT.BuildOptions(withDaemon = true)) {
+ assertSuccessful()
+ val matches = "\\[PERF\\] Used memory after build: (\\d+) kb \\(([+-]?\\d+) kb\\)".toRegex().match(output)
+ assert(matches != null && matches.groups.size() == 3, "Used memory after build is not reported by plugin")
+ val reportedGrowth = matches!!.groups.get(2)!!.value.toInt()
+ assert(reportedGrowth <= 500, "Used memory growth $reportedGrowth > 500")
+ }
+ }
+
+ project.stopDaemon {
+ assertSuccessful()
+ }
+ }
+
Test fun testKotlinClasspath() {
Project("classpathTest", "1.6").build("build") {
assertSuccessful()
@@ -107,5 +137,4 @@ class KotlinGradleIT: BaseGradleIT() {
assertFileExists("build/classes/main/example/AncestorClassGenerated.class")
}
}
-
}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradle/wrapper/gradle-wrapper.jar b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000000..30d399d8d2b
Binary files /dev/null and b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradle/wrapper/gradle-wrapper.properties b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000000..888191f04df
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Aug 10 11:44:23 CEST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradlew b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradlew
new file mode 100755
index 00000000000..91a7e269e19
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradlew.bat b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradlew.bat
new file mode 100644
index 00000000000..8a0b282aa68
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/GradleWrapper-2.4/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega